diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts index 39015be36c..1885913f8e 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts @@ -13,6 +13,7 @@ import { TranslateModule } from '@ngx-translate/core' import { HttpClientModule } from '@angular/common/http' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { PageSelectorComponent } from './components/page-selector/page-selector.component' +import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error' const getRoute = () => ({ snapshot: { @@ -117,9 +118,21 @@ describe('EditPageComponent', () => { beforeEach(() => { fixture.detectChanges() }) + describe('publish version error', () => { + it('shows notification', () => { + ;(facade.saveError$ as any).next(new PublicationVersionError('1.0.0')) + expect(notificationsService.showNotification).toHaveBeenCalledWith({ + type: 'error', + title: 'editor.record.publishVersionError.title', + text: 'editor.record.publishVersionError.body', + closeMessage: 'editor.record.publishVersionError.closeMessage', + }) + }) + }) + describe('publish error', () => { it('shows notification', () => { - ;(facade.saveError$ as any).next('oopsie') + ;(facade.saveError$ as any).next(new Error('oopsie')) expect(notificationsService.showNotification).toHaveBeenCalledWith({ type: 'error', title: 'editor.record.publishError.title', diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.ts b/apps/metadata-editor/src/app/edit/edit-page.component.ts index c6d76d4c6f..fe630e18ba 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -6,25 +6,26 @@ import { OnInit, ViewChild, } from '@angular/core' +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner' import { ActivatedRoute, Router } from '@angular/router' +import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error' import { EditorFacade, RecordFormComponent, } from '@geonetwork-ui/feature/editor' -import { ButtonComponent } from '@geonetwork-ui/ui/inputs' -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner' -import { PublishButtonComponent } from './components/publish-button/publish-button.component' -import { TopToolbarComponent } from './components/top-toolbar/top-toolbar.component' import { NotificationsContainerComponent, NotificationsService, } from '@geonetwork-ui/feature/notifications' +import { ButtonComponent } from '@geonetwork-ui/ui/inputs' import { TranslateModule, TranslateService } from '@ngx-translate/core' import { combineLatest, filter, firstValueFrom, Subscription, take } from 'rxjs' -import { PageSelectorComponent } from './components/page-selector/page-selector.component' -import { marker } from '@biesbjerg/ngx-translate-extract-marker' import { map } from 'rxjs/operators' import { SidebarComponent } from '../dashboard/sidebar/sidebar.component' +import { PageSelectorComponent } from './components/page-selector/page-selector.component' +import { PublishButtonComponent } from './components/publish-button/publish-button.component' +import { TopToolbarComponent } from './components/top-toolbar/top-toolbar.component' marker('editor.record.form.bottomButtons.comeBackLater') marker('editor.record.form.bottomButtons.previous') @@ -81,18 +82,34 @@ export class EditPageComponent implements OnInit, OnDestroy { this.subscription.add( this.facade.saveError$.subscribe((error) => { - this.notificationsService.showNotification({ - type: 'error', - title: this.translateService.instant( - 'editor.record.publishError.title' - ), - text: `${this.translateService.instant( - 'editor.record.publishError.body' - )} ${error}`, - closeMessage: this.translateService.instant( - 'editor.record.publishError.closeMessage' - ), - }) + if (error instanceof PublicationVersionError) { + this.notificationsService.showNotification({ + type: 'error', + title: this.translateService.instant( + 'editor.record.publishVersionError.title' + ), + text: this.translateService.instant( + 'editor.record.publishVersionError.body', + { currentVersion: error.detectedApiVersion } + ), + closeMessage: this.translateService.instant( + 'editor.record.publishVersionError.closeMessage' + ), + }) + } else { + this.notificationsService.showNotification({ + type: 'error', + title: this.translateService.instant( + 'editor.record.publishError.title' + ), + text: `${this.translateService.instant( + 'editor.record.publishError.body' + )} ${error.message}`, + closeMessage: this.translateService.instant( + 'editor.record.publishError.closeMessage' + ), + }) + } }) ) diff --git a/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts b/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts index d80b6381fa..9160184bf4 100644 --- a/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts +++ b/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts @@ -26,6 +26,8 @@ import { HttpClientTestingModule, HttpTestingController, } from '@angular/common/http/testing' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error' class Gn4MetadataMapperMock { readRecords = jest.fn((records) => @@ -91,11 +93,16 @@ class RecordsApiServiceMock { deleteRecord = jest.fn(() => of({})) } +class PlatformServiceInterfaceMock { + getApiVersion = jest.fn(() => of('4.2.5')) +} + describe('Gn4Repository', () => { let repository: Gn4Repository let gn4Helper: ElasticsearchService let gn4SearchApi: SearchApiService let gn4RecordsApi: RecordsApiService + let platformService: PlatformServiceInterface let httpTestingController: HttpTestingController beforeEach(() => { @@ -119,12 +126,17 @@ describe('Gn4Repository', () => { provide: Gn4Converter, useClass: Gn4MetadataMapperMock, }, + { + provide: PlatformServiceInterface, + useClass: PlatformServiceInterfaceMock, + }, ], }) repository = TestBed.inject(Gn4Repository) gn4Helper = TestBed.inject(ElasticsearchService) gn4SearchApi = TestBed.inject(SearchApiService) gn4RecordsApi = TestBed.inject(RecordsApiService) + platformService = TestBed.inject(PlatformServiceInterface) httpTestingController = TestBed.inject(HttpTestingController) }) @@ -392,6 +404,21 @@ describe('Gn4Repository', () => { // note: we're using a simple record here otherwise there might be loss of information when converting describe('saveRecord', () => { let recordSource: string + describe('version error', () => { + it('throws an error if the publication API version is too low', async () => { + ;(platformService.getApiVersion as jest.Mock).mockReturnValueOnce( + of('4.2.4') + ) + let error + await lastValueFrom( + repository.saveRecord( + simpleDatasetRecordFixture(), + simpleDatasetRecordAsXmlFixture() + ) + ).catch((e) => (error = e)) + expect(error).toEqual(new PublicationVersionError('4.2.4')) + }) + }) describe('with reference', () => { beforeEach(async () => { recordSource = await lastValueFrom( diff --git a/libs/api/repository/src/lib/gn4/gn4-repository.ts b/libs/api/repository/src/lib/gn4/gn4-repository.ts index 14046dc246..64068b7f02 100644 --- a/libs/api/repository/src/lib/gn4/gn4-repository.ts +++ b/libs/api/repository/src/lib/gn4/gn4-repository.ts @@ -1,9 +1,33 @@ +import { + HttpClient, + HttpErrorResponse, + HttpHeaders, +} from '@angular/common/http' import { Injectable } from '@angular/core' +import { + assertValidXml, + findConverterForDocument, + Gn4Converter, + Gn4SearchResults, + Iso19139Converter, +} from '@geonetwork-ui/api/metadata-converter' +import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' +import { + Aggregations, + AggregationsParams, + FieldFilters, +} from '@geonetwork-ui/common/domain/model/search' +import { + SearchParams, + SearchResults, +} from '@geonetwork-ui/common/domain/model/search/search.model' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { RecordsApiService, SearchApiService, } from '@geonetwork-ui/data-access/gn4' -import { ElasticsearchService } from './elasticsearch' import { combineLatest, exhaustMap, @@ -14,30 +38,11 @@ import { switchMap, throwError, } from 'rxjs' -import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' -import { - SearchParams, - SearchResults, -} from '@geonetwork-ui/common/domain/model/search/search.model' -import { - Aggregations, - AggregationsParams, - FieldFilters, -} from '@geonetwork-ui/common/domain/model/search' import { catchError, map, tap } from 'rxjs/operators' -import { - assertValidXml, - findConverterForDocument, - Gn4Converter, - Gn4SearchResults, - Iso19139Converter, -} from '@geonetwork-ui/api/metadata-converter' -import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' -import { - HttpClient, - HttpErrorResponse, - HttpHeaders, -} from '@angular/common/http' +import { lt } from 'semver' +import { ElasticsearchService } from './elasticsearch' + +const minPublicationApiVersion = '4.2.5' const TEMPORARY_ID_PREFIX = 'TEMP-ID-' @@ -53,7 +58,8 @@ export class Gn4Repository implements RecordsRepositoryInterface { private gn4SearchApi: SearchApiService, private gn4SearchHelper: ElasticsearchService, private gn4Mapper: Gn4Converter, - private gn4RecordsApi: RecordsApiService + private gn4RecordsApi: RecordsApiService, + private platformService: PlatformServiceInterface ) {} search({ @@ -242,33 +248,36 @@ export class Gn4Repository implements RecordsRepositoryInterface { record: CatalogRecord, referenceRecordSource?: string ): Observable { - return this.serializeRecordToXml(record, referenceRecordSource).pipe( + return this.platformService.getApiVersion().pipe( + map((version) => { + if (lt(version, minPublicationApiVersion)) { + throw new PublicationVersionError(version) + } + }), + switchMap(() => this.serializeRecordToXml(record, referenceRecordSource)), switchMap((recordXml) => - this.gn4RecordsApi - .insert( - 'METADATA', - undefined, - undefined, - undefined, - true, - undefined, - 'OVERWRITE', - undefined, - undefined, - undefined, - '_none_', - undefined, - undefined, - undefined, - recordXml - ) - .pipe( - map((response) => { - const metadataId = Object.keys(response.metadataInfos)[0] - return response.metadataInfos[metadataId][0].uuid - }) - ) - ) + this.gn4RecordsApi.insert( + 'METADATA', + undefined, + undefined, + undefined, + true, + undefined, + 'OVERWRITE', + undefined, + undefined, + undefined, + '_none_', + undefined, + undefined, + undefined, + recordXml + ) + ), + map((response) => { + const metadataId = Object.keys(response.metadataInfos)[0] + return response.metadataInfos[metadataId][0].uuid + }) ) } diff --git a/libs/common/domain/src/lib/model/error/index.ts b/libs/common/domain/src/lib/model/error/index.ts new file mode 100644 index 0000000000..082d315249 --- /dev/null +++ b/libs/common/domain/src/lib/model/error/index.ts @@ -0,0 +1 @@ +export * from './publication-version.error' diff --git a/libs/common/domain/src/lib/model/error/publication-version.error.ts b/libs/common/domain/src/lib/model/error/publication-version.error.ts new file mode 100644 index 0000000000..f9500c08b4 --- /dev/null +++ b/libs/common/domain/src/lib/model/error/publication-version.error.ts @@ -0,0 +1,9 @@ +export class PublicationVersionError extends Error { + detectedApiVersion: string + + constructor(detectedApiVersion: string) { + super() + this.name = 'PublicationVersionError' + this.detectedApiVersion = detectedApiVersion + } +} diff --git a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts index deaf617dd3..5ee8316314 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts @@ -118,7 +118,7 @@ describe('EditorEffects', () => { a: EditorActions.saveRecord(), }) const expected = hot('-a-|', { - a: EditorActions.saveRecordFailure({ error: 'oopsie' }), + a: EditorActions.saveRecordFailure({ error: new Error('oopsie') }), }) expect(effects.saveRecord$).toBeObservable(expected) }) diff --git a/libs/feature/editor/src/lib/+state/editor.effects.ts b/libs/feature/editor/src/lib/+state/editor.effects.ts index 0facb33c38..cb678bf6df 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.ts @@ -48,7 +48,7 @@ export class EditorEffects { catchError((error) => of( EditorActions.saveRecordFailure({ - error: error.message, + error, }) ) ) diff --git a/libs/feature/editor/src/lib/+state/editor.models.ts b/libs/feature/editor/src/lib/+state/editor.models.ts index e59136767b..a1720d03e8 100644 --- a/libs/feature/editor/src/lib/+state/editor.models.ts +++ b/libs/feature/editor/src/lib/+state/editor.models.ts @@ -1,6 +1,6 @@ import { EditorField, EditorFieldValue, EditorSection } from '../models' -export type SaveRecordError = string +export type SaveRecordError = Error export interface EditorFieldWithValue { config: EditorField diff --git a/libs/ui/elements/src/lib/notification/notification.component.html b/libs/ui/elements/src/lib/notification/notification.component.html index a6e847279b..d10958bc02 100644 --- a/libs/ui/elements/src/lib/notification/notification.component.html +++ b/libs/ui/elements/src/lib/notification/notification.component.html @@ -25,9 +25,7 @@
{{ title }}
-
- {{ text }} -
+
{{ text }}