Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find moj data 69/glossary #96

Merged
merged 15 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions home/service/glossary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from data_platform_catalogue.search_types import MultiSelectFilter, ResultType
from django.core.exceptions import ObjectDoesNotExist
from copy import deepcopy
from itertools import groupby

from .base import GenericService


class GlossaryService(GenericService):
def __init__(self):
# Can we put the client instantiation in the base class?
murdo-moj marked this conversation as resolved.
Show resolved Hide resolved
self.client = self._get_catalogue_client()
self.context = self._get_context()

def _get_context(self):
"""Returns a glossary context which is grouped by parent term"""
glossary_search_results = self.client.get_glossary_terms()
total_results = glossary_search_results.total_results

def sorter(result):
first_parent = result.metadata.get("parentNodes", [])
if first_parent:
return first_parent[0]["properties"]["name"]
if not first_parent:
return "Unsorted"

page_results_copy = sorted(glossary_search_results.page_results, key=sorter)
sorted_total_results = [
{"name": key, "members": list(group)}
for key, group
in groupby(page_results_copy, key=sorter)
]
# Adding the description in the list comprehension doesn't seem to work
for parent_term in sorted_total_results:
if parent_term["members"][0].metadata.get("parentNodes"):
parent_term["description"] = parent_term["members"][0].metadata["parentNodes"][0]["properties"]["description"]
else:
parent_term["description"] = ""

context = {"results": sorted_total_results}

return context

1 change: 1 addition & 0 deletions home/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
urlpatterns = [
path("", views.home_view, name="home"),
path("search", views.search_view, name="search"),
path("glossary", views.glossary_view, name="glossary"),
path(
"details/<str:result_type>/<str:id>",
views.details_view,
Expand Down
5 changes: 5 additions & 0 deletions home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from home.forms.search import SearchForm
from home.service.details import DataProductDetailsService, DatasetDetailsService
from home.service.search import SearchService
from home.service.glossary import GlossaryService


def home_view(request):
Expand Down Expand Up @@ -55,3 +56,7 @@ def search_view(request, page: str = "1"):

search_service = SearchService(form=form, page=page)
return render(request, "search.html", search_service.context)

def glossary_view(request):
glossary_service = GlossaryService()
return render(request, "glossary.html", glossary_service.context)
1,096 changes: 476 additions & 620 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ django = "^5.0.1"
pyyaml = "^6.0.1"
gunicorn = "^21.2.0"
whitenoise = "^6.6.0"
ministryofjustice-data-platform-catalogue = "^0.15.0"
ministryofjustice-data-platform-catalogue = "^0.16.1"
markdown = "^3.5.2"
python-dotenv = "^1.0.1"

Expand Down
6 changes: 6 additions & 0 deletions templates/base/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ul id="navigation" class="govuk-header__navigation-list">
{% url 'home:home' as home_url %}
{% url 'home:search' as search_url %}
{% url 'home:glossary' as glossary_url %}
<li class="govuk-header__navigation-item {% if request.path == home_url %}govuk-header__navigation-item--active{%endif%}">
<a class="govuk-header__link" href="{% url 'home:home' %}">
Home
Expand All @@ -36,6 +37,11 @@
Search
</a>
</li>
<li class="govuk-header__navigation-item {% if request.path == glossary_url %}govuk-header__navigation-item--active{%endif%}">
<a class="govuk-header__link" href="{% url 'home:glossary' %}?new=True">
Glossary
</a>
</li>
</ul>
</nav>
</div>
Expand Down
46 changes: 46 additions & 0 deletions templates/glossary.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% extends "base/base.html" %}
{% load markdown %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-quarter">
<h1 class="govuk-heading-l">Glossary</h1>
</div>
</div>
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-quarter">
<ul class="govuk-list">
{% for parent_term in results %}
<li>
<div><a class="govuk-link govuk-link--no-visited-state govuk-link--no-underline" href="#{{ parent_term }}">
<strong>{{ parent_term.name }}</strong>
</a></div>
{% for member in parent_term.members %}
<div><a class="govuk-link govuk-link--no-visited-state govuk-link--no-underline" href="#{{ member.name }}">
{{ member.name }}
</a></div>
{%endfor%}
<br>
</li>
{%endfor%}
</ul>
</div>
<div class="govuk-grid-column-three-quarters">
{% for parent_term in results %}
<div>
<h1 class="govuk-heading-l" id="{{ parent_term }}">{{ parent_term.name }}</h1>
<p class="govuk-body-l">{{ parent_term.description }}</p>
<hr class="govuk-section-break govuk-section-break--m govuk-section-break--visible">
</div>
{% for member in parent_term.members %}
<div>
<h2 class="govuk-heading-m" id="{{ member.name }}">{{ member.name }}</h2>
<p class="govuk-body">{{ member.description|markdown }}</p>
</div>
{%endfor%}
<br>
{%endfor%}
</div>
</div>

{% endblock content %}
4 changes: 4 additions & 0 deletions templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@
<a onclick="history.back()" class="govuk-back-link">Back</a>
<h1 class="govuk-heading-xl">Customised page template</h1>

<p class="govuk-body">
<a href="{% url 'home:glossary' %}" class="govuk-link">Glossary</a>.
</p>

{% endblock content %}
47 changes: 47 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from home.forms.search import SearchForm
from home.service.details import DataProductDetailsService
from home.service.search import SearchService
from home.service.glossary import GlossaryService

from datahub.metadata.schema_classes import (
DataProductPropertiesClass,
Expand Down Expand Up @@ -92,6 +93,7 @@ def mock_catalogue():
mock_catalogue, page_results=generate_page(), total_results=100
)
mock_search_facets_response(mock_catalogue, domains=generate_options())
mock_get_glossary_terms_response(mock_catalogue)
mock_list_data_product_response(
mock_catalogue,
page_results=generate_page(page_size=1, result_type=ResultType.TABLE),
Expand Down Expand Up @@ -132,6 +134,51 @@ def mock_get_dataproduct_aspect(mock_catalogue):
)
mock_catalogue.graph.get_aspect.return_value = response

def mock_get_glossary_terms_response(mock_catalogue):
mock_catalogue.get_glossary_terms.return_value = SearchResponse(
total_results=3,
page_results=[
SearchResult(
id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8",
name="IAO",
description="Information asset owner.\n",
metadata={
"parentNodes": [
{
"properties": {
"name": "Data protection terms",
"description": "Data protection terms",
}
}
]
},
result_type="GLOSSARY_TERM",
),
SearchResult(
id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8",
name="Other term",
description="Term description to test groupings work",
metadata={
"parentNodes": [
{
"properties": {
"name": "Data protection terms",
"description": "Data protection terms",
}
}
]
},
result_type="GLOSSARY_TERM",
),
SearchResult(
id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6",
name="Security classification",
description="Only data that is 'official'",
metadata={"parentNodes": []},
result_type="GLOSSARY_TERM",
),
],
)

@pytest.fixture
def valid_form():
Expand Down
3 changes: 3 additions & 0 deletions tests/selenium/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class HomePage(Page):
def search_nav_link(self) -> WebElement:
return self.selenium.find_element(By.LINK_TEXT, "Search")

def glossary_nav_link(self) -> WebElement:
return self.selenium.find_element(By.LINK_TEXT, "Glossary")


class SearchResultWrapper:
def __init__(self, element: WebElement):
Expand Down
13 changes: 11 additions & 2 deletions tests/selenium/test_search_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ def setup(
self.search_page = search_page
self.details_data_product_page = details_data_product_page
self.chromedriver_path = chromedriver_path
self.details_data_product_page = details_data_product_page
self.chromedriver_path = chromedriver_path

def verify_glossary_link_from_homepage_works(self):
self.start_on_the_home_page()
self.click_on_the_glossary_link()
self.verify_i_am_on_the_glossary_page()

def test_browse_to_first_item_data_product(self, mock_catalogue):
"""
Expand Down Expand Up @@ -206,13 +209,19 @@ def start_on_the_search_page(self):
def click_on_the_search_link(self):
self.home_page.search_nav_link().click()

def click_on_the_glossary_link(self):
self.home_page.glossary_nav_link().click()

def click_on_the_search_button(self):
self.search_page.search_button().click()

def verify_i_am_on_the_search_page(self):
assert "Search" in self.selenium.title
assert "Find MOJ Data" in self.search_page.primary_heading().text

def verify_i_am_on_the_glossary_page(self):
assert "Glossary" in self.selenium.title

def verify_i_have_results(self):
result_count = self.search_page.result_count().text
assert re.match(r"[1-9]\d* Results", result_count)
Expand Down
83 changes: 79 additions & 4 deletions tests/test_services.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from types import GeneratorType

import pytest
from data_platform_catalogue.search_types import ResultType
from home.service.search import SearchForm, SearchService

from home.service.search import domains_with_their_subdomains
from data_platform_catalogue.search_types import (
ResultType,
SearchResponse,
SearchResult,
)
from unittest.mock import patch
from home.service.search import SearchForm, SearchService, domains_with_their_subdomains
from home.service.glossary import GlossaryService


class TestSearchService:
Expand Down Expand Up @@ -125,6 +129,77 @@ def test_get_context_data_product_tables(
assert detail_dataproduct_context["tables"][0] == mock_table


class TestGlossaryService:
def test_get_context(self):
glossary_context = GlossaryService()
expected_context = {
"results": [
{
"name": "Data protection terms",
"members": [
SearchResult(
id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8",
result_type="GLOSSARY_TERM",
name="IAO",
description="Information asset owner.\n",
matches={},
metadata={
"parentNodes": [
{
"properties": {
"name": "Data protection terms",
"description": "Data protection terms",
}
}
]
},
tags=[],
last_updated=None,
),
SearchResult(
id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8",
result_type="GLOSSARY_TERM",
name="Other term",
description="Term description to test groupings work",
matches={},
metadata={
"parentNodes": [
{
"properties": {
"name": "Data protection terms",
"description": "Data protection terms",
}
}
]
},
tags=[],
last_updated=None,
),
],
"description": "Data protection terms",
},
{
"name": "Unsorted",
"members": [
SearchResult(
id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6",
result_type="GLOSSARY_TERM",
name="Security classification",
description="Only data that is 'official'",
matches={},
metadata={"parentNodes": []},
tags=[],
last_updated=None,
)
],
"description": "",
},
]
}

assert expected_context == glossary_context.context


@pytest.mark.parametrize(
"domain, expected_subdomains",
[
Expand Down
5 changes: 5 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,8 @@ def test_details_data_product_not_found(self, client, mock_catalogue):
)
)
assert response.status_code == 404

class TestGlossaryView:
def test_details(self, client):
response = client.get(reverse("home:glossary"))
assert response.status_code == 200
Loading