diff --git a/conf/default.toml b/conf/default.toml index 661eacf206..fee3f97505 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -12,8 +12,9 @@ geonetwork4_api_url = "/geonetwork/srv/api" proxy_path = "" # This optional parameter defines, in which language metadata should be queried in elasticsearch. # Use ISO 639-2/B (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format to indicate the language of the metadata. +# Setting to "current" will use the current language of the User Interface. # If not indicated, a wildcard is used and no language preference is applied for the search. -# metadata_language = "fre" +# metadata_language = "current" # This optional URL should point to the login page that allows authentication to the datahub. # If not indicated, the default geonetwork login page is used. # The following three placeholders can be part of this URL: diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts b/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts index bff735b7d4..676a59ae64 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts @@ -14,11 +14,12 @@ export const ES_SOURCE_SUMMARY = [ 'userSavedCount', ] -export const ES_QUERY_STRING_FIELDS = [ - 'resourceTitleObject.${searchLang}^5', - 'tag.${searchLang}^4', - 'resourceAbstractObject.${searchLang}^3', - 'lineageObject.${searchLang}^2', - 'any.${searchLang}', - 'uuid', -] +export type EsQueryFieldsPriorityType = Record +export const ES_QUERY_FIELDS_PRIORITY = { + 'resourceTitleObject.${searchLang}': 5, + 'tag.${searchLang}': 4, + 'resourceAbstractObject.${searchLang}': 3, + 'lineageObject.${searchLang}': 2, + 'any.${searchLang}': 1, + uuid: 1, +} diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts index 4dbe744ef7..c3e1b5688d 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts @@ -1,13 +1,32 @@ import { ElasticsearchService } from './elasticsearch.service' import { ES_FIXTURE_AGGS_RESPONSE } from '@geonetwork-ui/common/fixtures' -import { EsSearchParams } from '../types/elasticsearch.model' +import { LangService } from '@geonetwork-ui/util/i18n' +import { EsSearchParams } from '@geonetwork-ui/api/metadata-converter' +import { TestBed } from '@angular/core/testing' +import { METADATA_LANGUAGE } from '../../metadata-language' + +class LangServiceMock { + iso3 = 'eng' +} describe('ElasticsearchService', () => { let service: ElasticsearchService let searchFilters beforeEach(() => { - service = new ElasticsearchService('fre') + TestBed.configureTestingModule({ + providers: [ + { + provide: LangService, + useClass: LangServiceMock, + }, + { + provide: METADATA_LANGUAGE, + useValue: 'fre', + }, + ], + }) + service = TestBed.inject(ElasticsearchService) }) it('should be created', () => { @@ -356,6 +375,72 @@ describe('ElasticsearchService', () => { }) }) + describe('#injectLangInQueryStringFields - Search language', () => { + let queryStringFields = { 'resourceTitleObject.${searchLang}': 1 } + describe('When no lang from config', () => { + beforeEach(() => { + service['metadataLang'] = undefined + }) + it('use * wildcard', () => { + expect( + service['injectLangInQueryStringFields'](queryStringFields)[0].split( + '.' + )[1] + ).toEqual('*') + }) + }) + describe('When one lang in config', () => { + beforeEach(() => { + service['metadataLang'] = 'fre' + }) + it('search in the config language', () => { + expect( + service['injectLangInQueryStringFields'](queryStringFields)[0].split( + '.' + )[1] + ).toEqual('langfre') + }) + }) + describe('When "current" language from config"', () => { + beforeEach(() => { + service['metadataLang'] = 'current' + service['lang3'] = 'eng' + }) + it('search in the UI language', () => { + expect( + service['injectLangInQueryStringFields'](queryStringFields)[0].split( + '.' + )[1] + ).toEqual('langeng^11') + }) + it('add * fallback with low priority', () => { + queryStringFields = { + 'resourceTitleObject.${searchLang}': 5, + 'tag.${searchLang}': 4, + 'resourceAbstractObject.${searchLang}': 3, + 'lineageObject.${searchLang}': 2, + 'any.${searchLang}': 1, + uuid: 1, + } + expect( + service['injectLangInQueryStringFields'](queryStringFields) + ).toEqual([ + 'resourceTitleObject.langeng^15', + 'resourceTitleObject.*^5', + 'tag.langeng^14', + 'tag.*^4', + 'resourceAbstractObject.langeng^13', + 'resourceAbstractObject.*^3', + 'lineageObject.langeng^12', + 'lineageObject.*^2', + 'any.langeng^11', + 'any.*', + 'uuid', + ]) + }) + }) + }) + describe('#buildAutocompletePayload', () => { describe('given an autocomplete config', () => { it('returns the search payload', () => { @@ -373,9 +458,9 @@ describe('ElasticsearchService', () => { { multi_match: { fields: [ - 'resourceTitleObject.langfre', - 'resourceAbstractObject.langfre', - 'tag', + 'resourceTitleObject.langfre^4', + 'resourceAbstractObject.langfre^3', + 'tag^2', 'resourceIdentifier', ], query: 'blarg', diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts index 8a9b92af1d..3bdd0b7c0a 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts @@ -1,6 +1,10 @@ import { Inject, Injectable, Optional } from '@angular/core' import { Geometry } from 'geojson' -import { ES_QUERY_STRING_FIELDS, ES_SOURCE_SUMMARY } from './constant' +import { + ES_QUERY_FIELDS_PRIORITY, + ES_SOURCE_SUMMARY, + EsQueryFieldsPriorityType, +} from './constant' import { Aggregation, AggregationParams, @@ -19,6 +23,7 @@ import { SortParams, TermsAggregationResult, } from '@geonetwork-ui/api/metadata-converter' +import { LangService } from '@geonetwork-ui/util/i18n' @Injectable({ providedIn: 'root', @@ -27,8 +32,10 @@ export class ElasticsearchService { // runtime fields are computed using a Painless script // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html private runtimeFields: Record = {} + private lang3 = this.langService.iso3 constructor( + private langService: LangService, @Optional() @Inject(METADATA_LANGUAGE) private metadataLang: string ) {} @@ -168,13 +175,40 @@ export class ElasticsearchService { } private injectLangInQueryStringFields( - queryStringFields: string[], - lang: string + queryFieldsPriority: EsQueryFieldsPriorityType ) { - const queryLang = lang ? `lang${lang}` : `*` - return queryStringFields.map((field) => { - return field.replace(/\$\{searchLang\}/g, queryLang) - }) + const queryLang = this.getQueryLang() + return Object.keys(queryFieldsPriority).reduce((query, field) => { + const multiLangRegExp = /\$\{searchLang\}/g + const isMultilangField = multiLangRegExp.test(field) + const fieldPriority = queryFieldsPriority[field] + return [ + ...query, + ...(this.isCurrentSearchLang() && isMultilangField + ? [ + `${field.replace(multiLangRegExp, queryLang)}^${ + fieldPriority + 10 + }`, + field.replace(multiLangRegExp, '*') + + (fieldPriority > 1 ? `^${fieldPriority}` : ''), + ] + : [ + field.replace(multiLangRegExp, queryLang) + + (fieldPriority > 1 ? `^${fieldPriority}` : ''), + ]), + ] + }, []) + } + + private getQueryLang(): string { + if (this.metadataLang) { + return this.isCurrentSearchLang() + ? `lang${this.lang3}` + : `lang${this.metadataLang}` + } else return '*' + } + private isCurrentSearchLang() { + return this.metadataLang === 'current' } private buildPayloadQuery( @@ -203,10 +237,7 @@ export class ElasticsearchService { query_string: { query: this.escapeSpecialCharacters(any), default_operator: 'AND', - fields: this.injectLangInQueryStringFields( - ES_QUERY_STRING_FIELDS, - this.metadataLang - ), + fields: this.injectLangInQueryStringFields(ES_QUERY_FIELDS_PRIORITY), }, }) } @@ -290,15 +321,12 @@ export class ElasticsearchService { multi_match: { query, type: 'bool_prefix', - fields: this.injectLangInQueryStringFields( - [ - 'resourceTitleObject.${searchLang}', - 'resourceAbstractObject.${searchLang}', - 'tag', - 'resourceIdentifier', - ], - this.metadataLang - ), + fields: this.injectLangInQueryStringFields({ + 'resourceTitleObject.${searchLang}': 4, + 'resourceAbstractObject.${searchLang}': 3, + tag: 2, + resourceIdentifier: 1, + }), }, }, ],