Skip to content

Commit e356b2a

Browse files
hjribeiro-mojmurdo-mojLavMattMatMoore
authored
Fmd 1081 publication details page (#1112)
* Stash search changes * Add subtype mappings in cearch client * fmd-1081 - Extend ResultType class to hold multiple values - Added Datahub entity types to ResiultType class - Used the uupdated class as mapping in search.py Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fmd-1081 - Add new class to hold datahub and custom entity subtypes Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fmd-1081 - Add Publication Collection service - Added datahub client - Added details page and handler Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fmd-1081 - Add publication Dataset Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fixup! Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fixup! Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fixup! Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fixup! Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fixup! Further refactor Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fix unit tests Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fix unit tests Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> * fmd-1081 remake mappings between fmd types and datahub subtypes * Trying to fix the filters * Correct subtype in mapping * refactor: change the filter input type for search this allows us to use multiple and filter with the or filter * refactor: search_client to handle mulitple or filters this is required to handle returning subtypes * refactor: parse search results where subtype returned as None * refactor: Put access this data link at bottom of the page * stash * Filter fixes and test fixes * More test fixes * refactor: combine other filters with entity filters * Update home/service/search.py Co-authored-by: Mat <MatMoore@users.noreply.github.com> * Update lib/datahub-client/data_platform_catalogue/entities.py Co-authored-by: Mat <MatMoore@users.noreply.github.com> * refactor: split search module for easier testing * tests: add tests for map_filters function * refactor: remove redundant tag filters from the generated graphql query * override govuk-rtag max-width * Update lib/datahub-client/data_platform_catalogue/entities.py Co-authored-by: Mat <MatMoore@users.noreply.github.com> * Update lib/datahub-client/data_platform_catalogue/entities.py Co-authored-by: Mat <MatMoore@users.noreply.github.com> * Update lib/datahub-client/data_platform_catalogue/entities.py Co-authored-by: Mat <MatMoore@users.noreply.github.com> * fix: PublicationCollection entity external_url example * feat: add PlatformUrns to new entity contexts * refactor: move DatahubSubtype Enum into entities.py --------- Signed-off-by: Helder Ribeiro <helder.ribeiro@digital.justice.gov.uk> Co-authored-by: Murdo Moyse <murdo.moyse@digital.justice.gov.uk> Co-authored-by: Matt <38562764+LavMatt@users.noreply.github.com> Co-authored-by: LavMatt <matthew.laverty@justice.gov.uk> Co-authored-by: Murdo <109604278+murdo-moj@users.noreply.github.com> Co-authored-by: Mat <MatMoore@users.noreply.github.com>
1 parent 1e46860 commit e356b2a

28 files changed

+902
-525
lines changed

home/forms/search.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from copy import deepcopy
22
from urllib.parse import urlencode
33

4-
from data_platform_catalogue.search_types import DomainOption, ResultType
4+
from data_platform_catalogue.search_types import DomainOption
5+
from data_platform_catalogue.entities import EntityTypes
56
from django import forms
67
from django.utils.translation import gettext as _
78

@@ -36,8 +37,8 @@ def get_where_to_access_choices():
3637
def get_entity_types():
3738
return sorted(
3839
[
39-
(entity.name, entity.name.replace("_", " ").lower().title())
40-
for entity in ResultType
40+
(entity.name, entity.value)
41+
for entity in EntityTypes
4142
if entity.name != "GLOSSARY_TERM"
4243
]
4344
)

home/service/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ def _get_catalogue_client() -> DataHubCatalogueClient:
88
return DataHubCatalogueClient(
99
jwt_token=settings.CATALOGUE_TOKEN, api_url=settings.CATALOGUE_URL
1010
)
11+

home/service/details.py

+78-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import os
22
from urllib.parse import urlsplit
33

4-
from data_platform_catalogue.entities import EntityRef, RelationshipType
5-
from data_platform_catalogue.search_types import ResultType
4+
from data_platform_catalogue.entities import EntityRef, EntityTypes, RelationshipType
65
from django.core.exceptions import ObjectDoesNotExist, ValidationError
76
from django.core.validators import URLValidator
87
from django.utils.translation import gettext as _
@@ -66,6 +65,7 @@ def __init__(self, urn: str):
6665
RelationshipType.CHILD
6766
]
6867
self.context = self._get_context()
68+
self.template = "details_database.html"
6969

7070
def _get_context(self):
7171
context = {
@@ -114,7 +114,7 @@ def _get_context(self):
114114
"entity": self.table_metadata,
115115
"entity_type": "Table",
116116
"parent_entity": self.parent_entity,
117-
"parent_type": ResultType.DATABASE.name.lower(),
117+
"parent_type": EntityTypes.DATABASE.name.lower(),
118118
"h1_value": self.table_metadata.name,
119119
"has_lineage": self.has_lineage(),
120120
"lineage_url": f"{split_datahub_url.scheme}://{split_datahub_url.netloc}/dataset/{self.table_metadata.urn}/Lineage?is_lineage_mode=true&", # noqa: E501
@@ -151,6 +151,7 @@ def __init__(self, urn: str):
151151
self.chart_metadata = self.client.get_chart_details(urn)
152152
self.parent_entity = _parse_parent(self.chart_metadata.relationships or {})
153153
self.context = self._get_context()
154+
self.template = "details_chart.html"
154155

155156
def _get_context(self):
156157
return {
@@ -160,7 +161,7 @@ def _get_context(self):
160161
self.chart_metadata.platform.display_name
161162
),
162163
"parent_entity": self.parent_entity,
163-
"parent_type": ResultType.DASHBOARD.name.lower(),
164+
"parent_type": EntityTypes.DASHBOARD.name.lower(),
164165
"h1_value": self.chart_metadata.name,
165166
"is_access_requirements_a_url": is_access_requirements_a_url(
166167
self.chart_metadata.custom_properties.access_information.dc_access_requirements
@@ -175,6 +176,7 @@ def __init__(self, urn: str):
175176
self.dashboard_metadata = self.client.get_dashboard_details(urn)
176177
self.children = self.dashboard_metadata.relationships[RelationshipType.CHILD]
177178
self.context = self._get_context()
179+
self.template = "details_dashboard.html"
178180

179181
def _get_context(self):
180182

@@ -194,3 +196,75 @@ def _get_context(self):
194196
),
195197
"PlatformUrns": PlatformUrns,
196198
}
199+
200+
201+
class PublicationCollectionDetailsService(GenericService):
202+
def __init__(self, urn: str):
203+
self.urn = urn
204+
self.client = self._get_catalogue_client()
205+
206+
self.publication_collection_metadata = (
207+
self.client.get_publication_collection_details(self.urn)
208+
)
209+
210+
if not self.publication_collection_metadata:
211+
raise ObjectDoesNotExist(urn)
212+
213+
self.entities_in_container = self.publication_collection_metadata.relationships[
214+
RelationshipType.CHILD
215+
]
216+
self.context = self._get_context()
217+
self.template = "details_publication_collection.html"
218+
219+
def _get_context(self):
220+
context = {
221+
"entity": self.publication_collection_metadata,
222+
"entity_type": EntityTypes.PUBLICATION_COLLECTION.value,
223+
"platform_name": friendly_platform_name(
224+
self.publication_collection_metadata.platform.display_name
225+
),
226+
"publications": sorted(
227+
self.entities_in_container,
228+
key=lambda d: d.entity_ref.display_name,
229+
),
230+
"h1_value": self.publication_collection_metadata.name,
231+
"is_access_requirements_a_url": is_access_requirements_a_url(
232+
self.publication_collection_metadata.custom_properties.access_information.dc_access_requirements
233+
),
234+
"PlatformUrns": PlatformUrns,
235+
}
236+
237+
return context
238+
239+
240+
class PublicationDatasetDetailsService(GenericService):
241+
def __init__(self, urn: str):
242+
self.urn = urn
243+
self.client = self._get_catalogue_client()
244+
245+
self.publication_dataset_metadata = self.client.get_publication_dataset_details(
246+
self.urn
247+
)
248+
249+
if not self.publication_dataset_metadata:
250+
raise ObjectDoesNotExist(urn)
251+
252+
relationships = self.publication_dataset_metadata.relationships or {}
253+
self.parent_entity = _parse_parent(relationships)
254+
self.context = self._get_context()
255+
self.template = "details_publication_dataset.html"
256+
257+
def _get_context(self):
258+
259+
return {
260+
"entity": self.publication_dataset_metadata,
261+
"entity_type": EntityTypes.PUBLICATION_DATASET.value,
262+
"parent_entity": self.parent_entity,
263+
"parent_type": EntityTypes.DATABASE.name.lower(),
264+
"h1_value": self.publication_dataset_metadata.name,
265+
# noqa: E501
266+
"is_access_requirements_a_url": is_access_requirements_a_url(
267+
self.publication_dataset_metadata.custom_properties.access_information.dc_access_requirements
268+
),
269+
"PlatformUrns": PlatformUrns,
270+
}

home/service/search.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from copy import deepcopy
33
from typing import Any
44

5+
from data_platform_catalogue.entities import EntityTypes
56
from data_platform_catalogue.search_types import (
67
DomainOption,
78
MultiSelectFilter,
8-
ResultType,
99
SearchResponse,
1010
SortOption,
1111
)
@@ -45,15 +45,21 @@ def _build_custom_property_filter(
4545
) -> list[str]:
4646
return [f"{filter_param}{filter_value}" for filter_value in filter_value_list]
4747

48-
def _build_entity_types(self, entity_types: list[str]) -> tuple[ResultType, ...]:
48+
def _build_entity_types(self, entity_types: list[str]) -> tuple[EntityTypes, ...]:
4949
default_entities = tuple(
50-
entity for entity in ResultType if entity.name != "GLOSSARY_TERM"
50+
entity
51+
for entity in EntityTypes
52+
if entity.name != "GLOSSARY_TERM"
5153
)
5254
chosen_entities = (
53-
tuple(ResultType[entity] for entity in entity_types)
55+
tuple(
56+
EntityTypes[entity]
57+
for entity in entity_types
58+
)
5459
if entity_types
5560
else None
5661
)
62+
5763
return chosen_entities if chosen_entities else default_entities
5864

5965
def _format_query_value(self, query: str) -> str:
@@ -68,8 +74,8 @@ def _get_search_results(self, page: str, items_per_page: int) -> SearchResponse:
6874
form_data = self.form_data
6975
query = self._format_query_value(form_data.get("query", ""))
7076

71-
# we want to sort results ascending when a user is browsing data via non
72-
# keyword searches - otherwise we use the default releveant ordering
77+
# we want to sort results ascending when a user is browsing data via
78+
# non-keyword searches - otherwise we use the default relevant ordering
7379
sort = (
7480
form_data.get("sort", "relevance")
7581
if query not in ["*", ""]
@@ -82,6 +88,7 @@ def _get_search_results(self, page: str, items_per_page: int) -> SearchResponse:
8288
"dc_where_to_access_dataset=", form_data.get("where_to_access", [])
8389
)
8490
entity_types = self._build_entity_types(form_data.get("entity_types", []))
91+
8592
filter_value = []
8693
if domain:
8794
filter_value.append(MultiSelectFilter("domains", [domain]))
@@ -199,7 +206,7 @@ def _get_context(self, items_per_page: int) -> dict[str, Any]:
199206

200207
return context
201208

202-
def _highlight_results(self):
209+
def _highlight_results(self) -> SearchResponse:
203210
"Take a SearchResponse and add bold markdown where the query appears"
204211
query = self.form.cleaned_data.get("query", "") if self.form.is_valid() else ""
205212
highlighted_results = deepcopy(self.results)

home/service/search_facet_fetcher.py

-24
This file was deleted.

home/views.py

+29-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import csv
2+
import logging
23
from urllib.parse import urlparse
34

45
from data_platform_catalogue.client.exceptions import EntityDoesNotExist
@@ -9,12 +10,15 @@
910
from django.utils.translation import gettext as _
1011
from django.views.decorators.cache import cache_control
1112

13+
from data_platform_catalogue.entities import EntityTypes
1214
from home.forms.search import SearchForm
1315
from home.service.details import (
1416
ChartDetailsService,
1517
DashboardDetailsService,
1618
DatabaseDetailsService,
1719
DatasetDetailsService,
20+
PublicationCollectionDetailsService,
21+
PublicationDatasetDetailsService,
1822
)
1923
from home.service.details_csv import (
2024
DashboardDetailsCsvFormatter,
@@ -26,6 +30,15 @@
2630
from home.service.metadata_specification import MetadataSpecificationService
2731
from home.service.search import SearchService
2832

33+
type_details_map = {
34+
EntityTypes.TABLE.url_formatted: DatasetDetailsService,
35+
EntityTypes.DATABASE.url_formatted: DatabaseDetailsService,
36+
EntityTypes.CHART.url_formatted: ChartDetailsService,
37+
EntityTypes.DASHBOARD.url_formatted: DashboardDetailsService,
38+
EntityTypes.PUBLICATION_COLLECTION.url_formatted: PublicationCollectionDetailsService,
39+
EntityTypes.PUBLICATION_DATASET.url_formatted: PublicationDatasetDetailsService
40+
}
41+
2942

3043
@cache_control(max_age=300, private=True)
3144
def home_view(request):
@@ -39,41 +52,29 @@ def home_view(request):
3952

4053
@cache_control(max_age=300, private=True)
4154
def details_view(request, result_type, urn):
42-
try:
43-
if result_type == "table":
44-
service = DatasetDetailsService(urn)
45-
template = service.template
46-
elif result_type == "database":
47-
service = DatabaseDetailsService(urn)
48-
template = "details_database.html"
49-
elif result_type == "chart":
50-
service = ChartDetailsService(urn)
51-
template = "details_chart.html"
52-
elif result_type == "dashboard":
53-
service = DashboardDetailsService(urn)
54-
template = "details_dashboard.html"
55-
else:
56-
raise Http404("Invalid result type")
57-
58-
return render(request, template, service.context)
5955

56+
try:
57+
service = type_details_map[result_type](urn)
58+
except KeyError as missing_result_type:
59+
logging.exception(f"Missing service_details_map for {missing_result_type}")
60+
raise Http404("Invalid result type")
6061
except EntityDoesNotExist:
6162
raise Http404(f"{result_type} '{urn}' does not exist")
6263

64+
return render(request, service.template, service.context)
6365

6466
@cache_control(max_age=300, private=True)
6567
def details_view_csv(request, result_type, urn) -> HttpResponse:
66-
if result_type == "table":
67-
service = DatasetDetailsService(urn)
68-
csv_formatter = DatasetDetailsCsvFormatter(service)
69-
elif result_type == "database":
70-
service = DatabaseDetailsService(urn)
71-
csv_formatter = DatabaseDetailsCsvFormatter(service)
72-
elif result_type == "dashboard":
73-
service = DashboardDetailsService(urn)
74-
csv_formatter = DashboardDetailsCsvFormatter(service)
75-
else:
76-
raise Http404("CSV not available")
68+
match result_type:
69+
case EntityTypes.TABLE.url_formatted:
70+
csv_formatter = DatasetDetailsCsvFormatter(DatasetDetailsService(urn))
71+
case EntityTypes.DATABASE.url_formatted:
72+
csv_formatter = DatabaseDetailsCsvFormatter(DatabaseDetailsService(urn))
73+
case EntityTypes.DASHBOARD.url_formatted:
74+
csv_formatter = DashboardDetailsCsvFormatter(DashboardDetailsService(urn))
75+
case _:
76+
logging.error("Invalid result type for csv details view %s", result_type)
77+
raise Http404()
7778

7879
# In case there are any quotes in the filename, remove them in order to
7980
# not to break the header.

0 commit comments

Comments
 (0)