@eeacms/volto-cca-policy 1.0.35 → 1.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [1.0.36](https://github.com/eea/volto-cca-policy/compare/1.0.35...1.0.36) - 8 October 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat(navigator): update geographic coverage functionality, add runtime mappings and facet value retrieval [kreafox - [`4787c3b`](https://github.com/eea/volto-cca-policy/commit/4787c3b890b69d46ee0806084983ac3c0f36fe43)]
12
+ - feat: add tooltips for usability and functionality criteria in CompareToolsView - refs #304612 [kreafox - [`d23175c`](https://github.com/eea/volto-cca-policy/commit/d23175c7e3e1df85e2c75deba6612be3dd94b88d)]
13
+
14
+ #### :nail_care: Enhancements
15
+
16
+ - refactor(navigator): clean up imports and test files [kreafox - [`4a79b41`](https://github.com/eea/volto-cca-policy/commit/4a79b414b713d387af0fcf7baefd699da747cdac)]
17
+
7
18
  ### [1.0.35](https://github.com/eea/volto-cca-policy/compare/1.0.34...1.0.35) - 6 October 2026
8
19
 
9
20
  #### :bug: Bug Fixes
@@ -615,8 +626,42 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
615
626
  #### :hammer_and_wrench: Others
616
627
 
617
628
  - test: pin Chromium version 149 to work with Cypress [valentinab25 - [`659ec82`](https://github.com/eea/volto-cca-policy/commit/659ec823dd71fe592a3a7accda22effbe1fb07bc)]
618
- ## [1.0.0](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.31...1.0.0) - 12 June 2026
629
+ ## [1.0.0](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.33...1.0.0) - 12 June 2026
630
+
631
+ ### [1.0.0-alpha.33](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.32...1.0.0-alpha.33) - 7 October 2026
632
+
633
+ ### [1.0.0-alpha.32](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.31...1.0.0-alpha.32) - 7 October 2026
634
+
635
+ #### :bug: Bug Fixes
636
+
637
+ - fix(test): add waitFor to image load assertions [kreafox - [`84eed2e`](https://github.com/eea/volto-cca-policy/commit/84eed2e15a911d9a8a4a1d20e7d29b3de261bb7d)]
638
+
639
+ #### :nail_care: Enhancements
619
640
 
641
+ - change(navigator): conditionally render result count based on selections [kreafox - [`0ed5449`](https://github.com/eea/volto-cca-policy/commit/0ed54490c16c9a5b4652fd1de47adbad8d2c8256)]
642
+ - change(NavigatorGuide): implement sorting for guide options - refs #304612 [kreafox - [`4ffcbff`](https://github.com/eea/volto-cca-policy/commit/4ffcbff4f98a2c95b1c88dfc0f662c2906678c13)]
643
+ - change(navigator): implement default sorting in guide me tools listing - refs #304612 [kreafox - [`a9f7fb3`](https://github.com/eea/volto-cca-policy/commit/a9f7fb31b276ed2c28d0fa24b800bebfd0ff5425)]
644
+ - change(CatalogueChatView): remove CompareToolsPanel and update tests - refs #309337 [kreafox - [`a5bb420`](https://github.com/eea/volto-cca-policy/commit/a5bb420e8a3229b1a04fd38f36ee87266bb0e59d)]
645
+ - change(locales): update translations [kreafox - [`4541062`](https://github.com/eea/volto-cca-policy/commit/45410626484ff0cc536447ff75895b662e3e9554)]
646
+ - change(navigator): update adaptation stage questions [kreafox - [`a1e3705`](https://github.com/eea/volto-cca-policy/commit/a1e3705f1004777ce72a1395f89149c9d8f8e539)]
647
+ - change(locales): add "Governance level" translations [kreafox - [`23ae204`](https://github.com/eea/volto-cca-policy/commit/23ae2046557203a55b2dd40b761ed99e84b78d80)]
648
+ - change(navigator): update implementation level messages to governance level [kreafox - [`f28a93e`](https://github.com/eea/volto-cca-policy/commit/f28a93e408ba24a81ac27229947de1b959a5c42b)]
649
+
650
+ #### :house: Internal changes
651
+
652
+ - style: Automated code fix [eea-jenkins - [`3a0597d`](https://github.com/eea/volto-cca-policy/commit/3a0597dd9358ba2c7f365bef9d3e4ffae36ee68b)]
653
+ - style: Automated code fix [eea-jenkins - [`d91a798`](https://github.com/eea/volto-cca-policy/commit/d91a798f30b29e4bc058cc44ebcc02f07ac216ab)]
654
+
655
+ #### :hammer_and_wrench: Others
656
+
657
+ - update SonarQube messages [iugin - [`cf21375`](https://github.com/eea/volto-cca-policy/commit/cf21375202f946f7adb2959db45cb0e991bfae68)]
658
+ - add mapstyle test [iugin - [`e80b843`](https://github.com/eea/volto-cca-policy/commit/e80b8438e6ff47cb73e43cc8927511e7b8979a8f)]
659
+ - remove test [iugin - [`dcd7fb2`](https://github.com/eea/volto-cca-policy/commit/dcd7fb2873101fd7c0e6612e0eb11012b18412a3)]
660
+ - Refs #296263 - add more tests [iugin - [`898d8e7`](https://github.com/eea/volto-cca-policy/commit/898d8e7878e49a7e777c749e1f88dc6c94ce416b)]
661
+ - Refs #296263 - update tests img alt [iugin - [`1f720a8`](https://github.com/eea/volto-cca-policy/commit/1f720a83b389cb1cb5dbed33c96418fc8def73d9)]
662
+ - Refs #296263 - add tests [iugin - [`c9012e0`](https://github.com/eea/volto-cca-policy/commit/c9012e0e7ca850acda608459e9b78c70f37e7ad4)]
663
+ - Refs #296263 - duplication country profile mapstyle [iugin - [`a6771e9`](https://github.com/eea/volto-cca-policy/commit/a6771e921bcdc6b9ce2f0f9f40413a2fefe78e84)]
664
+ - Refs #296263 - duplicate content [iugin - [`400b661`](https://github.com/eea/volto-cca-policy/commit/400b6618cc3fb50df44f9007253aeaba5e5d36b0)]
620
665
  ### [1.0.0-alpha.31](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.30...1.0.0-alpha.31) - 2 October 2026
621
666
 
622
667
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -4,11 +4,11 @@ import { defineMessages, useIntl } from 'react-intl';
4
4
  import { Term } from '@eeacms/search/components';
5
5
  import { useSearchContext } from '@eeacms/search/lib/hocs';
6
6
  import { markSelectedFacetValuesFromFilters } from '@eeacms/search/lib/search/helpers';
7
-
8
- export const CHARACTERISATION_FIELD = 'cca_geographic_characterisation.keyword';
9
- export const TRANSNATIONAL_REGION_FIELD =
10
- 'cca_geographic_transnational_region.keyword';
11
- export const COUNTRIES_FIELD = 'cca_geographic_countries.keyword';
7
+ import {
8
+ CHARACTERISATION_FIELD,
9
+ COUNTRIES_FIELD,
10
+ TRANSNATIONAL_REGION_FIELD,
11
+ } from '../../../search/navigator_catalogue/geographicCoverage';
12
12
 
13
13
  const messages = defineMessages({
14
14
  noOptionsAvailable: {
@@ -196,29 +196,53 @@ const NavigatorGeographicCoverageFacet = ({
196
196
  TRANSNATIONAL_REGION_FIELD,
197
197
  );
198
198
  const countryOptions = getFacetOptions(facets, filters, COUNTRIES_FIELD);
199
+ const selectedDirectValues =
200
+ filters.find(({ field }) => field === CHARACTERISATION_FIELD)?.values || [];
201
+
202
+ const clearFilter = (field) => removeFilter(field, null, 'any');
203
+
204
+ const updateDirectFilter = (option) => {
205
+ if (option.selected) {
206
+ onRemove(option.value);
207
+ return;
208
+ }
209
+
210
+ clearFilter(COUNTRIES_FIELD);
211
+ clearFilter(TRANSNATIONAL_REGION_FIELD);
212
+ onSelect(option.value);
213
+ };
199
214
 
200
- const updateChildFilter = (field, option) =>
201
- option.selected
202
- ? removeFilter(field, option.value, 'any')
203
- : addFilter(field, option.value, 'any');
215
+ const updateChildFilter = (field, option) => {
216
+ if (option.selected) {
217
+ removeFilter(field, option.value, 'any');
218
+ return;
219
+ }
220
+
221
+ clearFilter(CHARACTERISATION_FIELD);
222
+ addFilter(field, option.value, 'any');
223
+ };
204
224
 
205
225
  return (
206
226
  <fieldset
207
227
  className={`sui-facet searchlib-multiterm-facet navigator-geographic-facet ${className}`}
208
228
  >
209
229
  <div className="sui-multi-checkbox-facet navigator-geographic-facet-direct-options">
210
- {options.map((option) => (
211
- <FacetOption
212
- key={valueAsText(option.value)}
213
- field={CHARACTERISATION_FIELD}
214
- option={option}
215
- onChange={(selectedOption) =>
216
- selectedOption.selected
217
- ? onRemove(selectedOption.value)
218
- : onSelect(selectedOption.value)
219
- }
220
- />
221
- ))}
230
+ {options.map((option) => {
231
+ const directOption = {
232
+ ...option,
233
+ selected:
234
+ option.selected || selectedDirectValues.includes(option.value),
235
+ };
236
+
237
+ return (
238
+ <FacetOption
239
+ key={valueAsText(option.value)}
240
+ field={CHARACTERISATION_FIELD}
241
+ option={directOption}
242
+ onChange={updateDirectFilter}
243
+ />
244
+ );
245
+ })}
222
246
  </div>
223
247
  <ExpandableFacetGroup
224
248
  field={COUNTRIES_FIELD}
@@ -3,10 +3,12 @@ import '@testing-library/jest-dom';
3
3
  import { fireEvent, render, screen } from '@testing-library/react';
4
4
  import { IntlProvider } from 'react-intl';
5
5
  import { useSearchContext } from '@eeacms/search/lib/hocs';
6
- import NavigatorGeographicCoverageFacet, {
6
+ import NavigatorGeographicCoverageFacet from './NavigatorGeographicCoverageFacet';
7
+ import {
8
+ CHARACTERISATION_FIELD,
7
9
  COUNTRIES_FIELD,
8
10
  TRANSNATIONAL_REGION_FIELD,
9
- } from './NavigatorGeographicCoverageFacet';
11
+ } from '../../../search/navigator_catalogue/geographicCoverage';
10
12
 
11
13
  jest.mock('@eeacms/search/components', () => ({
12
14
  Term: ({ term }) => <>{term}</>,
@@ -16,6 +18,7 @@ jest.mock('@eeacms/search/lib/hocs', () => ({
16
18
  }));
17
19
 
18
20
  const renderFacet = ({
21
+ detailedOptions = true,
19
22
  filters = [],
20
23
  onRemove = jest.fn(),
21
24
  onSelect = jest.fn(),
@@ -30,18 +33,22 @@ const renderFacet = ({
30
33
  facets: {
31
34
  [TRANSNATIONAL_REGION_FIELD]: [
32
35
  {
33
- data: [
34
- { value: 'Alpine Space', count: 3 },
35
- { value: 'Outermost regions', count: 2 },
36
- ],
36
+ data: detailedOptions
37
+ ? [
38
+ { value: 'Alpine Space', count: 3 },
39
+ { value: 'Outermost regions', count: 2 },
40
+ ]
41
+ : [],
37
42
  },
38
43
  ],
39
44
  [COUNTRIES_FIELD]: [
40
45
  {
41
- data: [
42
- { value: 'Germany', count: 4 },
43
- { value: 'Spain', count: 2 },
44
- ],
46
+ data: detailedOptions
47
+ ? [
48
+ { value: 'Germany', count: 4 },
49
+ { value: 'Spain', count: 2 },
50
+ ]
51
+ : [],
45
52
  },
46
53
  ],
47
54
  },
@@ -58,14 +65,8 @@ const renderFacet = ({
58
65
  defaultMessage: 'Transnational regions',
59
66
  }}
60
67
  options={[
61
- { value: 'Global', count: 5, selected: false },
62
68
  { value: 'Europe', count: 8, selected: false },
63
- {
64
- value: 'Macro-Transnational region',
65
- count: 4,
66
- selected: false,
67
- },
68
- { value: 'Countries', count: 6, selected: false },
69
+ { value: 'Global', count: 5, selected: false },
69
70
  ]}
70
71
  />
71
72
  </IntlProvider>,
@@ -79,15 +80,37 @@ describe('NavigatorGeographicCoverageFacet', () => {
79
80
  jest.clearAllMocks();
80
81
  });
81
82
 
82
- it('shows Globe and Europe as direct options and filters by the raw value', () => {
83
- const { onSelect } = renderFacet();
83
+ it('shows Global and Europe as direct options', () => {
84
+ renderFacet();
85
+
86
+ expect(
87
+ screen
88
+ .getAllByRole('checkbox')
89
+ .slice(0, 2)
90
+ .map((option) => option.getAttribute('aria-label')),
91
+ ).toEqual(['Europe', 'Global']);
92
+ });
84
93
 
85
- expect(screen.getByText('Global')).toBeInTheDocument();
86
- expect(screen.getByText('Europe')).toBeInTheDocument();
94
+ it('clears detailed coverage before selecting a direct scope', () => {
95
+ const { onSelect, removeFilter } = renderFacet({
96
+ filters: [
97
+ {
98
+ field: COUNTRIES_FIELD,
99
+ values: ['Germany'],
100
+ type: 'any',
101
+ },
102
+ ],
103
+ });
87
104
 
88
- fireEvent.click(screen.getByLabelText('Global'));
105
+ fireEvent.click(screen.getByLabelText('Europe'));
89
106
 
90
- expect(onSelect).toHaveBeenCalledWith('Global');
107
+ expect(removeFilter).toHaveBeenCalledWith(COUNTRIES_FIELD, null, 'any');
108
+ expect(removeFilter).toHaveBeenCalledWith(
109
+ TRANSNATIONAL_REGION_FIELD,
110
+ null,
111
+ 'any',
112
+ );
113
+ expect(onSelect).toHaveBeenCalledWith('Europe');
91
114
  });
92
115
 
93
116
  it('expands and filters Macro-Transnational region values', () => {
@@ -124,4 +147,47 @@ describe('NavigatorGeographicCoverageFacet', () => {
124
147
  'any',
125
148
  );
126
149
  });
150
+
151
+ it('shows empty detailed coverage groups while Europe is selected', () => {
152
+ const { onRemove } = renderFacet({
153
+ detailedOptions: false,
154
+ filters: [
155
+ {
156
+ field: CHARACTERISATION_FIELD,
157
+ values: ['Europe'],
158
+ type: 'any',
159
+ },
160
+ ],
161
+ });
162
+
163
+ expect(screen.getByLabelText('Europe')).toBeChecked();
164
+ fireEvent.click(screen.getByRole('button', { name: 'Countries' }));
165
+ fireEvent.click(
166
+ screen.getByRole('button', { name: 'Transnational regions' }),
167
+ );
168
+
169
+ expect(screen.getAllByText('No options available.')).toHaveLength(2);
170
+
171
+ fireEvent.click(screen.getByLabelText('Europe'));
172
+
173
+ expect(onRemove).toHaveBeenCalledWith('Europe');
174
+ });
175
+
176
+ it('keeps detailed coverage available when a country is selected', () => {
177
+ renderFacet({
178
+ filters: [
179
+ {
180
+ field: COUNTRIES_FIELD,
181
+ values: ['Germany'],
182
+ type: 'any',
183
+ },
184
+ ],
185
+ });
186
+
187
+ expect(screen.getByRole('button', { name: 'Countries' })).toBeEnabled();
188
+ expect(screen.getByLabelText('Germany')).toBeEnabled();
189
+ expect(
190
+ screen.getByRole('button', { name: 'Transnational regions' }),
191
+ ).toBeEnabled();
192
+ });
127
193
  });
@@ -7,6 +7,7 @@ import {
7
7
  Icon,
8
8
  Loader,
9
9
  Message,
10
+ Popup,
10
11
  Table,
11
12
  Button,
12
13
  } from 'semantic-ui-react';
@@ -84,10 +85,20 @@ const messages = defineMessages({
84
85
  id: 'Usability',
85
86
  defaultMessage: 'Usability',
86
87
  },
88
+ usabilityTooltip: {
89
+ id: 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
90
+ defaultMessage:
91
+ 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
92
+ },
87
93
  functionality: {
88
94
  id: 'Functionality',
89
95
  defaultMessage: 'Functionality',
90
96
  },
97
+ functionalityTooltip: {
98
+ id: 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
99
+ defaultMessage:
100
+ 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
101
+ },
91
102
  spatialScale: {
92
103
  id: 'Spatial scale',
93
104
  defaultMessage: 'Spatial scale',
@@ -231,6 +242,53 @@ const FunctionalityScore = ({ value }) => {
231
242
  );
232
243
  };
233
244
 
245
+ const UsabilityTooltipContent = ({ text }) => {
246
+ const parts = text.match(
247
+ /^(.* with) low = (.*), moderate = (.*), high = (.*)$/,
248
+ );
249
+
250
+ if (!parts) return text;
251
+
252
+ const [, introduction, low, moderate, high] = parts;
253
+ const levels = [
254
+ ['low', low],
255
+ ['moderate', moderate],
256
+ ['high', high],
257
+ ];
258
+
259
+ return (
260
+ <div className="usability-tooltip-content">
261
+ <div className="usability-tooltip-introduction">{introduction}:</div>
262
+ {levels.map(([level, description]) => (
263
+ <div className="usability-tooltip-level" key={level}>
264
+ <strong>{level}</strong>
265
+ <span>{` = ${description}`}</span>
266
+ </div>
267
+ ))}
268
+ </div>
269
+ );
270
+ };
271
+
272
+ const CriterionLabel = ({ label, tooltip, tooltipContent }) => (
273
+ <div className="compare-criteria-title">
274
+ <span>{label}</span>
275
+ <Popup
276
+ className="compare-criteria-popup"
277
+ content={tooltipContent || tooltip}
278
+ position="top left"
279
+ trigger={
280
+ <button
281
+ type="button"
282
+ className="compare-criteria-tooltip"
283
+ aria-label={tooltip}
284
+ >
285
+ <Icon className="ri-information-line" aria-hidden="true" />
286
+ </button>
287
+ }
288
+ />
289
+ </div>
290
+ );
291
+
234
292
  const getCompareUids = (search) => {
235
293
  const params = new URLSearchParams(search);
236
294
 
@@ -297,6 +355,7 @@ const CompareToolsView = () => {
297
355
  const registry = config.settings.searchlib;
298
356
  const landingPageURL = getNavigatorCataloguePageURL(currentLang);
299
357
  const compareToolsTitle = intl.formatMessage(messages.compareTools);
358
+ const usabilityTooltip = intl.formatMessage(messages.usabilityTooltip);
300
359
  const returnURL =
301
360
  location.state?.returnURL ||
302
361
  getReturnURL(location.search) ||
@@ -519,11 +578,19 @@ const CompareToolsView = () => {
519
578
 
520
579
  <Table.Body>
521
580
  <Table.Row>
522
- <Table.Cell as="th" scope="row">
581
+ <Table.Cell
582
+ as="th"
583
+ scope="row"
584
+ aria-label={intl.formatMessage(messages.usability)}
585
+ >
523
586
  <div className="compare-criteria">
524
- <div className="compare-criteria-title">
525
- {intl.formatMessage(messages.usability)}
526
- </div>
587
+ <CriterionLabel
588
+ label={intl.formatMessage(messages.usability)}
589
+ tooltip={usabilityTooltip}
590
+ tooltipContent={
591
+ <UsabilityTooltipContent text={usabilityTooltip} />
592
+ }
593
+ />
527
594
  </div>
528
595
  </Table.Cell>
529
596
 
@@ -537,11 +604,18 @@ const CompareToolsView = () => {
537
604
  </Table.Row>
538
605
 
539
606
  <Table.Row>
540
- <Table.Cell as="th" scope="row">
607
+ <Table.Cell
608
+ as="th"
609
+ scope="row"
610
+ aria-label={intl.formatMessage(messages.functionality)}
611
+ >
541
612
  <div className="compare-criteria">
542
- <div className="compare-criteria-title">
543
- {intl.formatMessage(messages.functionality)}
544
- </div>
613
+ <CriterionLabel
614
+ label={intl.formatMessage(messages.functionality)}
615
+ tooltip={intl.formatMessage(
616
+ messages.functionalityTooltip,
617
+ )}
618
+ />
545
619
  </div>
546
620
  </Table.Cell>
547
621
 
@@ -128,6 +128,16 @@ describe('CompareToolsView', () => {
128
128
  expect(
129
129
  screen.getByRole('rowheader', { name: 'Functionality' }),
130
130
  ).toHaveAttribute('scope', 'row');
131
+ expect(
132
+ screen.getByRole('button', {
133
+ name: 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
134
+ }),
135
+ ).toBeInTheDocument();
136
+ expect(
137
+ screen.getByRole('button', {
138
+ name: 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
139
+ }),
140
+ ).toBeInTheDocument();
131
141
  expect(
132
142
  screen.getByRole('button', {
133
143
  name: 'Remove Tool one from comparison',
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { defineMessages, FormattedMessage } from 'react-intl';
4
4
  import loadable from '@loadable/component';
5
5
  import { compose } from 'redux';
6
+ import { buildTermFacetAggregationRequest } from '@eeacms/search/lib/search';
6
7
  import Sitemap from '@plone/volto/components/theme/Sitemap/Sitemap';
7
8
  import DefaultView from '@plone/volto/components/theme/View/DefaultView';
8
9
  import SelectAutoCompleteWidget from '@plone/volto/components/manage/Widgets/SelectAutoComplete';
@@ -57,6 +58,7 @@ import NavigatorCatalogueContentView from './components/Search/NavigatorCatalogu
57
58
  import NavigatorGeographicCoverageFacet from './components/Search/NavigatorCatalogue/NavigatorGeographicCoverageFacet';
58
59
  import NavigatorGuideLayout from './components/Search/NavigatorGuide/NavigatorGuideLayout';
59
60
  import NavigatorGuideContentView from './components/Search/NavigatorGuide/NavigatorGuideContentView';
61
+ import { getGeographicCoverageFacetValue } from './search/navigator_catalogue/geographicCoverage';
60
62
 
61
63
  import { langRedirection } from './store/middleware';
62
64
 
@@ -437,7 +439,9 @@ const applyConfig = (config) => {
437
439
  };
438
440
  config.settings.searchlib.resolve.NavigatorGeographicCoverageFacet = {
439
441
  ...config.settings.searchlib.resolve.MultiTermFacet,
442
+ buildRequest: buildTermFacetAggregationRequest,
440
443
  component: NavigatorGeographicCoverageFacet,
444
+ getValue: getGeographicCoverageFacetValue,
441
445
  };
442
446
  config.settings.searchlib.resolve.NavigatorGuideLayout = {
443
447
  component: NavigatorGuideLayout,
@@ -4,6 +4,7 @@ import { getClientProxyAddress } from './../utils';
4
4
  import { vocab } from './../vocabulary';
5
5
 
6
6
  import facets from './facets';
7
+ import { geographicCoverageRuntimeMappings } from './geographicCoverage';
7
8
  import views from './views';
8
9
 
9
10
  const navigatorCatalogueConfig = {
@@ -46,7 +47,10 @@ export default function installNavigatorCatalogueSearch(config) {
46
47
  index_name: 'data_searchui',
47
48
  host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
48
49
  vocab,
49
- runtime_mappings: build_runtime_mappings(clusters),
50
+ runtime_mappings: {
51
+ ...build_runtime_mappings(clusters),
52
+ ...geographicCoverageRuntimeMappings,
53
+ },
50
54
  };
51
55
 
52
56
  const { navigatorCatalogueSearch } = config.searchui;
@@ -5,6 +5,7 @@ import {
5
5
  geographic_countries,
6
6
  language,
7
7
  } from './../common';
8
+ import { CHARACTERISATION_FIELD } from './geographicCoverage';
8
9
 
9
10
  const messages = defineMessages({
10
11
  geographicCoverage: {
@@ -68,7 +69,7 @@ const facets = [
68
69
  alwaysVisible: false,
69
70
  }),
70
71
  multiTermFacet({
71
- field: 'cca_geographic_characterisation.keyword',
72
+ field: CHARACTERISATION_FIELD,
72
73
  factory: 'NavigatorGeographicCoverageFacet',
73
74
  isFilterable: false,
74
75
  isMulti: true,
@@ -0,0 +1,47 @@
1
+ export const CHARACTERISATION_FIELD = 'cca_navigator_geographic_scope';
2
+ export const TRANSNATIONAL_REGION_FIELD =
3
+ 'cca_geographic_transnational_region.keyword';
4
+ export const COUNTRIES_FIELD = 'cca_geographic_countries.keyword';
5
+
6
+ export const geographicCoverageRuntimeMappings = {
7
+ [CHARACTERISATION_FIELD]: {
8
+ type: 'keyword',
9
+ script: {
10
+ source: `
11
+ boolean hasCountries = doc.containsKey('cca_geographic_countries.keyword') && doc['cca_geographic_countries.keyword'].size() != 0;
12
+ boolean hasTransnational = doc.containsKey('cca_geographic_transnational_region.keyword') && doc['cca_geographic_transnational_region.keyword'].size() != 0;
13
+ boolean hasSubnational = doc.containsKey('cca_sub_nationals.keyword') && doc['cca_sub_nationals.keyword'].size() != 0;
14
+ boolean hasCity = doc.containsKey('cca_city.keyword') && doc['cca_city.keyword'].size() != 0;
15
+
16
+ if (!hasCountries && !hasTransnational && !hasSubnational && !hasCity) {
17
+ for (def value : doc['cca_geographic_characterisation.keyword']) {
18
+ emit(value);
19
+ }
20
+ }
21
+ `,
22
+ },
23
+ },
24
+ };
25
+
26
+ export const getGeographicCoverageFacetValue = ({
27
+ aggregations,
28
+ fieldName,
29
+ }) => {
30
+ const aggregation = aggregations?.[fieldName];
31
+ if (!aggregation) return undefined;
32
+
33
+ const counts = Object.fromEntries(
34
+ (aggregation.buckets || []).map(({ key, doc_count }) => [key, doc_count]),
35
+ );
36
+
37
+ return [
38
+ {
39
+ field: fieldName,
40
+ type: 'value',
41
+ data: ['Europe', 'Global'].map((value) => ({
42
+ value,
43
+ count: counts[value] || 0,
44
+ })),
45
+ },
46
+ ];
47
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ CHARACTERISATION_FIELD,
3
+ geographicCoverageRuntimeMappings,
4
+ getGeographicCoverageFacetValue,
5
+ } from './geographicCoverage';
6
+
7
+ describe('geographic coverage runtime mapping', () => {
8
+ it('defines an exclusive keyword scope from all specific location fields', () => {
9
+ const mapping = geographicCoverageRuntimeMappings[CHARACTERISATION_FIELD];
10
+
11
+ expect(mapping.type).toBe('keyword');
12
+ expect(mapping.script.source).toContain(
13
+ "doc['cca_geographic_countries.keyword']",
14
+ );
15
+ expect(mapping.script.source).toContain(
16
+ "doc['cca_geographic_transnational_region.keyword']",
17
+ );
18
+ expect(mapping.script.source).toContain("doc['cca_sub_nationals.keyword']");
19
+ expect(mapping.script.source).toContain("doc['cca_city.keyword']");
20
+ expect(mapping.script.source).toContain(
21
+ "doc['cca_geographic_characterisation.keyword']",
22
+ );
23
+ });
24
+ });
25
+
26
+ describe('geographic coverage facet value', () => {
27
+ it('keeps the facet available with zero counts when its buckets are empty', () => {
28
+ expect(
29
+ getGeographicCoverageFacetValue({
30
+ aggregations: {
31
+ [CHARACTERISATION_FIELD]: { buckets: [] },
32
+ },
33
+ fieldName: CHARACTERISATION_FIELD,
34
+ }),
35
+ ).toEqual([
36
+ {
37
+ field: CHARACTERISATION_FIELD,
38
+ type: 'value',
39
+ data: [
40
+ { value: 'Europe', count: 0 },
41
+ { value: 'Global', count: 0 },
42
+ ],
43
+ },
44
+ ]);
45
+ });
46
+
47
+ it('returns Europe and Global alphabetically with their counts', () => {
48
+ expect(
49
+ getGeographicCoverageFacetValue({
50
+ aggregations: {
51
+ [CHARACTERISATION_FIELD]: {
52
+ buckets: [
53
+ { key: 'Global', doc_count: 25 },
54
+ { key: 'Europe', doc_count: 20 },
55
+ ],
56
+ },
57
+ },
58
+ fieldName: CHARACTERISATION_FIELD,
59
+ })[0].data,
60
+ ).toEqual([
61
+ { value: 'Europe', count: 20 },
62
+ { value: 'Global', count: 25 },
63
+ ]);
64
+ });
65
+ });
@@ -354,6 +354,22 @@
354
354
  margin-left: 0.5rem !important;
355
355
  }
356
356
 
357
+ .ui.popup.compare-criteria-popup {
358
+ max-width: min(450px, calc(100vw - 2rem));
359
+
360
+ .usability-tooltip-introduction {
361
+ margin-bottom: 0.65rem;
362
+ }
363
+
364
+ .usability-tooltip-level {
365
+ strong {
366
+ font-weight: 700;
367
+ text-transform: capitalize;
368
+ color: @secondaryColor;
369
+ }
370
+ }
371
+ }
372
+
357
373
  .navigator-catalogue-compare-view {
358
374
  overflow-x: auto;
359
375
 
@@ -431,6 +447,20 @@
431
447
  font-weight: 500;
432
448
  }
433
449
 
450
+ .compare-criteria-tooltip {
451
+ padding: 0;
452
+ border: 0;
453
+ margin-left: 0.4rem;
454
+ background: transparent;
455
+ color: @primaryColor;
456
+ cursor: pointer;
457
+ line-height: 1;
458
+
459
+ i.icon {
460
+ margin: 0;
461
+ }
462
+ }
463
+
434
464
  .usability-value,
435
465
  .functionality-value {
436
466
  color: @secondaryColor;