Skip to content

Commit

Permalink
Adds generic search thumbnail retrieval (#10266)
Browse files Browse the repository at this point in the history
* Adds generic search thumbnail retrieval

* PR feedback, fix for manifest URL

* updates auto-formatting for black

* updates auto-formatting

* Update the catch block to catch additional exception if the resource does not exist
  • Loading branch information
aarongundel authored Nov 28, 2023
1 parent 1e97437 commit bc1695b
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,22 @@ function($, _, BaseFilter, bootstrap, arches, select2, ko, koMapping, GraphModel
return acc;
}, []);

this.searchResults.results.hits.hits.forEach(function(result){
this.searchResults.results.hits.hits.forEach(async function(result){
var graphdata = _.find(viewdata.graphs, function(graphdata){
return result._source.graph_id === graphdata.graphid;
});
var point = null;
if (result._source.points.length > 0) {
point = result._source.points[0].point;
}

const thumbnailUrl = `/thumbnail/${result._source.resourceinstanceid}`;
const thumbnailResponse = arches.searchThumbnails == 'True' ? await fetch(thumbnailUrl, {method: 'HEAD'}): undefined;
const thumbnail = thumbnailResponse && thumbnailResponse.ok ? thumbnailUrl: undefined;

this.results.push({
displayname: result._source.displayname,
thumbnail: thumbnail,
resourceinstanceid: result._source.resourceinstanceid,
displaydescription: result._source.displaydescription,
alternativelanguage: result._source.displayname_language != arches.activeLanguage,
Expand Down
1 change: 1 addition & 0 deletions arches/app/templates/javascript.htm
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@
active-language="{{ app_settings.ACTIVE_LANGUAGE }}"
active-language-dir="{{ app_settings.ACTIVE_LANGUAGE_DIR }}"
languages='{{ app_settings.LANGUAGES }}'
search-thumbnails='{{ app_settings.SEARCH_THUMBNAILS }}'
mapbox-api-key="{{ map_info.mapbox_api_key }}"
mapbox-glyphs="{{ map_info.mapbox_glyphs }}"
mapbox-sprites="{{ map_info.mapbox_sprites }}"
Expand Down
27 changes: 16 additions & 11 deletions arches/app/templates/views/components/search/search-results.htm
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@
<div id="search-results-list" data-bind="foreach: results, visible: true" style="display: none;">

<div class="search-listing" data-bind="event: { mouseover: mouseoverInstance, mouseout: mouseoverInstance('')}, css: {'selected': selected()}">
<h3
class="search-listing-title"
data-bind="css: {'i18n-alt': $parent.alternativelanguage}"
>
<a class="search-candidate-title" href="" data-bind="click: $parent.viewReport.bind($parent)">
<i class="search-listing-icon" data-bind="css: iconclass"></i>
<span data-bind="text: displayname"></span>
</a>
</h3>
<div style="display: flex">
<div style="flex: 1">
<h3
class="search-listing-title"
data-bind="css: {'i18n-alt': $parent.alternativelanguage}"
>
<a class="search-candidate-title" href="" data-bind="click: $parent.viewReport.bind($parent)">
<i class="search-listing-icon" data-bind="css: iconclass"></i>
<span data-bind="text: displayname"></span>
</a>
</h3>

<div class="search-listing-body" data-bind="html: displaydescription">
<div class="search-listing-body" data-bind="html: displaydescription">

</div>
</div>
<div style="display: flex; align-items: center; margin: 10px;" data-bind="visible: thumbnail"><img style="max-width: 60px;" data-bind="attr:{src: thumbnail}"></div>
</div>

<div class="search-listing-footer">
<div style="flex-grow: 1;">
<!--ko if: provisional_resource == 'true' -->
Expand Down
1 change: 1 addition & 0 deletions arches/app/utils/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def app_settings(request=None):
"VERSION": __version__,
"APP_VERSION": settings.APP_VERSION,
"APP_NAME": settings.APP_NAME,
"SEARCH_THUMBNAILS": settings.SEARCH_THUMBNAILS,
"GOOGLE_ANALYTICS_TRACKING_ID": settings.GOOGLE_ANALYTICS_TRACKING_ID,
"USE_SEMANTIC_RESOURCE_RELATIONSHIPS": settings.USE_SEMANTIC_RESOURCE_RELATIONSHIPS,
"SEARCH_EXPORT_IMMEDIATE_DOWNLOAD_THRESHOLD": settings.SEARCH_EXPORT_IMMEDIATE_DOWNLOAD_THRESHOLD,
Expand Down
16 changes: 16 additions & 0 deletions arches/app/utils/search_thumbnail_fetcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import base64
import copy
import re
from typing import List, Tuple
from urllib.error import HTTPError
from requests.exceptions import ConnectionError
import requests
from urllib.parse import urlparse, urlunparse


class SearchThumbnailFetcher(object):
def __init__(self, resource):
self.resource = resource

def get_thumbnail(self, retrieve=False):
pass
34 changes: 34 additions & 0 deletions arches/app/utils/search_thumbnail_fetcher_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging
from typing import Callable
from arches.app.models.resource import Resource

from arches.app.utils.search_thumbnail_fetcher import SearchThumbnailFetcher

logger = logging.getLogger(__name__)


class SearchThumbnailFetcherFactory(object):
registry = {}

@classmethod
def register(cls, name: str):
def inner_wrapper(wrapped_class: SearchThumbnailFetcher) -> Callable:
if name in cls.registry:
logger.warning("Search Thumbnail Fetcher %s already exists. Will replace it", name)
cls.registry[name] = wrapped_class
return wrapped_class

return inner_wrapper

@classmethod
def create_thumbnail_fetcher(cls, resource_id: str, **kwargs) -> SearchThumbnailFetcher:
"""Factory command to create the template engine"""
try:
resource = Resource.objects.get(resourceinstanceid=resource_id)
search_thumbnail_fetcher_class = cls.registry[str(resource.graph_id)]
search_thumbnail_fetcher = search_thumbnail_fetcher_class(resource, **kwargs)
return search_thumbnail_fetcher
except KeyError:
return None # there is no thumbnail fetcher registered for the graph requested
except Resource.DoesNotExist:
return None # there is no resource with this ID. This is rare, but can happen if there are issues with the index.
16 changes: 3 additions & 13 deletions arches/app/views/manifest_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@
import logging
import os
import requests
import shutil
import uuid
from revproxy.views import ProxyView
from django.core.files.storage import default_storage
from django.http.response import Http404
from django.utils.translation import gettext as _
from django.views.generic import View
from arches.app.utils.response import JSONResponse, JSONErrorResponse
from arches.app.models import models
from arches.app.models.tile import Tile
from arches.app.models.system_settings import settings
from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer
from arches.app.views.search import search_results

from arches.app.utils.betterJSONSerializer import JSONDeserializer

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -214,18 +209,13 @@ def change_canvas_label(manifest, canvas_id, label):
else:
logger.warning("filetype unacceptable: " + f.name)

pres_dict = create_manifest(name=name, canvases=canvases)
pres_dict = create_manifest(name=name, canvases=canvases, file_url=canvases[0]["thumbnail"]["service"]["@id"])
manifest_global_id = str(uuid.uuid4())
json_url = f"/manifest/{manifest_global_id}"
pres_dict["@id"] = f"{request.scheme}://{request.get_host()}{json_url}"

manifest = models.IIIFManifest.objects.create(
label=name,
description=desc,
manifest=pres_dict,
url=json_url,
globalid=manifest_global_id,
transactionid=transaction_id
label=name, description=desc, manifest=pres_dict, url=json_url, globalid=manifest_global_id, transactionid=transaction_id
)

return JSONResponse(manifest)
Expand Down
31 changes: 31 additions & 0 deletions arches/app/views/thumbnail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from django.views.generic import View
from django.http import HttpResponse, HttpResponseNotFound
from arches.app.utils.search_thumbnail_fetcher_factory import SearchThumbnailFetcherFactory


class ThumbnailView(View):
def head(self, request, resource_id):
fetcher = self.get_thumbnail_fetcher(resource_id)
if fetcher is None:
return HttpResponseNotFound()
thumbnail = fetcher.get_thumbnail(False)
if thumbnail is not None:
return HttpResponse()
else:
return HttpResponseNotFound()

def get(self, request, resource_id):
fetcher = self.get_thumbnail_fetcher(resource_id)
if fetcher is None:
return HttpResponseNotFound()

thumbnail = fetcher.get_thumbnail(True)
if thumbnail is not None:
return HttpResponse(thumbnail[0], content_type=thumbnail[1])
else:
return HttpResponseNotFound()

def get_thumbnail_fetcher(self, resource_id):
factory = SearchThumbnailFetcherFactory()
fetcher = factory.create_thumbnail_fetcher(resource_id)
return fetcher
2 changes: 2 additions & 0 deletions arches/install/arches-templates/project_name/settings.py-tpl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ DATABASES = {
}
}

SEARCH_THUMBNAILS = False

INSTALLED_APPS = (
"webpack_loader",
"django.contrib.admin",
Expand Down
2 changes: 2 additions & 0 deletions arches/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@

ELASTICSEARCH_HTTP_PORT = 9200 # this should be in increments of 200, eg: 9400, 9600, 9800
SEARCH_BACKEND = "arches.app.search.search.SearchEngine"
SEARCH_THUMBNAILS = False
# see http://elasticsearch-py.readthedocs.org/en/master/api.html#elasticsearch.Elasticsearch
ELASTICSEARCH_HOSTS = [{"scheme": "https", "host": "localhost", "port": ELASTICSEARCH_HTTP_PORT}]

Expand Down Expand Up @@ -744,6 +745,7 @@
def JSON_LD_FIX_DATA_FUNCTION(data, jsdata, model):
return jsdata


##########################################
### END RUN TIME CONFIGURABLE SETTINGS ###
##########################################
Expand Down
10 changes: 8 additions & 2 deletions arches/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from arches.app.views.admin import ReIndexResources, ClearUserPermissionCache
from arches.app.views.etl_manager import ETLManagerView
from arches.app.views.file import FileView, TempFileView
from arches.app.views.thumbnail import ThumbnailView
from arches.app.views.graph import (
GraphDesignerView,
GraphSettingsView,
Expand Down Expand Up @@ -240,7 +241,9 @@
re_path(r"^%s/(?P<path>.*)$" % settings.KIBANA_CONFIG_BASEPATH, api.KibanaProxy.as_view()),
re_path(r"^graphs/(?P<graph_id>%s)$" % (uuid_regex), api.Graphs.as_view(), name="graphs_api"),
re_path(r"^graphs", api.Graphs.as_view(action="get_graph_models"), name="get_graph_models_api"),
re_path(r"^resources/(?P<graphid>%s)/(?P<resourceid>%s|())$" % (uuid_regex, uuid_regex), api.Resources.as_view(), name="resources_graphid"),
re_path(
r"^resources/(?P<graphid>%s)/(?P<resourceid>%s|())$" % (uuid_regex, uuid_regex), api.Resources.as_view(), name="resources_graphid"
),
re_path(r"^resources/(?P<slug>[-\w]+)/(?P<resourceid>%s|())$" % uuid_regex, api.Resources.as_view(), name="resources_slug"),
re_path(r"^resources/(?P<resourceid>%s|())$" % uuid_regex, api.Resources.as_view(), name="resources"),
re_path(r"^api/tiles/(?P<tileid>%s|())$" % (uuid_regex), api.Tile.as_view(), name="api_tiles"),
Expand Down Expand Up @@ -278,6 +281,7 @@
re_path(r"^history/$", ResourceActivityStreamCollectionView.as_view(), name="as_stream_collection"),
re_path(r"^history/(?P<page>[0-9]+)$", ResourceActivityStreamPageView.as_view(), name="as_stream_page"),
re_path(r"^icons$", IconDataView.as_view(), name="icons"),
re_path(r"^thumbnail/(?P<resource_id>%s)$" % uuid_regex, ThumbnailView.as_view(), name="thumbnail"),
# Uncomment the admin/doc line below to enable admin documentation:
# re_path(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
Expand All @@ -303,7 +307,9 @@
re_path(r"^manifest/(?P<id>[0-9]+)$", api.Manifest.as_view(), name="manifest"),
re_path(r"^manifest/(?P<id>%s)$" % uuid_regex, api.Manifest.as_view(), name="manifest"),
re_path(r"^image-service-manager", ManifestManagerView.as_view(), name="manifest_manager"),
re_path(r"^two-factor-authentication-settings", TwoFactorAuthenticationSettingsView.as_view(), name="two-factor-authentication-settings"),
re_path(
r"^two-factor-authentication-settings", TwoFactorAuthenticationSettingsView.as_view(), name="two-factor-authentication-settings"
),
re_path(r"^two-factor-authentication-login", TwoFactorAuthenticationLoginView.as_view(), name="two-factor-authentication-login"),
re_path(r"^two-factor-authentication-reset", TwoFactorAuthenticationResetView.as_view(), name="two-factor-authentication-reset"),
re_path(r"^etl-manager$", ETLManagerView.as_view(), name="etl_manager"),
Expand Down

0 comments on commit bc1695b

Please sign in to comment.