diff --git a/apps/datahub/src/app/home/home-header/home-header.component.spec.ts b/apps/datahub/src/app/home/home-header/home-header.component.spec.ts index 6c2ee6bc7..07858c10a 100644 --- a/apps/datahub/src/app/home/home-header/home-header.component.spec.ts +++ b/apps/datahub/src/app/home/home-header/home-header.component.spec.ts @@ -6,11 +6,15 @@ import { RouterFacade, ROUTER_ROUTE_SEARCH, } from '@geonetwork-ui/feature/router' -import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' +import { + FieldsService, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' import { SortByEnum } from '@geonetwork-ui/util/shared' import { TranslateModule } from '@ngx-translate/core' import { readFirst } from '@nrwl/angular/testing' -import { BehaviorSubject } from 'rxjs' +import { BehaviorSubject, of } from 'rxjs' import { ROUTER_ROUTE_NEWS } from '../../router/constants' import { HeaderBadgeButtonComponent } from '../header-badge-button/header-badge-button.component' import { HomeHeaderComponent } from './home-header.component' @@ -23,14 +27,14 @@ jest.mock('@geonetwork-ui/util/app-config', () => ({ getOptionalSearchConfig: () => ({ SEARCH_PRESET: [ { - _sort: '-createDate', + sort: '-createDate', name: 'sortCeatedDateAndOrg', - OrgForResource: ['DREAL'], + filters: { publisher: ['DREAL'] }, }, { - _sort: '-createDate', + sort: '-createDate', name: 'filterCarto', - any: 'Cartographie', + filters: { q: 'Cartographie' }, }, ], }), @@ -59,6 +63,10 @@ class AuthServiceMock { _authSubject$ = new BehaviorSubject({}) } +class FieldsServiceMock { + buildFiltersFromFieldValues = jest.fn(() => of({ thisIs: 'a fake filter' })) +} + describe('HeaderComponent', () => { let component: HomeHeaderComponent let fixture: ComponentFixture @@ -89,6 +97,10 @@ describe('HeaderComponent', () => { provide: AuthService, useClass: AuthServiceMock, }, + { + provide: FieldsService, + useClass: FieldsServiceMock, + }, ], }).compileComponents() authService = TestBed.inject(AuthService) @@ -208,7 +220,7 @@ describe('HeaderComponent', () => { }) it('should redirect correctly', () => { expect(searchService.setSortAndFilters).toHaveBeenCalledWith( - { OrgForResource: { DREAL: true } }, + { thisIs: 'a fake filter' }, SortByEnum.CREATE_DATE ) }) diff --git a/apps/datahub/src/app/home/home-header/home-header.component.ts b/apps/datahub/src/app/home/home-header/home-header.component.ts index 10736d0c6..567a336bc 100644 --- a/apps/datahub/src/app/home/home-header/home-header.component.ts +++ b/apps/datahub/src/app/home/home-header/home-header.component.ts @@ -5,20 +5,22 @@ import { RouterFacade, ROUTER_ROUTE_SEARCH, } from '@geonetwork-ui/feature/router' -import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' +import { + FieldValues, + FieldsService, + SearchFacade, + SearchService, +} from '@geonetwork-ui/feature/search' import { getOptionalSearchConfig, getThemeConfig, SearchConfig, + SearchPreset, } from '@geonetwork-ui/util/app-config' -import { - MetadataRecord, - RawCustomSearchFilters, - SearchFilters, - SortByEnum, -} from '@geonetwork-ui/util/shared' +import { MetadataRecord, SortByEnum } from '@geonetwork-ui/util/shared' import { map } from 'rxjs/operators' import { ROUTER_ROUTE_NEWS } from '../../router/constants' +import { lastValueFrom } from 'rxjs' marker('datahub.header.myfavorites') marker('datahub.header.lastRecords') @@ -45,7 +47,8 @@ export class HomeHeaderComponent { public routerFacade: RouterFacade, public searchFacade: SearchFacade, private searchService: SearchService, - private authService: AuthService + private authService: AuthService, + private fieldsService: FieldsService ) {} displaySortBadges$ = this.routerFacade.currentRoute$.pipe( @@ -60,25 +63,6 @@ export class HomeHeaderComponent { .authReady() .pipe(map((user) => !!user?.id)) - private mapArrayIntoSearchFiltersObject( - customSearchParameters: RawCustomSearchFilters - ): SearchFilters { - const searchFilters = {} - for (const [key, value] of Object.entries(customSearchParameters)) { - if (value instanceof Array) { - const searchFilterProperty = {} - customSearchParameters[key].forEach((element) => { - searchFilterProperty[element] = true - }) - searchFilters[key] = searchFilterProperty - } else if (key === 'any' && value) { - searchFilters[key] = value - } - } - - return searchFilters - } - onFuzzySearchSelection(record: MetadataRecord) { this.routerFacade.goToMetadata(record) } @@ -91,10 +75,15 @@ export class HomeHeaderComponent { this.searchService.setSortAndFilters({}, sort) } - clearSearchAndFilterAndSort(customSearchParameters: any): void { + async clearSearchAndFilterAndSort(customSearchParameters: SearchPreset) { + const searchFilters = await lastValueFrom( + this.fieldsService.buildFiltersFromFieldValues( + customSearchParameters.filters + ) + ) this.searchService.setSortAndFilters( - this.mapArrayIntoSearchFiltersObject(customSearchParameters), - customSearchParameters._sort + searchFilters, + customSearchParameters.sort as SortByEnum ) } } diff --git a/conf/default.toml b/conf/default.toml index 8aff71b1c..90568614c 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -63,21 +63,31 @@ background_color = "#fdfbff" # Note: if the GeoJSON object contains multiple features, only the geometry of the first one will be kept! # filter_geometry_url = "https://my.domain.org/assets/boundary.geojson" # filter_geometry_data = '{ "coordinates": [...], "type": "Polygon" }' + +# One or several search presets can be defined here; every search preset is composed of: +# - a name (which can be a translation key) +# - a sort criteria: either `createDate`, `userSavedCount` or `_score` (prepend with `-` for descending sort) +# - filters which can be expressed like so: +# [[search_preset]] +# filters.q = 'Full text search +# filters.publisher = ['Org 1', 'Org 2'] +# filters.format = ['format 1', 'format 2'] +# filters.documentStandard = ['iso19115-3.2018'] +# filters.inspireKeyword = ['keyword 1', 'keyword 2'] +# filters.topic = ['boundaries'] +# filters.publicationYear = ['2023', '2022'] +# filters.isSpatial = ['yes'] +# filters.license = ['unknown'] +# +# Search presets will be advertised to the user along the main search field. + # [[search_preset]] -# _sort = "-createDate" +# sort = "-createDate" # name = 'filterByOrgs' -# any = 'Carto' -# organisation = ['DREAL', 'atmo Hauts-de-France'] -# format = ['ESRI Shapefile'] -# standard = ['iso19115-3.2018'] -# #inspireKeyword = [] -# topic = ['boundaries'] -# publicationYear = ['2023', '2022'] -# spatial = ['yes'] -# license = ['unknown'] +# filters.publisher = ['DREAL', 'atmo Hauts-de-France', 'blargz'] # [[search_preset]] # name = 'Wind turbines' -# any = 'wind' +# filters.q = 'wind' ### MAP SETTINGS diff --git a/libs/feature/search/src/lib/utils/service/fields.service.ts b/libs/feature/search/src/lib/utils/service/fields.service.ts index 3c4eccb19..02181c271 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -10,8 +10,8 @@ import { SimpleSearchField, TopicSearchField, } from './fields' -import { combineLatest, forkJoin, Observable, of, takeLast } from 'rxjs' -import { map, mergeScan } from 'rxjs/operators' +import { forkJoin, Observable, of } from 'rxjs' +import { map } from 'rxjs/operators' // key is the field name export type FieldValues = Record diff --git a/libs/util/app-config/src/lib/app-config.spec.ts b/libs/util/app-config/src/lib/app-config.spec.ts index 6a01fdc34..5c0ba7a72 100644 --- a/libs/util/app-config/src/lib/app-config.spec.ts +++ b/libs/util/app-config/src/lib/app-config.spec.ts @@ -161,13 +161,15 @@ describe('app config utils', () => { SEARCH_PRESET: [ { name: 'filterByOrgs', - _sort: '-createDate', - any: 'Carto', - OrgForResource: ['Org1', 'Org2'], - 'cl_topic.key': ['boundaries'], - format: ['ESRI Shapefile'], - isSpatial: ['yes'], - publicationYearForResource: ['2023', '2022'], + sort: '-createDate', + filters: { + q: 'Carto', + organisation: ['Org1', 'Org2'], + topic: ['boundaries'], + format: ['ESRI Shapefile'], + spatial: ['yes'], + publicationYear: ['2023', '2022'], + }, }, ], }) diff --git a/libs/util/app-config/src/lib/app-config.ts b/libs/util/app-config/src/lib/app-config.ts index e7170c08d..393acdc05 100644 --- a/libs/util/app-config/src/lib/app-config.ts +++ b/libs/util/app-config/src/lib/app-config.ts @@ -14,7 +14,6 @@ import { SearchConfig, ThemeConfig, } from './model' -import { RawCustomSearchFilters } from '@geonetwork-ui/util/shared' const MISSING_CONFIG_ERROR = `Application configuration was not initialized correctly. This error might show up in case of an invalid/malformed configuration file. @@ -199,18 +198,7 @@ export function loadAppConfig() { parsed, 'search_preset', ['name'], - [ - '_sort', - 'any', - 'organisation', - 'format', - 'standard', - 'inspireKeyword', - 'topic', - 'publicationYear', - 'spatial', - 'license', - ], + ['sort', 'filters'], warnings, errors ) @@ -220,23 +208,21 @@ export function loadAppConfig() { : ({ FILTER_GEOMETRY_DATA: parsedSearchSection.filter_geometry_data, FILTER_GEOMETRY_URL: parsedSearchSection.filter_geometry_url, - SEARCH_PRESET: parsedSearchParams.map( - (param) => - ({ - _sort: param._sort, - name: param.name, - any: param.any, - OrgForResource: param.organisation, - format: param.format, - documentStandard: param.standard, - 'th_httpinspireeceuropaeutheme-theme_tree.default': - param.inspireKeyword, - 'cl_topic.key': param.topic, - publicationYearForResource: param.publicationYear, - isSpatial: param.spatial, - license: param.license, - } as RawCustomSearchFilters) - ), + SEARCH_PRESET: parsedSearchParams.map((param) => ({ + sort: param.sort, + name: param.name, + filters: param.filters, + // any: param.any, + // OrgForResource: param.organisation, + // format: param.format, + // documentStandard: param.standard, + // 'th_httpinspireeceuropaeutheme-theme_tree.default': + // param.inspireKeyword, + // 'cl_topic.key': param.topic, + // publicationYearForResource: param.publicationYear, + // isSpatial: param.spatial, + // license: param.license, + })), } as any) customTranslations = parseTranslationsConfigSection( diff --git a/libs/util/app-config/src/lib/fixtures.ts b/libs/util/app-config/src/lib/fixtures.ts index ff93ec26c..45fa72e0d 100644 --- a/libs/util/app-config/src/lib/fixtures.ts +++ b/libs/util/app-config/src/lib/fixtures.ts @@ -39,14 +39,14 @@ fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans" filter_geometry_url = 'https://my.domain.org/geom.json' [[search_preset]] -_sort = "-createDate" +sort = "-createDate" name = 'filterByOrgs' -any = 'Carto' -organisation = ['Org1', 'Org2'] -format = ['ESRI Shapefile'] -topic = ['boundaries'] -publicationYear = ['2023', '2022'] -spatial = ['yes'] +filters.q = 'Carto' +filters.organisation = ['Org1', 'Org2'] +filters.format = ['ESRI Shapefile'] +filters.topic = ['boundaries'] +filters.publicationYear = ['2023', '2022'] +filters.spatial = ['yes'] [translations.en] "my.first.key" = 'First label.' diff --git a/libs/util/app-config/src/lib/model.ts b/libs/util/app-config/src/lib/model.ts index eb59e6e17..0af2c0dd3 100644 --- a/libs/util/app-config/src/lib/model.ts +++ b/libs/util/app-config/src/lib/model.ts @@ -37,14 +37,16 @@ export interface ThemeConfig { FONTS_STYLESHEET_URL?: string } +export interface SearchPreset { + sort: string + name: string + filters: Record +} + export interface SearchConfig { FILTER_GEOMETRY_URL?: string FILTER_GEOMETRY_DATA?: string - SEARCH_PRESET?: { - _sort: string - _filters: unknown - name: string - }[] + SEARCH_PRESET?: SearchPreset[] } export type CustomTranslations = { [translationKey: string]: string } diff --git a/libs/util/app-config/src/lib/parse-utils.ts b/libs/util/app-config/src/lib/parse-utils.ts index b21965cb1..01528207c 100644 --- a/libs/util/app-config/src/lib/parse-utils.ts +++ b/libs/util/app-config/src/lib/parse-utils.ts @@ -54,7 +54,7 @@ export function parseConfigSection( return null } - const sectionConf = flatten('', fullConfigObj[sectionName]) + const sectionConf = fullConfigObj[sectionName] as Record const keysCheck = checkKeys(sectionConf, mandatoryKeys, optionalKeys) if (keysCheck.missing.length) {