From 912f2cb8fb6d002ed902aa4f59ebc1b32172e7b6 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Wed, 22 Nov 2023 18:43:25 +0100 Subject: [PATCH 01/10] feat: adds ThesaurusTranslationSearchField request to fields --- .../gn4/src/openapi/api/tools.api.service.ts | 103 +++++++++++++++++- .../src/lib/utils/service/fields.service.ts | 8 +- .../search/src/lib/utils/service/fields.ts | 52 +++++++++ 3 files changed, 157 insertions(+), 6 deletions(-) diff --git a/libs/data-access/gn4/src/openapi/api/tools.api.service.ts b/libs/data-access/gn4/src/openapi/api/tools.api.service.ts index c03adcf654..ffb0c02e7a 100644 --- a/libs/data-access/gn4/src/openapi/api/tools.api.service.ts +++ b/libs/data-access/gn4/src/openapi/api/tools.api.service.ts @@ -14,18 +14,31 @@ import { Inject, Injectable, Optional } from '@angular/core' import { HttpClient, + HttpEvent, HttpHeaders, + HttpParameterCodec, HttpParams, HttpResponse, - HttpEvent, - HttpParameterCodec, } from '@angular/common/http' import { CustomHttpParameterCodec } from '../encoder' import { Observable } from 'rxjs' -import { BASE_PATH, COLLECTION_FORMATS } from '../variables' +import { BASE_PATH } from '../variables' import { Configuration } from '../configuration' +interface thesaurusResponse { + values: string[] + definitions: string[] + coordEast: string + coordWest: string + coordSouth: string + coordNorth: string + thesaurusKey: string + value: string + uri: string + definition: string +} + @Injectable({ providedIn: 'root', }) @@ -913,4 +926,88 @@ export class ToolsApiService { } ) } + + /** + * List database translations (used to overrides client application translations). + * @param esFieldName set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param iso3 set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'body', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'response', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'events', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe: any = 'body', + reportProgress: boolean = false, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable { + if ( + thesaurusName === null || + thesaurusName === undefined || + langIso3 === null || + langIso3 === undefined + ) { + throw new Error( + 'Required parameter pack was null or undefined when calling getTranslationsFromThesaurus.' + ) + } + + let headers = this.defaultHeaders + + let httpHeaderAcceptSelected: string | undefined = + options && options.httpHeaderAccept + if (httpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = ['application/json'] + httpHeaderAcceptSelected = + this.configuration.selectHeaderAccept(httpHeaderAccepts) + } + if (httpHeaderAcceptSelected !== undefined) { + headers = headers.set('Accept', httpHeaderAcceptSelected) + } + + let responseType_: 'text' | 'json' = 'json' + if ( + httpHeaderAcceptSelected && + httpHeaderAcceptSelected.startsWith('text') + ) { + responseType_ = 'text' + } + + return this.httpClient.get( + `${ + this.configuration.basePath + }/registries/vocabularies/search?rows=1000&type=CONTAINS&sort=DESC&thesaurus=${encodeURIComponent( + String(thesaurusName) + )}&lang=${encodeURIComponent(langIso3)}`, + { + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: headers, + observe: observe, + reportProgress: reportProgress, + } + ) + } } 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 8ef7ae172e..d4006f8f3b 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -1,14 +1,15 @@ import { Injectable, Injector } from '@angular/core' import { AbstractSearchField, - GnUiTranslationSearchField, FieldValue, FullTextSearchField, + GnUiTranslationSearchField, IsSpatialSearchField, LicenseSearchField, OrganizationSearchField, OwnerSearchField, SimpleSearchField, + ThesaurusTranslationSearchField, } from './fields' import { forkJoin, Observable, of } from 'rxjs' import { map } from 'rxjs/operators' @@ -52,8 +53,9 @@ export class FieldsService { this.injector ), topic: new GnUiTranslationSearchField('cl_topic.key', 'asc', this.injector), - inspireKeyword: new SimpleSearchField( - 'th_httpinspireeceuropaeutheme-theme_tree.default', + inspireKeyword: new ThesaurusTranslationSearchField( + 'th_httpinspireeceuropaeutheme-theme.link', + 'external.theme.httpinspireeceuropaeutheme-theme', 'asc', this.injector ), diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index 41949fe408..feb2f9f29a 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -14,6 +14,7 @@ import { } from '@geonetwork-ui/common/domain/model/search' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { ElasticsearchService } from '@geonetwork-ui/api/repository/gn4' +import { LangService } from '@geonetwork-ui/util/i18n' export type FieldValue = string | number export interface FieldAvailableValue { @@ -127,6 +128,57 @@ export class GnUiTranslationSearchField extends SimpleSearchField { } } +export class ThesaurusTranslationSearchField extends SimpleSearchField { + private toolsApiService = this.injector.get(ToolsApiService) + private langService = this.injector.get(LangService) + allTranslations = this.toolsApiService + .getTranslationsFromThesaurus(this.thesaurusName, this.langService.iso3) + .pipe( + map((thesaurus) => { + const alltranslations = {} + thesaurus.map((val) => { + alltranslations[val.uri] = val.value + }) + return alltranslations + }), + catchError(() => { + console.warn('Error while loading thesaurus language package') + return of({}) + }), + shareReplay(1) + ) + + constructor( + esFieldName: string, + protected thesaurusName: string, + order: 'asc' | 'desc' = 'asc', + injector: Injector + ) { + super(esFieldName, order, injector) + } + + private async getTranslation(topicKey: string) { + return firstValueFrom( + this.allTranslations.pipe(map((translations) => translations[topicKey])) + ) + } + + protected async getBucketLabel(bucket: TermBucket) { + return (await this.getTranslation(bucket.term)) || bucket.term + } + + getAvailableValues(): Observable { + // sort values by alphabetical order + return super + .getAvailableValues() + .pipe( + map((values) => + values.sort((a, b) => new Intl.Collator().compare(a.label, b.label)) + ) + ) + } +} + export class FullTextSearchField implements AbstractSearchField { getAvailableValues(): Observable { return of([]) From 6302fe0a9b06f608acc2e430e4e1d23fd47c2fe8 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 5 Dec 2023 11:00:30 +0100 Subject: [PATCH 02/10] Add contact filter from OrgForResource --- .../src/custom-api/thesaurus.api.service.ts | 136 ++++++++++++++++++ libs/data-access/gn4/src/openapi/README.md | 23 ++- .../gn4/src/openapi/api/tools.api.service.ts | 97 ------------- libs/data-access/gn4/src/openapi/index.ts | 1 + .../lib/utils/service/fields.service.spec.ts | 16 ++- .../src/lib/utils/service/fields.service.ts | 5 + .../src/lib/utils/service/fields.spec.ts | 91 +++++++++++- .../search/src/lib/utils/service/fields.ts | 9 +- 8 files changed, 262 insertions(+), 116 deletions(-) create mode 100644 libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts diff --git a/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts b/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts new file mode 100644 index 0000000000..4da8b73ebf --- /dev/null +++ b/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts @@ -0,0 +1,136 @@ +import { Inject, Injectable, Optional } from '@angular/core' +import { + HttpClient, + HttpHeaders, + HttpParameterCodec, +} from '@angular/common/http' +import { Observable } from 'rxjs' +import { CustomHttpParameterCodec } from '../openapi/encoder' +import { Configuration } from '../openapi/configuration' + +import { BASE_PATH, COLLECTION_FORMATS } from '../openapi/variables' + +export interface thesaurusResponse { + values: { [key: string]: string } + definitions: { [key: string]: string } + coordEast?: string + coordWest?: string + coordSouth?: string + coordNorth?: string + thesaurusKey: string + value: string + uri: string + definition?: string +} + +@Injectable({ + providedIn: 'root', +}) +export class ThesaurusApiService { + protected basePath = 'https://apps.titellus.net/geonetwork/srv/api' + public defaultHeaders = new HttpHeaders() + public configuration = new Configuration() + public encoder: HttpParameterCodec + + constructor( + protected httpClient: HttpClient, + @Optional() @Inject(BASE_PATH) basePath: string, + @Optional() configuration: Configuration + ) { + if (configuration) { + this.configuration = configuration + } + if (typeof this.configuration.basePath !== 'string') { + if (typeof basePath !== 'string') { + basePath = this.basePath + } + this.configuration.basePath = basePath + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec() + } + + /** + * List database translations (used to overrides client application translations). + * @param esFieldName set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param iso3 set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'body', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'response', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe?: 'events', + reportProgress?: boolean, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable + public getTranslationsFromThesaurus( + thesaurusName: string, + langIso3: string, + observe: any = 'body', + reportProgress: boolean = false, + options?: { httpHeaderAccept?: 'application/json' } + ): Observable { + if ( + thesaurusName === null || + thesaurusName === undefined || + langIso3 === null || + langIso3 === undefined + ) { + throw new Error( + 'Required parameter pack was null or undefined when calling getTranslationsFromThesaurus.' + ) + } + + console.log('here') + let headers = this.defaultHeaders + + let httpHeaderAcceptSelected: string | undefined = + options && options.httpHeaderAccept + if (httpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = ['application/json'] + httpHeaderAcceptSelected = + this.configuration.selectHeaderAccept(httpHeaderAccepts) + } + if (httpHeaderAcceptSelected !== undefined) { + headers = headers.set('Accept', httpHeaderAcceptSelected) + } + + let responseType_: 'text' | 'json' = 'json' + if ( + httpHeaderAcceptSelected && + httpHeaderAcceptSelected.startsWith('text') + ) { + responseType_ = 'text' + } + + return this.httpClient.get( + `${ + this.configuration.basePath + }/registries/vocabularies/search?rows=1000&type=CONTAINS&sort=DESC&thesaurus=${encodeURIComponent( + String(thesaurusName) + )}&lang=${encodeURIComponent(langIso3)}`, + { + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: headers, + observe: observe, + reportProgress: reportProgress, + } + ) + } +} diff --git a/libs/data-access/gn4/src/openapi/README.md b/libs/data-access/gn4/src/openapi/README.md index dcc0fbf9db..f16604dcff 100644 --- a/libs/data-access/gn4/src/openapi/README.md +++ b/libs/data-access/gn4/src/openapi/README.md @@ -3,7 +3,6 @@ ### Building To install the required dependencies and to build the typescript sources run: - ``` npm install npm run build @@ -11,7 +10,7 @@ npm run build ### publishing -First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!) +First build the package then run ```npm publish dist``` (don't forget to specify the `dist` folder!) ### consuming @@ -34,25 +33,25 @@ _It's important to take the tgz file, otherwise you'll get trouble with links on _using `npm link`:_ In PATH_TO_GENERATED_PACKAGE/dist: - ``` npm link ``` In your project: - ``` -npm link +npm link ``` -**Note for Windows users:** The Angular CLI has troubles to use linked npm packages. +__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages. Please refer to this issue https://github.com/angular/angular-cli/issues/8284 for a solution / workaround. Published packages are not effected by this issue. + #### General usage In your Angular project: + ``` // without configuring providers import { ApiModule } from ''; @@ -129,11 +128,9 @@ Note: The ApiModule is restricted to being instantiated once app wide. This is to ensure that all services are treated as singletons. #### Using multiple OpenAPI files / APIs / ApiModules - In order to use multiple `ApiModules` generated from different OpenAPI files, you can create an alias name when importing the modules in order to avoid naming conflicts: - ``` import { ApiModule } from 'my-api-path'; import { ApiModule as OtherApiModule } from 'my-other-api-path'; @@ -153,9 +150,9 @@ export class AppModule { } ``` -### Set service base path -If different than the generated base path, during app bootstrap, you can provide the base path to your service. +### Set service base path +If different than the generated base path, during app bootstrap, you can provide the base path to your service. ``` import { BASE_PATH } from ''; @@ -164,7 +161,6 @@ bootstrap(AppComponent, [ { provide: BASE_PATH, useValue: 'https://your-web-service.com' }, ]); ``` - or ``` @@ -179,8 +175,8 @@ import { BASE_PATH } from ''; export class AppModule {} ``` -#### Using @angular/cli +#### Using @angular/cli First extend your `src/environments/*.ts` files by adding the corresponding base path: ``` @@ -191,7 +187,6 @@ export const environment = { ``` In the src/app/app.module.ts: - ``` import { BASE_PATH } from ''; import { environment } from '../environments/environment'; @@ -205,4 +200,4 @@ import { environment } from '../environments/environment'; bootstrap: [ AppComponent ] }) export class AppModule { } -``` +``` diff --git a/libs/data-access/gn4/src/openapi/api/tools.api.service.ts b/libs/data-access/gn4/src/openapi/api/tools.api.service.ts index ffb0c02e7a..0faa979284 100644 --- a/libs/data-access/gn4/src/openapi/api/tools.api.service.ts +++ b/libs/data-access/gn4/src/openapi/api/tools.api.service.ts @@ -26,19 +26,6 @@ import { Observable } from 'rxjs' import { BASE_PATH } from '../variables' import { Configuration } from '../configuration' -interface thesaurusResponse { - values: string[] - definitions: string[] - coordEast: string - coordWest: string - coordSouth: string - coordNorth: string - thesaurusKey: string - value: string - uri: string - definition: string -} - @Injectable({ providedIn: 'root', }) @@ -926,88 +913,4 @@ export class ToolsApiService { } ) } - - /** - * List database translations (used to overrides client application translations). - * @param esFieldName set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param iso3 set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param reportProgress flag to report request and response progress. - */ - public getTranslationsFromThesaurus( - thesaurusName: string, - langIso3: string, - observe?: 'body', - reportProgress?: boolean, - options?: { httpHeaderAccept?: 'application/json' } - ): Observable - public getTranslationsFromThesaurus( - thesaurusName: string, - langIso3: string, - observe?: 'response', - reportProgress?: boolean, - options?: { httpHeaderAccept?: 'application/json' } - ): Observable - public getTranslationsFromThesaurus( - thesaurusName: string, - langIso3: string, - observe?: 'events', - reportProgress?: boolean, - options?: { httpHeaderAccept?: 'application/json' } - ): Observable - public getTranslationsFromThesaurus( - thesaurusName: string, - langIso3: string, - observe: any = 'body', - reportProgress: boolean = false, - options?: { httpHeaderAccept?: 'application/json' } - ): Observable { - if ( - thesaurusName === null || - thesaurusName === undefined || - langIso3 === null || - langIso3 === undefined - ) { - throw new Error( - 'Required parameter pack was null or undefined when calling getTranslationsFromThesaurus.' - ) - } - - let headers = this.defaultHeaders - - let httpHeaderAcceptSelected: string | undefined = - options && options.httpHeaderAccept - if (httpHeaderAcceptSelected === undefined) { - // to determine the Accept header - const httpHeaderAccepts: string[] = ['application/json'] - httpHeaderAcceptSelected = - this.configuration.selectHeaderAccept(httpHeaderAccepts) - } - if (httpHeaderAcceptSelected !== undefined) { - headers = headers.set('Accept', httpHeaderAcceptSelected) - } - - let responseType_: 'text' | 'json' = 'json' - if ( - httpHeaderAcceptSelected && - httpHeaderAcceptSelected.startsWith('text') - ) { - responseType_ = 'text' - } - - return this.httpClient.get( - `${ - this.configuration.basePath - }/registries/vocabularies/search?rows=1000&type=CONTAINS&sort=DESC&thesaurus=${encodeURIComponent( - String(thesaurusName) - )}&lang=${encodeURIComponent(langIso3)}`, - { - responseType: responseType_, - withCredentials: this.configuration.withCredentials, - headers: headers, - observe: observe, - reportProgress: reportProgress, - } - ) - } } diff --git a/libs/data-access/gn4/src/openapi/index.ts b/libs/data-access/gn4/src/openapi/index.ts index 8e6723a9e5..92f0d7b85f 100644 --- a/libs/data-access/gn4/src/openapi/index.ts +++ b/libs/data-access/gn4/src/openapi/index.ts @@ -3,3 +3,4 @@ export * from './model/models' export * from './variables' export * from './configuration' export * from './api.module' +export * from '../custom-api/thesaurus.api.service' diff --git a/libs/feature/search/src/lib/utils/service/fields.service.spec.ts b/libs/feature/search/src/lib/utils/service/fields.service.spec.ts index 138965ffba..1427c0b66d 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.spec.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.spec.ts @@ -1,7 +1,10 @@ import { TestBed } from '@angular/core/testing' import { FieldsService } from './fields.service' import { EMPTY, lastValueFrom, of } from 'rxjs' -import { ToolsApiService } from '@geonetwork-ui/data-access/gn4' +import { + ThesaurusApiService, + ToolsApiService, +} from '@geonetwork-ui/data-access/gn4' import { TranslateModule } from '@ngx-translate/core' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' @@ -17,6 +20,11 @@ class ElasticsearchServiceMock { class ToolsApiServiceMock { getTranslationsPackage1 = jest.fn(() => EMPTY) } + +class ThesaurusApiServiceMock { + getTranslationsFromThesaurus = jest.fn(() => of([])) +} + class OrganisationsServiceMock { organisations$ = of([{ name: 'orgA', recordCount: 10 }]) getOrgsFromFilters = jest.fn(() => of([{ name: 'orgB' }])) @@ -50,6 +58,10 @@ describe('FieldsService', () => { provide: OrganizationsServiceInterface, useClass: OrganisationsServiceMock, }, + { + provide: ThesaurusApiService, + useClass: ThesaurusApiServiceMock, + }, ], }) }) @@ -78,6 +90,7 @@ describe('FieldsService', () => { 'q', 'license', 'owner', + 'contact', ]) }) }) @@ -146,6 +159,7 @@ describe('FieldsService', () => { }) it('calls the search api', () => { expect(values).toEqual({ + contact: [], documentStandard: [], format: ['ascii', 'png'], inspireKeyword: [], 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 d4006f8f3b..40485f9499 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -68,6 +68,11 @@ export class FieldsService { q: new FullTextSearchField(), license: new LicenseSearchField(this.injector), owner: new OwnerSearchField(this.injector), + contact: new GnUiTranslationSearchField( + 'OrgForResource', + 'asc', + this.injector + ), } as Record get supportedFields() { diff --git a/libs/feature/search/src/lib/utils/service/fields.spec.ts b/libs/feature/search/src/lib/utils/service/fields.spec.ts index 01b5259af5..fade2a56dd 100644 --- a/libs/feature/search/src/lib/utils/service/fields.spec.ts +++ b/libs/feature/search/src/lib/utils/service/fields.spec.ts @@ -1,4 +1,8 @@ -import { ToolsApiService } from '@geonetwork-ui/data-access/gn4' +import { + ThesaurusApiService, + ToolsApiService, + thesaurusResponse, +} from '@geonetwork-ui/data-access/gn4' import { lastValueFrom, of } from 'rxjs' import { AbstractSearchField, @@ -8,6 +12,7 @@ import { LicenseSearchField, OrganizationSearchField, SimpleSearchField, + ThesaurusTranslationSearchField, } from './fields' import { TestBed } from '@angular/core/testing' import { Injector } from '@angular/core' @@ -145,6 +150,30 @@ const sampleOrgs: Organization[] = [ }, ] +const samplInspireThesaurus: thesaurusResponse[] = [ + { + values: { + ger: 'Adressen', + }, + definitions: { + ger: 'Lokalisierung von Grundstücken anhand von Adressdaten, in der Regel Straßenname, Hausnummer und Postleitzahl.', + }, + coordEast: '', + coordWest: '', + coordSouth: '', + coordNorth: '', + thesaurusKey: 'external.theme.httpinspireeceuropaeutheme-theme', + definition: + 'Lokalisierung von Grundstücken anhand von Adressdaten, in der Regel Straßenname, Hausnummer und Postleitzahl.', + value: 'Adressen', + uri: 'http://inspire.ec.europa.eu/theme/ad', + }, +] + +class ThesaurusApiServiceMock { + getTranslationsFromThesaurus = jest.fn(() => of(samplInspireThesaurus)) +} + class OrganisationsServiceMock { organisations$ = of(sampleOrgs) getOrgsFromFilters = jest.fn(() => of(sampleOrgs.slice(0, 2))) @@ -165,6 +194,7 @@ describe('search fields implementations', () => { let esService: ElasticsearchService let repository: RecordsRepositoryInterface let toolsService: ToolsApiService + let thesaurusService: ThesaurusApiService let injector: Injector beforeEach(() => { @@ -183,6 +213,10 @@ describe('search fields implementations', () => { provide: ToolsApiService, useClass: ToolsApiServiceMock, }, + { + provide: ThesaurusApiService, + useClass: ThesaurusApiServiceMock, + }, { provide: TranslateService, useClass: TranslateServiceMock, @@ -196,6 +230,7 @@ describe('search fields implementations', () => { esService = TestBed.inject(ElasticsearchService) repository = TestBed.inject(RecordsRepositoryInterface) toolsService = TestBed.inject(ToolsApiService) + thesaurusService = TestBed.inject(ThesaurusApiService) injector = TestBed.inject(Injector) }) @@ -545,4 +580,58 @@ describe('search fields implementations', () => { }) }) }) + describe('ThesaurusTranslationSearchField', () => { + beforeEach(() => { + searchField = new ThesaurusTranslationSearchField( + 'th_httpinspireeceuropaeutheme-theme.link', + 'external.theme.httpinspireeceuropaeutheme-theme', + 'asc', + injector + ) + }) + describe('#getFiltersForValues', () => { + let filters + beforeEach(async () => { + filters = await lastValueFrom( + searchField.getFiltersForValues([ + 'Adressen', + 'Abwasser', + 'Atmosphärische Bedingungen', + ]) + ) + }) + it('returns the filters provided by the thesaurus service', () => { + expect(filters).toEqual({ + 'th_httpinspireeceuropaeutheme-theme.link': { + Adressen: true, + Abwasser: true, + 'Atmosphärische Bedingungen': true, + }, + }) + }) + }) + describe('#getAvailableValues', () => { + let values + beforeEach(async () => { + values = await lastValueFrom(searchField.getAvailableValues()) + }) + it('returns a list of values sorted by translated labels', () => { + expect(values).toEqual([ + { label: 'First value (5)', value: 'First value' }, + { + label: 'Fourth value (1)', + value: 'Fourth value', + }, + { label: 'Second value (3)', value: 'Second value' }, + { label: 'Third value (12)', value: 'Third value' }, + ]) + }) + + it('only calls the thesaurus service once', () => { + expect( + thesaurusService.getTranslationsFromThesaurus + ).toHaveBeenCalledTimes(1) + }) + }) + }) }) diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index feb2f9f29a..c960cc0a8e 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -1,5 +1,8 @@ import { firstValueFrom, Observable, of, switchMap } from 'rxjs' -import { ToolsApiService } from '@geonetwork-ui/data-access/gn4' +import { + ThesaurusApiService, + ToolsApiService, +} from '@geonetwork-ui/data-access/gn4' import { catchError, map, shareReplay } from 'rxjs/operators' import { Injector } from '@angular/core' import { TranslateService } from '@ngx-translate/core' @@ -129,9 +132,9 @@ export class GnUiTranslationSearchField extends SimpleSearchField { } export class ThesaurusTranslationSearchField extends SimpleSearchField { - private toolsApiService = this.injector.get(ToolsApiService) + private thesaurusApiService = this.injector.get(ThesaurusApiService) private langService = this.injector.get(LangService) - allTranslations = this.toolsApiService + allTranslations = this.thesaurusApiService .getTranslationsFromThesaurus(this.thesaurusName, this.langService.iso3) .pipe( map((thesaurus) => { From c7d0f9ab70346a3d2d7e085666a1411c0e02547a Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 5 Dec 2023 13:44:28 +0100 Subject: [PATCH 03/10] feat(DH) :Add translation for contact filter --- .../key-figures/key-figures.component.html | 4 +- .../src/lib/utils/service/fields.service.ts | 1 + translations/de.json | 10 +++-- translations/en.json | 33 ++++---------- translations/es.json | 33 ++++---------- translations/fr.json | 33 ++++---------- translations/it.json | 33 ++++---------- translations/nl.json | 33 ++++---------- translations/pt.json | 33 ++++---------- translations/sk.json | 43 +++++++++++++++++-- 10 files changed, 104 insertions(+), 152 deletions(-) diff --git a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html index 943dab7b50..d04e7eee5f 100644 --- a/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html +++ b/apps/datahub/src/app/home/news-page/key-figures/key-figures.component.html @@ -4,7 +4,7 @@ class="py-[37px] pl-[47px] rounded-lg border bg-white mb-5 card-shadow cursor-pointer" [figure]="recordsCount$ | async" [icon]="'folder_open'" - [title]="'catalog.figures.datasets'" + [title]="'catalog.figures.datasets' | translate" [color]="'secondary'" > @@ -13,7 +13,7 @@ class="py-[37px] pl-[47px] rounded-lg bg-white border card-shadow cursor-pointer" [figure]="orgsCount$ | async" [icon]="'corporate_fare'" - [title]="'catalog.figures.organisations'" + [title]="'catalog.figures.organisations' | translate" [color]="'secondary'" > 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 40485f9499..07f4061073 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -29,6 +29,7 @@ marker('search.filters.representationType') marker('search.filters.resourceType') marker('search.filters.standard') marker('search.filters.topic') +marker('search.filters.contact') @Injectable({ providedIn: 'root', diff --git a/translations/de.json b/translations/de.json index 8755763492..9819a448a9 100644 --- a/translations/de.json +++ b/translations/de.json @@ -26,10 +26,10 @@ "dashboard.records.noUser": "", "dashboard.records.publishedRecords": "", "dashboard.records.search": "", - "dashboard.records.users": "", - "dashboard.records.username": "", "dashboard.records.userDetail": "", "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "Dateiformat-Erkennung", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Sammeln von Datensatzinformationen", "datafeeder.analysisProgressBar.illustration.samplingData": "Datenauswahl", @@ -207,6 +207,8 @@ "record.metadata.origin": "Über die Daten", "record.metadata.preview": "Vorschau", "record.metadata.publications": "Veröffentlichungen", + "record.metadata.quality": "", + "record.metadata.quality.details": "", "record.metadata.related": "Ähnliche Datensätze", "record.metadata.sheet": "Weitere Informationen erhalten Sie unter :", "record.metadata.title": "Titel", @@ -228,6 +230,7 @@ "results.showMore": "Mehr Ergebnisse anzeigen...", "results.sortBy.dateStamp": "Letzte Aktualisierungen", "results.sortBy.popularity": "Beliebtheit", + "results.sortBy.qualityScore": "", "results.sortBy.relevancy": "Relevanz", "search.autocomplete.error": "Vorschläge konnten nicht abgerufen werden:", "search.error.couldNotReachApi": "Die API konnte nicht erreicht werden", @@ -236,6 +239,7 @@ "search.field.any.placeholder": "Suche nach Datensätzen ...", "search.field.sortBy": "Sortieren nach:", "search.filters.clear": "Zurücksetzen", + "search.filters.contact": "Kontakt", "search.filters.format": "Formate", "search.filters.inspireKeyword": "INSPIRE-Schlüsselwort", "search.filters.isSpatial": "Ist räumliche Daten", @@ -253,10 +257,10 @@ "search.filters.license.unknown": "Unbekannt oder nicht vorhanden", "search.filters.maximize": "Erweitern", "search.filters.minimize": "Minimieren", - "search.filters.publicationYear": "Veröffentlichungsjahr", "search.filters.myRecords": "", "search.filters.myRecordsHelp": "", "search.filters.otherRecords": "", + "search.filters.publicationYear": "Veröffentlichungsjahr", "search.filters.publisher": "Organisationen", "search.filters.representationType": "", "search.filters.resourceType": "", diff --git a/translations/en.json b/translations/en.json index 19c040d9d0..2c99251c67 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "{count, plural, =0{datasets} one{dataset} other{datasets}}", - "catalog.figures.organisations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "average", "chart.aggregation.count": "count", "chart.aggregation.max": "max", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "No users for this organization", "dashboard.records.publishedRecords": "published records", "dashboard.records.search": "Search for \"{searchText}\"", - "dashboard.records.users": "users", - "dashboard.records.username": "Username", "dashboard.records.userDetail": "Name", "dashboard.records.userEmail": "Email", + "dashboard.records.username": "Username", + "dashboard.records.users": "users", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "File format \n detection", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Gathering dataset \n information", "datafeeder.analysisProgressBar.illustration.samplingData": "Sampling \n data", @@ -207,6 +207,8 @@ "record.metadata.origin": "About the data", "record.metadata.preview": "Preview", "record.metadata.publications": "publications", + "record.metadata.quality": "Metadata Quality", + "record.metadata.quality.details": "Details", "record.metadata.related": "Related records", "record.metadata.sheet": "More information available from:", "record.metadata.title": "Title", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "Data Update Status", "record.metadata.updatedOn": "Last Data Information Update", "record.metadata.usage": "Usage & constraints", - "record.metadata.quality": "Metadata Quality", - "record.metadata.quality.details": "Details", - "record.metadata.quality.title.success": "Title is completed", - "record.metadata.quality.title.failed": "Title is not completed", - "record.metadata.quality.description.success": "Description is completed", - "record.metadata.quality.description.failed": "Description is not completed", - "record.metadata.quality.topic.success": "Topic is completed", - "record.metadata.quality.topic.failed": "Topic is not completed", - "record.metadata.quality.keywords.success": "Keywords are completed", - "record.metadata.quality.keywords.failed": "Keywords are not completed", - "record.metadata.quality.legalConstraints.success": "Legal constraints are completed", - "record.metadata.quality.legalConstraints.failed": "Legal constraints are not completed", - "record.metadata.quality.contact.success": "Contact is completed", - "record.metadata.quality.contact.failed": "Contact is not completed", - "record.metadata.quality.updateFrequency.success": "Update frequency is completed", - "record.metadata.quality.updateFrequency.failed": "Update frequency is not completed", - "record.metadata.quality.organisation.success": "Organisation is completed", - "record.metadata.quality.organisation.failed": "Organisation is not completed", "record.more.details": "Read more", "record.tab.chart": "Chart", "record.tab.data": "Table", @@ -246,8 +230,8 @@ "results.showMore": "Show more results...", "results.sortBy.dateStamp": "Most recent", "results.sortBy.popularity": "Popularity", - "results.sortBy.relevancy": "Relevancy", "results.sortBy.qualityScore": "Quality score", + "results.sortBy.relevancy": "Relevancy", "search.autocomplete.error": "Suggestions could not be fetched:", "search.error.couldNotReachApi": "The API could not be reached", "search.error.receivedError": "An error was received", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "Search datasets ...", "search.field.sortBy": "Sort by:", "search.filters.clear": "Reset", + "search.filters.contact": "Contact", "search.filters.format": "Formats", "search.filters.inspireKeyword": "INSPIRE keyword", "search.filters.isSpatial": "Is spatial data", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "Unknown or absent", "search.filters.maximize": "Expand", "search.filters.minimize": "Minimize", - "search.filters.publicationYear": "Publication year", "search.filters.myRecords": "Show only my records", "search.filters.myRecordsHelp": "When this is enabled, records only created by myself are shown; records created by others will not show up.", "search.filters.otherRecords": "Showing records from another person", + "search.filters.publicationYear": "Publication year", "search.filters.publisher": "Organizations", "search.filters.representationType": "Representation type", "search.filters.resourceType": "Resource type", diff --git a/translations/es.json b/translations/es.json index d35079a512..2b12fdcb37 100644 --- a/translations/es.json +++ b/translations/es.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "conjuntos de datos", - "catalog.figures.organisations": "organizaciones", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "promedio", "chart.aggregation.count": "conteo", "chart.aggregation.max": "máximo", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "", "dashboard.records.publishedRecords": "", "dashboard.records.search": "Buscar \"{searchText}\"", - "dashboard.records.users": "", - "dashboard.records.username": "", "dashboard.records.userDetail": "", "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "", "datafeeder.analysisProgressBar.illustration.samplingData": "", @@ -207,6 +207,8 @@ "record.metadata.origin": "", "record.metadata.preview": "", "record.metadata.publications": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", "record.metadata.related": "", "record.metadata.sheet": "", "record.metadata.title": "", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", - "record.metadata.quality": "", - "record.metadata.quality.details": "", - "record.metadata.quality.title.success": "", - "record.metadata.quality.title.failed": "", - "record.metadata.quality.description.success": "", - "record.metadata.quality.description.failed": "", - "record.metadata.quality.topic.success": "", - "record.metadata.quality.topic.failed": "", - "record.metadata.quality.keywords.success": "", - "record.metadata.quality.keywords.failed": "", - "record.metadata.quality.legalConstraints.success": "", - "record.metadata.quality.legalConstraints.failed": "", - "record.metadata.quality.contact.success": "", - "record.metadata.quality.contact.failed": "", - "record.metadata.quality.updateFrequency.success": "", - "record.metadata.quality.updateFrequency.failed": "", - "record.metadata.quality.organisation.success": "", - "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -246,8 +230,8 @@ "results.showMore": "", "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", - "results.sortBy.relevancy": "", "results.sortBy.qualityScore": "", + "results.sortBy.relevancy": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", + "search.filters.contact": "", "search.filters.format": "", "search.filters.inspireKeyword": "", "search.filters.isSpatial": "", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "", "search.filters.maximize": "", "search.filters.minimize": "", - "search.filters.publicationYear": "", "search.filters.myRecords": "", "search.filters.myRecordsHelp": "", "search.filters.otherRecords": "", + "search.filters.publicationYear": "", "search.filters.publisher": "", "search.filters.representationType": "", "search.filters.resourceType": "", diff --git a/translations/fr.json b/translations/fr.json index c32b0b18d1..be9d5af691 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "{count, plural, =0{données} one{donnée} other{données}}", - "catalog.figures.organisations": "{count, plural, =0{organisations} one{organisation} other{organisations}}", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "moyenne", "chart.aggregation.count": "nombre", "chart.aggregation.max": "maximum", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "Aucun utilisateur pour cette organisation", "dashboard.records.publishedRecords": "données publiées", "dashboard.records.search": "Résultats pour \"{searchText}\"", - "dashboard.records.users": "utilisateurs", - "dashboard.records.username": "Nom d'utilisateur", "dashboard.records.userDetail": "Nom", "dashboard.records.userEmail": "Email", + "dashboard.records.username": "Nom d'utilisateur", + "dashboard.records.users": "utilisateurs", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "Détection du \n format de fichier", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Récupération des informations \n sur le jeu de données", "datafeeder.analysisProgressBar.illustration.samplingData": "Sampling \n des données", @@ -207,6 +207,8 @@ "record.metadata.origin": "À propos des données", "record.metadata.preview": "Aperçu", "record.metadata.publications": "données", + "record.metadata.quality": "Qualité des métadonnées", + "record.metadata.quality.details": "Détails", "record.metadata.related": "Voir aussi", "record.metadata.sheet": "Plus d'informations à l'adresse suivante :", "record.metadata.title": "Titre", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "Statut de mise à jour des données", "record.metadata.updatedOn": "Dernière mise à jour des informations sur les données", "record.metadata.usage": "Conditions d'utilisation", - "record.metadata.quality": "Qualité des métadonnées", - "record.metadata.quality.details": "Détails", - "record.metadata.quality.title.success": "Titre est renseigné", - "record.metadata.quality.title.failed": "Titre n'est pas renseigné", - "record.metadata.quality.description.success": "Description est renseignée", - "record.metadata.quality.description.failed": "Description n'est pas renseignée", - "record.metadata.quality.topic.success": "Thème est renseigné", - "record.metadata.quality.topic.failed": "Thème n'est pas renseigné", - "record.metadata.quality.keywords.success": "Mots clés sont renseignés", - "record.metadata.quality.keywords.failed": "Mots clés ne sont pas renseignés", - "record.metadata.quality.legalConstraints.success": "Contraintes légales sont renseignées", - "record.metadata.quality.legalConstraints.failed": "Contraintes légales ne sont pas renseignées", - "record.metadata.quality.contact.success": "Contact est renseigné", - "record.metadata.quality.contact.failed": "Contact n'est pas renseigné", - "record.metadata.quality.updateFrequency.success": "Fréquence de mise à jour est renseignée", - "record.metadata.quality.updateFrequency.failed": "Fréquence de mise à jour n'est pas renseignée", - "record.metadata.quality.organisation.success": "Producteur est renseigné", - "record.metadata.quality.organisation.failed": "Producteur n'est pas renseigné", "record.more.details": "Détails", "record.tab.chart": "Graphique", "record.tab.data": "Tableau", @@ -246,8 +230,8 @@ "results.showMore": "Plus de résultats...", "results.sortBy.dateStamp": "Plus récent", "results.sortBy.popularity": "Popularité", - "results.sortBy.relevancy": "Pertinence", "results.sortBy.qualityScore": "Indicateur de qualité", + "results.sortBy.relevancy": "Pertinence", "search.autocomplete.error": "Les suggestions ne peuvent pas être récupérées", "search.error.couldNotReachApi": "Problème de connexion à l'API", "search.error.receivedError": "Erreur retournée", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "Rechercher une donnée...", "search.field.sortBy": "Trier par :", "search.filters.clear": "Réinitialiser", + "search.filters.contact": "Contact", "search.filters.format": "Formats", "search.filters.inspireKeyword": "Mot-clé INSPIRE", "search.filters.isSpatial": "Données spatiales", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "Non-reconnue ou absente", "search.filters.maximize": "Agrandir", "search.filters.minimize": "Réduire", - "search.filters.publicationYear": "Année de publication", "search.filters.myRecords": "Voir mes données", "search.filters.myRecordsHelp": "Quand activé, n'affiche que les données créées avec mon utilisateur. Les données créées par les autres utilisateurs ne sont pas affichées.", "search.filters.otherRecords": "Affichage des données d'un autre utilisateur", + "search.filters.publicationYear": "Année de publication", "search.filters.publisher": "Organisations", "search.filters.representationType": "Type de représentation", "search.filters.resourceType": "Type de ressource", diff --git a/translations/it.json b/translations/it.json index 73c62af3af..f3a10a848f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "dataset", - "catalog.figures.organisations": "organizzazioni", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "media", "chart.aggregation.count": "conteggio", "chart.aggregation.max": "massimo", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "", "dashboard.records.publishedRecords": "", "dashboard.records.search": "Cerca \"{searchText}\"", - "dashboard.records.users": "", - "dashboard.records.username": "", "dashboard.records.userDetail": "", "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "", "datafeeder.analysisProgressBar.illustration.samplingData": "", @@ -207,6 +207,8 @@ "record.metadata.origin": "", "record.metadata.preview": "", "record.metadata.publications": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", "record.metadata.related": "", "record.metadata.sheet": "", "record.metadata.title": "", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", - "record.metadata.quality": "", - "record.metadata.quality.details": "", - "record.metadata.quality.title.success": "", - "record.metadata.quality.title.failed": "", - "record.metadata.quality.description.success": "", - "record.metadata.quality.description.failed": "", - "record.metadata.quality.topic.success": "", - "record.metadata.quality.topic.failed": "", - "record.metadata.quality.keywords.success": "", - "record.metadata.quality.keywords.failed": "", - "record.metadata.quality.legalConstraints.success": "", - "record.metadata.quality.legalConstraints.failed": "", - "record.metadata.quality.contact.success": "", - "record.metadata.quality.contact.failed": "", - "record.metadata.quality.updateFrequency.success": "", - "record.metadata.quality.updateFrequency.failed": "", - "record.metadata.quality.organisation.success": "", - "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -246,8 +230,8 @@ "results.showMore": "", "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", - "results.sortBy.relevancy": "", "results.sortBy.qualityScore": "", + "results.sortBy.relevancy": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", + "search.filters.contact": "", "search.filters.format": "", "search.filters.inspireKeyword": "", "search.filters.isSpatial": "", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "", "search.filters.maximize": "", "search.filters.minimize": "", - "search.filters.publicationYear": "", "search.filters.myRecords": "", "search.filters.myRecordsHelp": "", "search.filters.otherRecords": "", + "search.filters.publicationYear": "", "search.filters.publisher": "", "search.filters.representationType": "", "search.filters.resourceType": "", diff --git a/translations/nl.json b/translations/nl.json index 00087862f2..f45077e8af 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "datasets", - "catalog.figures.organisations": "organisaties", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "gemiddelde", "chart.aggregation.count": "aantal", "chart.aggregation.max": "max", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "", "dashboard.records.publishedRecords": "", "dashboard.records.search": "Zoeken naar \"{searchText}\"", - "dashboard.records.users": "", - "dashboard.records.username": "", "dashboard.records.userDetail": "", "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "", "datafeeder.analysisProgressBar.illustration.samplingData": "", @@ -207,6 +207,8 @@ "record.metadata.origin": "", "record.metadata.preview": "", "record.metadata.publications": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", "record.metadata.related": "", "record.metadata.sheet": "", "record.metadata.title": "", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", - "record.metadata.quality": "", - "record.metadata.quality.details": "", - "record.metadata.quality.title.success": "", - "record.metadata.quality.title.failed": "", - "record.metadata.quality.description.success": "", - "record.metadata.quality.description.failed": "", - "record.metadata.quality.topic.success": "", - "record.metadata.quality.topic.failed": "", - "record.metadata.quality.keywords.success": "", - "record.metadata.quality.keywords.failed": "", - "record.metadata.quality.legalConstraints.success": "", - "record.metadata.quality.legalConstraints.failed": "", - "record.metadata.quality.contact.success": "", - "record.metadata.quality.contact.failed": "", - "record.metadata.quality.updateFrequency.success": "", - "record.metadata.quality.updateFrequency.failed": "", - "record.metadata.quality.organisation.success": "", - "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -246,8 +230,8 @@ "results.showMore": "", "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", - "results.sortBy.relevancy": "", "results.sortBy.qualityScore": "", + "results.sortBy.relevancy": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", + "search.filters.contact": "", "search.filters.format": "", "search.filters.inspireKeyword": "", "search.filters.isSpatial": "", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "", "search.filters.maximize": "", "search.filters.minimize": "", - "search.filters.publicationYear": "", "search.filters.myRecords": "", "search.filters.myRecordsHelp": "", "search.filters.otherRecords": "", + "search.filters.publicationYear": "", "search.filters.publisher": "", "search.filters.representationType": "", "search.filters.resourceType": "", diff --git a/translations/pt.json b/translations/pt.json index 83a73859c8..f1bbf5f261 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "conjuntos de dados", - "catalog.figures.organisations": "organizações", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "média", "chart.aggregation.count": "contagem", "chart.aggregation.max": "máximo", @@ -26,10 +26,10 @@ "dashboard.records.noUser": "", "dashboard.records.publishedRecords": "", "dashboard.records.search": "Buscar por \"{searchText}\"", - "dashboard.records.users": "", - "dashboard.records.username": "", "dashboard.records.userDetail": "", "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "", "datafeeder.analysisProgressBar.illustration.samplingData": "", @@ -207,6 +207,8 @@ "record.metadata.origin": "", "record.metadata.preview": "", "record.metadata.publications": "", + "record.metadata.quality": "", + "record.metadata.quality.details": "", "record.metadata.related": "", "record.metadata.sheet": "", "record.metadata.title": "", @@ -214,24 +216,6 @@ "record.metadata.updateStatus": "", "record.metadata.updatedOn": "", "record.metadata.usage": "", - "record.metadata.quality": "", - "record.metadata.quality.details": "", - "record.metadata.quality.title.success": "", - "record.metadata.quality.title.failed": "", - "record.metadata.quality.description.success": "", - "record.metadata.quality.description.failed": "", - "record.metadata.quality.topic.success": "", - "record.metadata.quality.topic.failed": "", - "record.metadata.quality.keywords.success": "", - "record.metadata.quality.keywords.failed": "", - "record.metadata.quality.legalConstraints.success": "", - "record.metadata.quality.legalConstraints.failed": "", - "record.metadata.quality.contact.success": "", - "record.metadata.quality.contact.failed": "", - "record.metadata.quality.updateFrequency.success": "", - "record.metadata.quality.updateFrequency.failed": "", - "record.metadata.quality.organisation.success": "", - "record.metadata.quality.organisation.failed": "", "record.more.details": "", "record.tab.chart": "", "record.tab.data": "", @@ -246,8 +230,8 @@ "results.showMore": "", "results.sortBy.dateStamp": "", "results.sortBy.popularity": "", - "results.sortBy.relevancy": "", "results.sortBy.qualityScore": "", + "results.sortBy.relevancy": "", "search.autocomplete.error": "", "search.error.couldNotReachApi": "", "search.error.receivedError": "", @@ -255,6 +239,7 @@ "search.field.any.placeholder": "", "search.field.sortBy": "", "search.filters.clear": "", + "search.filters.contact": "", "search.filters.format": "", "search.filters.inspireKeyword": "", "search.filters.isSpatial": "", @@ -272,10 +257,10 @@ "search.filters.license.unknown": "", "search.filters.maximize": "", "search.filters.minimize": "", - "search.filters.publicationYear": "", "search.filters.myRecords": "", "search.filters.myRecordsHelp": "", "search.filters.otherRecords": "", + "search.filters.publicationYear": "", "search.filters.publisher": "", "search.filters.representationType": "", "search.filters.resourceType": "", diff --git a/translations/sk.json b/translations/sk.json index 55a57fd795..45e7b6d133 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -1,6 +1,6 @@ { - "catalog.figures.datasets": "datasety", - "catalog.figures.organisations": "organizácie", + "catalog.figures.datasets": "", + "catalog.figures.organisations": "", "chart.aggregation.average": "priemer", "chart.aggregation.count": "počet", "chart.aggregation.max": "maximum", @@ -22,7 +22,14 @@ "dashboard.records.myLibrary": "Moja knižnica", "dashboard.records.myOrg": "Organizácia", "dashboard.records.myRecords": "Moje záznamy", + "dashboard.records.noRecord": "", + "dashboard.records.noUser": "", + "dashboard.records.publishedRecords": "", "dashboard.records.search": "Hľadať \"{searchText}\"", + "dashboard.records.userDetail": "", + "dashboard.records.userEmail": "", + "dashboard.records.username": "", + "dashboard.records.users": "", "datafeeder.analysisProgressBar.illustration.fileFormatDetection": "Detekcia formátu súboru", "datafeeder.analysisProgressBar.illustration.gatheringDatasetInformation": "Zbieranie informácií o datasete", "datafeeder.analysisProgressBar.illustration.samplingData": "Vzorkovanie dát", @@ -219,6 +226,7 @@ "results.records.hits.displayedOn": "{displayed, plural, =0{Žiadny záznam.} one{1 záznam} other{{displayed} záznamov }} {hits, plural, other{zobrazených z {hits} celkovo.}}", "results.records.hits.empty.help.html": "Návrhy:
  • Vyskúšajte iné slová
  • Uveďte menej slov
", "results.records.hits.found": "{hits, plural, =0{Žiadne dokumenty nezodpovedajú zadanému vyhľadávaniu.} one{1 záznam nájdený.} other{{hits} záznamov nájdených.}}", + "results.records.hits.selected": "", "results.showMore": "Zobraziť viac výsledkov...", "results.sortBy.dateStamp": "Najnovšie", "results.sortBy.popularity": "Popularita", @@ -231,6 +239,7 @@ "search.field.any.placeholder": "Hľadať datasety ...", "search.field.sortBy": "Zoradiť podľa:", "search.filters.clear": "Obnoviť", + "search.filters.contact": "", "search.filters.format": "Formáty", "search.filters.inspireKeyword": "Kľúčové slová INSPIRE", "search.filters.isSpatial": "Je priestorový údaj", @@ -244,5 +253,33 @@ "search.filters.license.etalab-v2": "Open Licence v2.0 (Etalab)", "search.filters.license.odbl": "Open Data Commons ODbL", "search.filters.license.odc-by": "Open Data Commons ODC-By", - "search.filters.license.pddl": "Open Data Commons" + "search.filters.license.pddl": "Open Data Commons", + "search.filters.license.unknown": "", + "search.filters.maximize": "", + "search.filters.minimize": "", + "search.filters.myRecords": "", + "search.filters.myRecordsHelp": "", + "search.filters.otherRecords": "", + "search.filters.publicationYear": "", + "search.filters.publisher": "", + "search.filters.representationType": "", + "search.filters.resourceType": "", + "search.filters.standard": "", + "search.filters.title": "", + "search.filters.topic": "", + "search.filters.useSpatialFilter": "", + "search.filters.useSpatialFilterHelp": "", + "share.tab.permalink": "", + "share.tab.webComponent": "", + "table.loading.data": "", + "table.object.count": "", + "table.select.data": "", + "tooltip.html.copy": "", + "tooltip.url.copy": "", + "tooltip.url.open": "", + "wfs.featuretype.notfound": "", + "wfs.geojsongml.notsupported": "", + "wfs.unreachable.cors": "", + "wfs.unreachable.http": "", + "wfs.unreachable.unknown": "" } From b91f0faa4ebd8609f6210232c0dbc097bb61f587 Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Tue, 5 Dec 2023 23:36:10 +0100 Subject: [PATCH 04/10] feat: retrieve values for orgs --- .../src/custom-api/thesaurus.api.service.ts | 3 +- .../src/lib/utils/service/fields.service.ts | 5 +- .../search/src/lib/utils/service/fields.ts | 80 +++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts b/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts index 4da8b73ebf..138d7bfc14 100644 --- a/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts +++ b/libs/data-access/gn4/src/custom-api/thesaurus.api.service.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs' import { CustomHttpParameterCodec } from '../openapi/encoder' import { Configuration } from '../openapi/configuration' -import { BASE_PATH, COLLECTION_FORMATS } from '../openapi/variables' +import { BASE_PATH } from '../openapi/variables' export interface thesaurusResponse { values: { [key: string]: string } @@ -95,7 +95,6 @@ export class ThesaurusApiService { ) } - console.log('here') let headers = this.defaultHeaders let httpHeaderAcceptSelected: string | undefined = 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 07f4061073..7212f4b82f 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -10,6 +10,7 @@ import { OwnerSearchField, SimpleSearchField, ThesaurusTranslationSearchField, + TranslatedSearchField, } from './fields' import { forkJoin, Observable, of } from 'rxjs' import { map } from 'rxjs/operators' @@ -69,8 +70,8 @@ export class FieldsService { q: new FullTextSearchField(), license: new LicenseSearchField(this.injector), owner: new OwnerSearchField(this.injector), - contact: new GnUiTranslationSearchField( - 'OrgForResource', + contact: new TranslatedSearchField( + 'contactForResource.organisationObject.default', 'asc', this.injector ), diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index c960cc0a8e..0c640cb079 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -1,5 +1,6 @@ import { firstValueFrom, Observable, of, switchMap } from 'rxjs' import { + SearchApiService, ThesaurusApiService, ToolsApiService, } from '@geonetwork-ui/data-access/gn4' @@ -377,3 +378,82 @@ export class OwnerSearchField extends SimpleSearchField { return of([]) } } + +export class TranslatedSearchField extends SimpleSearchField { + protected searchApiService = this.injector.get(SearchApiService) + + // FIXME: this is required to register runtime fields; abstract this as well + protected esService = this.injector.get(ElasticsearchService) + private langService = this.injector.get(LangService) + private esResearchName: string + + constructor( + esFieldName: string, + order: 'asc' | 'desc' = 'asc', + injector: Injector + ) { + super(esFieldName, order, injector) + this.esResearchName = this.esFieldName.substring( + 0, + this.esFieldName.lastIndexOf('.') + ) + } + + getTranslatedAggregations() { + return this.searchApiService + .search( + 'bucket', + JSON.stringify( + this.esService.getSearchRequestBody({ + [this.esFieldName.split('.')[0]]: { + nested: { + path: this.esFieldName.split('.')[0], + }, + aggs: { + default: { + terms: { + field: `${this.esResearchName}.default.keyword`, + exclude: '', + size: 5000, + order: { _key: this.order }, + }, + aggs: { + translation: { + terms: { + size: 50, + exclude: '', + field: `${this.esResearchName}.${this.langService.gnLang}.keyword`, + }, + }, + }, + }, + }, + }, + }) + ) + ) + .pipe( + map( + (response) => + response.aggregations[this.esFieldName.split('.')[0]].default + .buckets + ), + shareReplay() + ) + } + + getAvailableValues(): Observable { + // sort values by alphabetical order + return this.getTranslatedAggregations().pipe( + map((response) => { + return response.map((tmp) => { + const label = tmp.translation.buckets[0]?.key || tmp.key + return { + label: `${label} (${tmp.doc_count})`, + value: tmp.key, + } + }) + }) + ) + } +} From ff26b823274d6863dda9d223399dceeb321f8998 Mon Sep 17 00:00:00 2001 From: Florent Gravin Date: Sat, 2 Dec 2023 16:58:09 +0100 Subject: [PATCH 05/10] chore: add semver dependency to compare semantic version of backend API --- package-lock.json | 319 +++++++++++++++++++++++++++++++++++++++++----- package.json | 1 + 2 files changed, 287 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 159b377f88..a938a8fa4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "pg": "^8.9.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.0.0", + "semver": "^7.5.4", "tippy.js": "^6.3.7", "tslib": "^2.3.0", "typeorm": "^0.3.14", @@ -921,17 +922,6 @@ "@esbuild/win32-x64": "0.18.17" } }, - "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/magic-string": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", @@ -970,25 +960,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@angular-devkit/build-webpack": { "version": "0.1602.0", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.0.tgz", @@ -1278,6 +1249,39 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@angular/common": { "version": "16.1.7", "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.1.7.tgz", @@ -6788,6 +6792,17 @@ "node": ">=8" } }, + "node_modules/@nx/angular/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/angular/node_modules/magic-string": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", @@ -6799,6 +6814,20 @@ "node": ">=12" } }, + "node_modules/@nx/angular/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/angular/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6810,6 +6839,11 @@ "node": ">=8" } }, + "node_modules/@nx/angular/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/@nx/cypress": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-16.6.0.tgz", @@ -6834,6 +6868,36 @@ } } }, + "node_modules/@nx/cypress/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/cypress/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/cypress/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/@nx/devkit": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.6.0.tgz", @@ -6850,6 +6914,36 @@ "nx": ">= 15 <= 17" } }, + "node_modules/@nx/devkit/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/devkit/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/devkit/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/@nx/eslint-plugin": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-16.6.0.tgz", @@ -6992,6 +7086,33 @@ "node": ">=8" } }, + "node_modules/@nx/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/eslint-plugin/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7004,6 +7125,12 @@ "node": ">=8" } }, + "node_modules/@nx/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@nx/jest": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-16.6.0.tgz", @@ -7166,6 +7293,31 @@ "node": ">=8" } }, + "node_modules/@nx/js/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/js/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/js/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7194,6 +7346,11 @@ "node": ">=8" } }, + "node_modules/@nx/js/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/@nx/linter": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/linter/-/linter-16.6.0.tgz", @@ -7304,6 +7461,39 @@ "node": ">=8.6" } }, + "node_modules/@nx/nest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/nest/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/nest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@nx/node": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/node/-/node-16.6.0.tgz", @@ -7487,6 +7677,39 @@ "tslib": "^2.3.0" } }, + "node_modules/@nx/storybook/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/storybook/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/storybook/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@nx/webpack": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-16.6.0.tgz", @@ -25662,6 +25885,31 @@ "node": ">=8" } }, + "node_modules/nx/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nx/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/nx/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -25686,6 +25934,11 @@ "node": ">=8" } }, + "node_modules/nx/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -28733,9 +28986,9 @@ } }, "node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 72a8dc2c56..c569b15385 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "pg": "^8.9.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.0.0", + "semver": "^7.5.4", "tippy.js": "^6.3.7", "tslib": "^2.3.0", "typeorm": "^0.3.14", From d4ac0882fbb41f951ae480a526a5224275d26a09 Mon Sep 17 00:00:00 2001 From: Florent Gravin Date: Sat, 2 Dec 2023 17:00:07 +0100 Subject: [PATCH 06/10] refactor(platform): remove isApiCompatible as the error is thrown from getApiVersion() --- .../lib/gn4/platform/gn4-platform.service.ts | 21 +++++++------------ .../src/lib/platform.service.interface.ts | 1 - 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts index 5e4807ade3..b87eb9ef0f 100644 --- a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +++ b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts @@ -17,7 +17,7 @@ export class Gn4PlatformService implements PlatformServiceInterface { private readonly type = 'GeoNetwork' private me$: Observable private users$: Observable - isAnonymous$: Observable + private isAnonymous$: Observable private settings$ = of(true).pipe( switchMap(() => this.siteApiService.getSiteOrPortalDescription()), @@ -26,18 +26,16 @@ export class Gn4PlatformService implements PlatformServiceInterface { private readonly apiVersion$ = this.settings$.pipe( map((info) => info['system/platform/version'] as string), + tap((version) => { + if (ltr(version, minApiVersion)) { + throw new Error( + `Gn4 API version is not compatible.\nMinimum: ${minApiVersion}\nYour version: ${version}` + ) + } + }), shareReplay(1) ) - private readonly isApiCompatible$ = this.apiVersion$.pipe( - tap( - (version) => - version < minApiVersion && - console.warn(`The GeoNetwork Api version is too low ${version}`) - ), - map((version) => version >= minApiVersion) - ) - constructor( private siteApiService: SiteApiService, private meApi: MeApiService, @@ -62,9 +60,6 @@ export class Gn4PlatformService implements PlatformServiceInterface { getApiVersion(): Observable { return this.apiVersion$ } - isApiCompatible(): Observable { - return this.isApiCompatible$ - } getMe(): Observable { return this.me$ diff --git a/libs/common/domain/src/lib/platform.service.interface.ts b/libs/common/domain/src/lib/platform.service.interface.ts index 7872c2d052..4711d1d047 100644 --- a/libs/common/domain/src/lib/platform.service.interface.ts +++ b/libs/common/domain/src/lib/platform.service.interface.ts @@ -5,7 +5,6 @@ import { Organization } from './model/record/organization.model' export abstract class PlatformServiceInterface { abstract getType(): string abstract getApiVersion(): Observable - abstract isApiCompatible(): Observable abstract getMe(): Observable abstract isAnonymous(): Observable From 5c5aae1fc7236b79f877ecb408d2ab34bf09b80a Mon Sep 17 00:00:00 2001 From: Florent Gravin Date: Sat, 2 Dec 2023 17:04:43 +0100 Subject: [PATCH 07/10] refactor(org): use semver for version comparison --- .../organizations-from-metadata.service.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts index 504412fb37..bea2a42653 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts @@ -28,6 +28,7 @@ import { combineLatest, Observable, of, switchMap, takeLast } from 'rxjs' import { filter, map, shareReplay, startWith, tap } from 'rxjs/operators' import { LangService } from '@geonetwork-ui/util/i18n' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { coerce, satisfies, valid } from 'semver' const IMAGE_URL = '/geonetwork/images/harvesting/' @@ -144,6 +145,7 @@ export class OrganizationsFromMetadataService } private getAggregationSearchRequest(gnVersion: string) { + const semVersion = valid(coerce(gnVersion)) return this.esService.getSearchRequestBody({ contact: { nested: { @@ -152,9 +154,10 @@ export class OrganizationsFromMetadataService aggs: { org: { terms: { - field: gnVersion.startsWith('4.2.2') - ? 'contactForResource.organisation' - : 'contactForResource.organisationObject.default.keyword', + field: + semVersion === '4.2.2' + ? 'contactForResource.organisation' + : 'contactForResource.organisationObject.default.keyword', exclude: '', size: 5000, order: { _key: 'asc' }, @@ -164,12 +167,9 @@ export class OrganizationsFromMetadataService terms: { size: 50, exclude: '', - field: - gnVersion.startsWith('4.2.2') || - gnVersion.startsWith('4.2.3') || - gnVersion.startsWith('4.2.4') - ? 'contactForResource.email.keyword' - : 'contactForResource.email', + field: satisfies(semVersion, '4.2.2 - 4.2.4') + ? 'contactForResource.email.keyword' + : 'contactForResource.email', }, }, logoUrl: { @@ -187,9 +187,10 @@ export class OrganizationsFromMetadataService terms: { size: 5000, exclude: '', - field: gnVersion.startsWith('4.2.2') - ? 'OrgForResource' - : 'OrgForResourceObject.default', + field: + semVersion === '4.2.2' + ? 'OrgForResource' + : 'OrgForResourceObject.default', order: { _key: 'asc', }, From d56f845ff446602b039506ed50135cb104d31f34 Mon Sep 17 00:00:00 2001 From: Florent Gravin Date: Sat, 2 Dec 2023 17:08:27 +0100 Subject: [PATCH 08/10] feat(platform): throw an error is the version is not compatible --- .../lib/gn4/platform/gn4-platform.mapper.ts | 2 +- .../gn4/platform/gn4-platform.service.spec.ts | 33 ++++++++++++++++--- .../lib/gn4/platform/gn4-platform.service.ts | 3 +- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts b/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts index bb34126673..67da958a92 100644 --- a/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts +++ b/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts @@ -37,6 +37,6 @@ export class Gn4PlatformMapper { credentialsNonExpired, ...user } = apiUser - return { ...apiUser, id: id + '' } as UserModel + return { ...apiUser, id: id.toString() } as UserModel } } diff --git a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.spec.ts b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.spec.ts index 4d42efc935..7580f52018 100644 --- a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.spec.ts @@ -5,7 +5,7 @@ import { } from '@geonetwork-ui/data-access/gn4' import { TestBed } from '@angular/core/testing' import { Gn4PlatformService } from './gn4-platform.service' -import { firstValueFrom, lastValueFrom, of, Subject } from 'rxjs' +import { firstValueFrom, of, Subject } from 'rxjs' import { AvatarServiceInterface } from '../auth/avatar.service.interface' import { Gn4PlatformMapper } from './gn4-platform.mapper' @@ -31,6 +31,7 @@ class MeApiMock { getMe() { return this._me$ } + _me$ = new Subject() } @@ -46,6 +47,7 @@ class SiteApiServiceMock { }) ) } + class UsersApiServiceMock { getUsers() { return of([ @@ -98,11 +100,32 @@ describe('Gn4PlatformService', () => { expect(service).toBeTruthy() }) - it('fetches version from settings', async () => { - geonetworkVersion = '4.2.0' - const version = await firstValueFrom(service.getApiVersion()) - expect(version).toEqual('4.2.0') + describe('version', () => { + describe('when version is lower than 4.2.2', () => { + beforeEach(() => { + geonetworkVersion = '4.2.0' + }) + it('throws an error', async () => { + let error + await firstValueFrom(service.getApiVersion()).catch((e) => (error = e)) + expect(error).toEqual( + new Error( + 'Gn4 API version is not compatible.\nMinimum: 4.2.2\nYour version: 4.2.0' + ) + ) + }) + }) + describe('when version is euqal or greater than 4.2.2', () => { + beforeEach(() => { + geonetworkVersion = '4.2.2' + }) + it('fetches version from settings', async () => { + const version = await firstValueFrom(service.getApiVersion()) + expect(version).toEqual('4.2.2') + }) + }) }) + it('fetches users from api', async () => { const users = await firstValueFrom(service.getUsers()) expect(users).toEqual([ diff --git a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts index b87eb9ef0f..0b4cfa1d3e 100644 --- a/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +++ b/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts @@ -10,8 +10,9 @@ import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform. import { UserModel } from '@geonetwork-ui/common/domain/model/user/user.model' import { Organization } from '@geonetwork-ui/common/domain/model/record' import { Gn4PlatformMapper } from './gn4-platform.mapper' +import { ltr } from 'semver' -const minApiVersion = '4.2.0' +const minApiVersion = '4.2.2' @Injectable() export class Gn4PlatformService implements PlatformServiceInterface { private readonly type = 'GeoNetwork' From f909efcb02adffd7add3ff2e2634435e215c8cc0 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Thu, 7 Dec 2023 17:40:16 +0100 Subject: [PATCH 09/10] WIP: Add new ContactField that differs between gn version --- .../src/lib/utils/service/fields.service.ts | 8 +- .../search/src/lib/utils/service/fields.ts | 101 +++++++++++++----- 2 files changed, 74 insertions(+), 35 deletions(-) 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 7212f4b82f..a329078272 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -1,6 +1,7 @@ import { Injectable, Injector } from '@angular/core' import { AbstractSearchField, + ContactField, FieldValue, FullTextSearchField, GnUiTranslationSearchField, @@ -10,7 +11,6 @@ import { OwnerSearchField, SimpleSearchField, ThesaurusTranslationSearchField, - TranslatedSearchField, } from './fields' import { forkJoin, Observable, of } from 'rxjs' import { map } from 'rxjs/operators' @@ -70,11 +70,7 @@ export class FieldsService { q: new FullTextSearchField(), license: new LicenseSearchField(this.injector), owner: new OwnerSearchField(this.injector), - contact: new TranslatedSearchField( - 'contactForResource.organisationObject.default', - 'asc', - this.injector - ), + contact: new ContactField('asc', this.injector), // new TranslationField('OrgForResource') } as Record get supportedFields() { diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index 0c640cb079..c2c267f526 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -19,6 +19,8 @@ import { import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { ElasticsearchService } from '@geonetwork-ui/api/repository/gn4' import { LangService } from '@geonetwork-ui/util/i18n' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { coerce, valid } from 'semver' export type FieldValue = string | number export interface FieldAvailableValue { @@ -74,13 +76,17 @@ export class SimpleSearchField implements AbstractSearchField { }) ) } - getFiltersForValues(values: FieldValue[]): Observable { + getFiltersForValues( + values: FieldValue[], + esFieldName: string = this.esFieldName + ): Observable { return of({ - [this.esFieldName]: values.reduce((acc, val) => { + [esFieldName]: values.reduce((acc, val) => { return { ...acc, [val.toString()]: true } }, {}), }) } + getValuesForFilter(filters: FieldFilters): Observable { const filter = filters[this.esFieldName] if (!filter) return of([]) @@ -379,25 +385,58 @@ export class OwnerSearchField extends SimpleSearchField { } } -export class TranslatedSearchField extends SimpleSearchField { +// ResourceContactField +// contact of the data not metadata +// data is the resource +export class ContactField extends SimpleSearchField { + protected repository = this.injector.get(RecordsRepositoryInterface) protected searchApiService = this.injector.get(SearchApiService) // FIXME: this is required to register runtime fields; abstract this as well protected esService = this.injector.get(ElasticsearchService) private langService = this.injector.get(LangService) + private platformService = this.injector.get(PlatformServiceInterface) + public esFieldName: string private esResearchName: string + private version: Observable = this.platformService + .getApiVersion() + .pipe(map((version) => version)) - constructor( - esFieldName: string, - order: 'asc' | 'desc' = 'asc', - injector: Injector - ) { - super(esFieldName, order, injector) - this.esResearchName = this.esFieldName.substring( - 0, - this.esFieldName.lastIndexOf('.') + constructor(public order: 'asc' | 'desc' = 'asc', public injector: Injector) { + super('OrgForResource', order, injector) + this.esFieldName = 'contactForResource.organisationObject.default.keyword' + } + + getFiltersForValues(values: FieldValue[]): Observable { + return this.version.pipe( + switchMap((version) => + version == '4.2.2' + ? super.getFiltersForValues(values) + : super.getFiltersForValues(values, this.esFieldName) + ) ) } + getValuesForFilter(filters: FieldFilters): Observable { + return this.version.pipe( + switchMap((version) => + version == '4.2.2' + ? super.getValuesForFilter(filters) + : this.tmpFunction(filters) + ) + ) + } + + tmpFunction(filters: FieldFilters) { + const filter = filters[this.esFieldName] + console.log(filters) + if (!filter) return of([]) + const values = + typeof filter === 'string' + ? [filter] + : Object.keys(filter).filter((v) => filter[v]) + console.log(values) + return of(values) + } getTranslatedAggregations() { return this.searchApiService @@ -405,14 +444,14 @@ export class TranslatedSearchField extends SimpleSearchField { 'bucket', JSON.stringify( this.esService.getSearchRequestBody({ - [this.esFieldName.split('.')[0]]: { + contactForResource: { nested: { - path: this.esFieldName.split('.')[0], + path: 'contactForResource', }, aggs: { default: { terms: { - field: `${this.esResearchName}.default.keyword`, + field: `contactForResource.organisationObject.default.keyword`, exclude: '', size: 5000, order: { _key: this.order }, @@ -422,7 +461,7 @@ export class TranslatedSearchField extends SimpleSearchField { terms: { size: 50, exclude: '', - field: `${this.esResearchName}.${this.langService.gnLang}.keyword`, + field: `contactForResource.organisationObject.${this.langService.gnLang}.keyword`, }, }, }, @@ -434,9 +473,7 @@ export class TranslatedSearchField extends SimpleSearchField { ) .pipe( map( - (response) => - response.aggregations[this.esFieldName.split('.')[0]].default - .buckets + (response) => response.aggregations.contactForResource.default.buckets ), shareReplay() ) @@ -444,16 +481,22 @@ export class TranslatedSearchField extends SimpleSearchField { getAvailableValues(): Observable { // sort values by alphabetical order - return this.getTranslatedAggregations().pipe( - map((response) => { - return response.map((tmp) => { - const label = tmp.translation.buckets[0]?.key || tmp.key - return { - label: `${label} (${tmp.doc_count})`, - value: tmp.key, - } - }) - }) + return this.version.pipe( + switchMap((version) => + version == '4.2.2' + ? super.getAvailableValues() //TODO copy from simplesearch field + : this.getTranslatedAggregations().pipe( + map((response) => + response.map((tmp) => { + const label = tmp.translation.buckets[0]?.key || tmp.key + return { + label: `${label} (${tmp.doc_count})`, + value: tmp.key, + } + }) + ) + ) + ) ) } } From ed85f7527387ed9d51c967c43a814882412caaaa Mon Sep 17 00:00:00 2001 From: Florian Necas Date: Thu, 7 Dec 2023 18:39:54 +0100 Subject: [PATCH 10/10] feat: update dump, remove log and fix filter --- .../search/src/lib/utils/service/fields.ts | 27 ++++++------------ .../docker-entrypoint-initdb.d/dump | Bin 415147 -> 414602 bytes 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index c2c267f526..2f3c272367 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -20,7 +20,6 @@ import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/reposit import { ElasticsearchService } from '@geonetwork-ui/api/repository/gn4' import { LangService } from '@geonetwork-ui/util/i18n' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' -import { coerce, valid } from 'semver' export type FieldValue = string | number export interface FieldAvailableValue { @@ -87,8 +86,11 @@ export class SimpleSearchField implements AbstractSearchField { }) } - getValuesForFilter(filters: FieldFilters): Observable { - const filter = filters[this.esFieldName] + getValuesForFilter( + filters: FieldFilters, + esFieldName: string = this.esFieldName + ): Observable { + const filter = filters[esFieldName] if (!filter) return of([]) const values = typeof filter === 'string' @@ -397,14 +399,13 @@ export class ContactField extends SimpleSearchField { private langService = this.injector.get(LangService) private platformService = this.injector.get(PlatformServiceInterface) public esFieldName: string - private esResearchName: string private version: Observable = this.platformService .getApiVersion() .pipe(map((version) => version)) constructor(public order: 'asc' | 'desc' = 'asc', public injector: Injector) { super('OrgForResource', order, injector) - this.esFieldName = 'contactForResource.organisationObject.default.keyword' + this.esFieldName = 'OrgForResourceObject.default' } getFiltersForValues(values: FieldValue[]): Observable { @@ -421,23 +422,11 @@ export class ContactField extends SimpleSearchField { switchMap((version) => version == '4.2.2' ? super.getValuesForFilter(filters) - : this.tmpFunction(filters) + : super.getValuesForFilter(filters, this.esFieldName) ) ) } - tmpFunction(filters: FieldFilters) { - const filter = filters[this.esFieldName] - console.log(filters) - if (!filter) return of([]) - const values = - typeof filter === 'string' - ? [filter] - : Object.keys(filter).filter((v) => filter[v]) - console.log(values) - return of(values) - } - getTranslatedAggregations() { return this.searchApiService .search( @@ -484,7 +473,7 @@ export class ContactField extends SimpleSearchField { return this.version.pipe( switchMap((version) => version == '4.2.2' - ? super.getAvailableValues() //TODO copy from simplesearch field + ? super.getAvailableValues() : this.getTranslatedAggregations().pipe( map((response) => response.map((tmp) => { diff --git a/support-services/docker-entrypoint-initdb.d/dump b/support-services/docker-entrypoint-initdb.d/dump index 6a86fefc3d016a0975749cebca0c8f3b4679402e..66d62e47a3a6fab664bfd2c65788fb1ddede67a7 100644 GIT binary patch delta 10098 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