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) => {