diff --git a/app/search/mappings.py b/app/search/mappings.py index 0894d1d5..541f9964 100644 --- a/app/search/mappings.py +++ b/app/search/mappings.py @@ -51,6 +51,12 @@ "type": "text", "analyzer": TEXT_ANALYZER, "search_analyzer": TEXT_ANALYZER, + "fields": { + "normalized": { + "type": "keyword", + "normalizer": KEYWORD_NORMALIZER, # simple lowercase normalizer + }, + }, }, "identifier": { "type": "text", diff --git a/app/search/queries/filters/__init__.py b/app/search/queries/filters/__init__.py index fea8ea87..96bc180a 100644 --- a/app/search/queries/filters/__init__.py +++ b/app/search/queries/filters/__init__.py @@ -14,6 +14,7 @@ ) from app.search.queries.filters.publisher import PUBLISHER_FILTER from app.search.queries.filters.spatial_data import SPATIAL_DATA_FILTER +from app.search.queries.filters.theme import THEME_FILTER __all__ = [ "API_CONTEXT", @@ -32,4 +33,5 @@ PUBLISHER_FILTER, SPATIAL_DATA_FILTER, COLLECTION_FILTER, + THEME_FILTER, ) diff --git a/app/search/queries/filters/theme.py b/app/search/queries/filters/theme.py new file mode 100644 index 00000000..9b378c2a --- /dev/null +++ b/app/search/queries/filters/theme.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from app.search.queries.filters.base import ( + API_CONTEXT, + ApiQueryParam, + FilterDefinition, + get_list, +) + + +def _clause(criteria, values: list[str]) -> list[dict]: + return [{"term": {"theme.normalized": theme.lower()}} for theme in values] + + +THEME_FILTER = FilterDefinition( + name="theme", + query_params=("theme",), + parse_contexts=(API_CONTEXT,), + api_query_params=(ApiQueryParam("theme", repeated=True),), + parse=lambda args: get_list(args, "theme"), + to_query_pairs=lambda values: [("theme", value) for value in values], + clause_builder=_clause, +) diff --git a/tests/unit/test_routes.py b/tests/unit/test_routes.py index cedfb13f..6d8bfd09 100644 --- a/tests/unit/test_routes.py +++ b/tests/unit/test_routes.py @@ -270,6 +270,18 @@ def test_search_api_by_org_slug(interface_with_dataset, db_client): assert len(response.json["results"]) == 0 +def test_search_api_by_theme(interface_with_dataset, db_client): + with patch("app.routes.interface", interface_with_dataset): + response = db_client.get( + "/search", query_string={"theme": ["climate", "environment"]} + ) + assert len(response.json["results"]) == 1 + + # non-existent theme + response = db_client.get("/search", query_string={"theme": ["nonexistent"]}) + assert len(response.json["results"]) == 0 + + def test_index_page_filters_by_org_slug(db_client): mock_interface = Mock() mock_org = type(