Skip to content

Commit 9bc282f

Browse files
committed
Load the list of domains from Datahub
Previously we hardcoded the list of domains shown in the search filter, and had different lists per environment. This was useful in alpha when we had some junk domains we wanted to filter out, but now we're at a point where every domain in Datahub should be one we want to use. This commit means we now fetch every domain that has something linked to it, and display that in alphabetical order.
1 parent d855eb5 commit 9bc282f

File tree

8 files changed

+93
-143
lines changed

8 files changed

+93
-143
lines changed

home/forms/domain_model.py

+17-100
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import os
1+
import logging
22
from typing import NamedTuple
33

4+
from home.service.search_facet_fetcher import SearchFacetFetcher
5+
6+
logger = logging.getLogger(__name__)
7+
48

59
class Domain(NamedTuple):
610
urn: str
@@ -12,109 +16,22 @@ class DomainModel:
1216
Store information about domains and subdomains
1317
"""
1418

15-
def __init__(self):
19+
def __init__(self, search_facet_fetcher: SearchFacetFetcher):
1620
self.labels = {}
17-
# This is temporary whilst we still have the dev enviroment connected to a
18-
# datahub with different domains.
19-
if os.environ.get("ENV") == "dev":
20-
self.top_level_domains = [
21-
Domain("urn:li:domain:HMCTS", "HMCTS"),
22-
Domain("urn:li:domain:HMPPS", "HMPPS"),
23-
Domain("urn:li:domain:HQ", "HQ"),
24-
Domain("urn:li:domain:LAA", "LAA"),
25-
Domain("urn:li:domain:OPG", "OPG"),
26-
]
2721

28-
for urn, label in self.top_level_domains:
29-
self.labels[urn] = label
22+
search_facets = search_facet_fetcher.fetch()
23+
self.top_level_domains = [
24+
Domain(option.value, option.label)
25+
for option in search_facets.options("domains")
26+
]
27+
self.top_level_domains.sort(key=lambda d: d.label)
28+
29+
logger.info(f"{self.top_level_domains=}")
3030

31-
self.subdomains = {
32-
"urn:li:domain:HMPPS": [
33-
Domain(
34-
"urn:li:domain:2feb789b-44d3-4412-b998-1f26819fabf9", "Prisons"
35-
),
36-
Domain(
37-
"urn:li:domain:abe153c1-416b-4abb-be7f-6accf2abb10a",
38-
"Probation",
39-
),
40-
],
41-
"urn:li:domain:HMCTS": [
42-
Domain(
43-
"urn:li:domain:4d77af6d-9eca-4c44-b189-5f1addffae55",
44-
"Civil courts",
45-
),
46-
Domain(
47-
"urn:li:domain:31754f66-33df-4a73-b039-532518bc765e",
48-
"Crown courts",
49-
),
50-
Domain(
51-
"urn:li:domain:81adfe94-1284-46a2-9179-945ad2a76c14",
52-
"Family courts",
53-
),
54-
Domain(
55-
"urn:li:domain:b261176c-d8eb-4111-8454-c0a1fa95005f",
56-
"Magistrates courts",
57-
),
58-
],
59-
"urn:li:domain:OPG": [
60-
Domain(
61-
"urn:li:domain:bc091f6c-7674-4c82-a315-f5489398f099",
62-
"Lasting power of attourney",
63-
),
64-
Domain(
65-
"urn:li:domain:efb9ade3-3c5d-4c5c-b451-df9f2d8136f5",
66-
"Supervision orders",
67-
),
68-
],
69-
"urn:li:domain:HQ": [
70-
Domain(
71-
"urn:li:domain:9fb7ff13-6c7e-47ef-bef1-b13b23fd8c7a", "Estates"
72-
),
73-
Domain(
74-
"urn:li:domain:e4476e66-37a1-40fd-83b9-c908f805d8f4", "Finance"
75-
),
76-
Domain(
77-
"urn:li:domain:0985731b-8e1c-4b4a-bfc0-38e58d8ba8a1", "People"
78-
),
79-
Domain(
80-
"urn:li:domain:a320c915-0b43-4277-9769-66615aab4adc",
81-
"Performance",
82-
),
83-
],
84-
"urn:li:domain:LAA": [
85-
Domain(
86-
"urn:li:domain:24344488-d770-437a-ba6f-e6129203b927",
87-
"Civil legal advice",
88-
),
89-
Domain("urn:li:domain:Legal%20Aid", "Legal aid"),
90-
Domain(
91-
"urn:li:domain:5c423c06-d328-431f-8634-7a7e86928819",
92-
"Public defender",
93-
),
94-
],
95-
}
96-
for domain, subdomains in self.subdomains.items():
97-
domain_label = self.labels[domain]
98-
for urn, subdoman_label in subdomains:
99-
self.labels[urn] = f"{domain_label} - {subdoman_label}"
100-
else:
101-
self.top_level_domains = [
102-
Domain("urn:li:domain:courts", "Courts"),
103-
Domain("urn:li:domain:electronic_monitoring", "Electronic monitoring"),
104-
Domain("urn:li:domain:general", "General"),
105-
Domain("urn:li:domain:interventions", "Interventions"),
106-
Domain("urn:li:domain:opg", "OPG"),
107-
Domain("urn:li:domain:prison", "Prison"),
108-
Domain("urn:li:domain:probation", "Probation"),
109-
Domain("urn:li:domain:risk", "Risk"),
110-
Domain(
111-
"urn:li:domain:victims_case_management", "Victims case management"
112-
),
113-
]
114-
self.subdomains = {}
31+
self.subdomains = {}
11532

116-
for urn, label in self.top_level_domains:
117-
self.labels[urn] = label
33+
for urn, label in self.top_level_domains:
34+
self.labels[urn] = label
11835

11936
def all_subdomains(self) -> list[Domain]: # -> list[Any]
12037
"""

home/forms/search.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from data_platform_catalogue.search_types import ResultType
55
from django import forms
66

7+
from ..service.search_facet_fetcher import SearchFacetFetcher
78
from .domain_model import Domain, DomainModel
89

910

@@ -12,13 +13,13 @@ def get_domain_choices() -> list[Domain]:
1213
choices = [
1314
Domain("", "All domains"),
1415
]
15-
choices.extend(DomainModel().top_level_domains)
16+
choices.extend(DomainModel(SearchFacetFetcher()).top_level_domains)
1617
return choices
1718

1819

1920
def get_subdomain_choices() -> list[Domain]:
2021
choices = [Domain("", "All subdomains")]
21-
choices.extend(DomainModel().all_subdomains())
22+
choices.extend(DomainModel(SearchFacetFetcher()).all_subdomains())
2223
return choices
2324

2425

@@ -47,8 +48,7 @@ def get_entity_types():
4748
class SelectWithOptionAttribute(forms.Select):
4849
def __init__(self, *args, **kwargs):
4950
super().__init__(*args, **kwargs)
50-
51-
self.domain_model = DomainModel()
51+
self.domain_model = None
5252

5353
def create_option(
5454
self, name, urn, label, selected, index, subindex=None, attrs=None
@@ -57,6 +57,8 @@ def create_option(
5757
name, urn, label, selected, index, subindex, attrs
5858
)
5959

60+
self.domain_model = self.domain_model or DomainModel(SearchFacetFetcher())
61+
6062
if urn:
6163
option["attrs"]["data-parent"] = self.domain_model.get_parent_urn(urn)
6264

home/service/search.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
from home.forms.search import SearchForm
1717

1818
from .base import GenericService
19+
from .search_facet_fetcher import SearchFacetFetcher
1920

2021

21-
def domains_with_their_subdomains(domain: str, subdomain: str) -> list[str]:
22+
def domains_with_their_subdomains(
23+
domain: str, subdomain: str, domain_model: DomainModel
24+
) -> list[str]:
2225
"""
2326
Users can search by domain, and optionally by subdomain.
2427
When subdomain is passed, then we can filter on that directly.
@@ -30,14 +33,15 @@ def domains_with_their_subdomains(domain: str, subdomain: str) -> list[str]:
3033
if subdomain:
3134
return [subdomain]
3235

33-
subdomains = DomainModel().subdomains.get(domain, [])
36+
subdomains = domain_model.subdomains.get(domain, [])
3437
subdomains = [subdomain[0] for subdomain in subdomains]
3538
return [domain, *subdomains] if not domain == "" else []
3639

3740

3841
class SearchService(GenericService):
3942
def __init__(self, form: SearchForm, page: str, items_per_page: int = 20):
40-
self.domain_model = DomainModel()
43+
search_facet_fetcher = SearchFacetFetcher()
44+
self.domain_model = DomainModel(search_facet_fetcher)
4145
self.stemmer = PorterStemmer()
4246
self.form = form
4347
if self.form.is_bound:
@@ -76,7 +80,9 @@ def _get_search_results(self, page: str, items_per_page: int) -> SearchResponse:
7680
sort = form_data.get("sort", "relevance")
7781
domain = form_data.get("domain", "")
7882
subdomain = form_data.get("subdomain", "")
79-
domains_and_subdomains = domains_with_their_subdomains(domain, subdomain)
83+
domains_and_subdomains = domains_with_their_subdomains(
84+
domain, subdomain, self.domain_model
85+
)
8086
where_to_access = self._build_custom_property_filter(
8187
"whereToAccessDataset=", form_data.get("where_to_access", [])
8288
)

home/service/search_facet_fetcher.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from data_platform_catalogue.search_types import SearchFacets
2+
3+
from .base import GenericService
4+
5+
6+
class SearchFacetFetcher(GenericService):
7+
def __init__(self):
8+
self.client = self._get_catalogue_client()
9+
10+
def fetch(self) -> SearchFacets:
11+
"""
12+
Fetch a static list of options that is independent of the search query
13+
and any applied filters.
14+
TODO: this can be cached in memory to speed up future requests
15+
"""
16+
return self.client.search_facets()

tests/conftest.py

+30-22
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
from typing import Any
44
from unittest.mock import MagicMock, patch
55

6-
from django.conf import settings
7-
86
import pytest
97
from data_platform_catalogue.client.datahub_client import DataHubCatalogueClient
108
from data_platform_catalogue.entities import (
@@ -29,13 +27,15 @@
2927
SearchResponse,
3028
SearchResult,
3129
)
32-
30+
from django.conf import settings
3331
from django.test import Client
3432
from faker import Faker
3533

34+
from home.forms.domain_model import DomainModel
3635
from home.forms.search import SearchForm
3736
from home.service.details import DatabaseDetailsService
3837
from home.service.search import SearchService
38+
from home.service.search_facet_fetcher import SearchFacetFetcher
3939

4040
fake = Faker()
4141

@@ -146,22 +146,6 @@ def generate_page(page_size=20, result_type: ResultType | None = None):
146146
return results
147147

148148

149-
def generate_options(num_options=5):
150-
"""
151-
Generate a list of options for the search facets
152-
"""
153-
results = []
154-
for _ in range(num_options):
155-
results.append(
156-
FacetOption(
157-
value=fake.name(),
158-
label=fake.name(),
159-
count=fake.random_int(min=0, max=100),
160-
)
161-
)
162-
return results
163-
164-
165149
@pytest.fixture(autouse=True)
166150
def client():
167151
client = Client()
@@ -181,7 +165,26 @@ def mock_catalogue(request):
181165
mock_search_response(
182166
mock_catalogue, page_results=generate_page(), total_results=100
183167
)
184-
mock_search_facets_response(mock_catalogue, domains=generate_options())
168+
mock_search_facets_response(
169+
mock_catalogue,
170+
domains=[
171+
FacetOption(
172+
value="urn:li:domain:prisons",
173+
label="Prisons",
174+
count=fake.random_int(min=0, max=100),
175+
),
176+
FacetOption(
177+
value="urn:li:domain:courts",
178+
label="Courts",
179+
count=fake.random_int(min=0, max=100),
180+
),
181+
FacetOption(
182+
value="urn:li:domain:finance",
183+
label="Finance",
184+
count=fake.random_int(min=0, max=100),
185+
),
186+
],
187+
)
185188
mock_get_glossary_terms_response(mock_catalogue)
186189
mock_list_database_tables_response(
187190
mock_catalogue,
@@ -296,11 +299,16 @@ def mock_get_glossary_terms_response(mock_catalogue):
296299

297300

298301
@pytest.fixture
299-
def valid_form():
302+
def valid_domain():
303+
return DomainModel(SearchFacetFetcher()).top_level_domains[0]
304+
305+
306+
@pytest.fixture
307+
def valid_form(valid_domain):
300308
valid_form = SearchForm(
301309
data={
302310
"query": "test",
303-
"domain": "urn:li:domain:prison",
311+
"domain": valid_domain.urn,
304312
"entity_types": ["TABLE"],
305313
"where_to_access": ["analytical_platform"],
306314
"sort": "ascending",

tests/home/service/test_search.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import re
33
from types import GeneratorType
4+
from urllib.parse import quote
45

56
import pytest
67

@@ -29,9 +30,9 @@ def test_get_context_paginator(self, search_context):
2930
def test_get_context_h1_value(self, search_context):
3031
assert search_context["h1_value"] == "Search"
3132

32-
def test_get_context_label_clear_href(self, search_context):
33+
def test_get_context_label_clear_href(self, search_context, valid_domain):
3334
assert search_context["label_clear_href"]["domain"] == {
34-
"Prison": (
35+
valid_domain.label: (
3536
"?query=test&"
3637
"where_to_access=analytical_platform&"
3738
"entity_types=TABLE&"
@@ -44,7 +45,7 @@ def test_get_context_label_clear_href(self, search_context):
4445
assert search_context["label_clear_href"]["Where To Access"] == {
4546
"analytical_platform": (
4647
"?query=test&"
47-
"domain=urn%3Ali%3Adomain%3Aprison&"
48+
f"domain={quote(valid_domain.urn)}&"
4849
"subdomain=&"
4950
"entity_types=TABLE&"
5051
"sort=ascending&"
@@ -56,7 +57,7 @@ def test_get_context_label_clear_href(self, search_context):
5657
assert search_context["label_clear_href"]["Entity Types"] == {
5758
"Table": (
5859
"?query=test&"
59-
"domain=urn%3Ali%3Adomain%3Aprison&"
60+
f"domain={quote(valid_domain.urn)}&"
6061
"subdomain=&"
6162
"where_to_access=analytical_platform&"
6263
"sort=ascending&"

0 commit comments

Comments
 (0)