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..284fa7e349 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,6 +118,18 @@ 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') 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.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/translations/de.json b/translations/de.json index 0095630ab3..cfba8a822e 100644 --- a/translations/de.json +++ b/translations/de.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Katalog", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Meine Entwürfe", - "dashboard.records.myLibrary": "Meine Bibliothek", "dashboard.records.myRecords": "Meine Datensätze", - "dashboard.records.publishedRecords": "{count, plural, =1{veröffentlichter Datensatz} other{veröffentlichte Datensätze}}", "dashboard.records.search": "Suche nach \"{searchText}\"", "dashboard.records.templates": "Vorlagen", "dashboard.records.userDetail": "Name", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "Fehler beim Veröffentlichen des Datensatzes", "editor.record.publishSuccess.body": "Der Datensatz wurde erfolgreich veröffentlicht!", "editor.record.publishSuccess.title": "Veröffentlichung erfolgreich", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "", diff --git a/translations/en.json b/translations/en.json index 1c0638f91d..30c1e32c36 100644 --- a/translations/en.json +++ b/translations/en.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Metadata records", "dashboard.records.hasDraft": "draft", "dashboard.records.myDraft": "My drafts", - "dashboard.records.myLibrary": "My library", "dashboard.records.myRecords": "My Records", - "dashboard.records.publishedRecords": "{count, plural, =1{published record} other{published records}}", "dashboard.records.search": "Search for \"{searchText}\"", "dashboard.records.templates": "Templates", "dashboard.records.userDetail": "Name", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "Error publishing record", "editor.record.publishSuccess.body": "The record was successfully published!", "editor.record.publishSuccess.title": "Publish success", + "editor.record.publishVersionError.body": "The record cannot be published because an incompatible GeoNetwork version was detected. Please contact the administrator of the platform to solve this issue.\nCurrent version: {currentVersion}\nMinimum compatible version: 4.2.5", + "editor.record.publishVersionError.closeMessage": "Understood", + "editor.record.publishVersionError.title": "A critical issue was encountered", "editor.record.resourceError.body": "There was an issue with the record attachments:", "editor.record.resourceError.closeMessage": "Understood", "editor.record.resourceError.title": "Error with the record attachments", diff --git a/translations/es.json b/translations/es.json index c0a6c50667..41796cba82 100644 --- a/translations/es.json +++ b/translations/es.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Catálogo", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Mis borradores", - "dashboard.records.myLibrary": "Mi biblioteca", "dashboard.records.myRecords": "Mis Registros", - "dashboard.records.publishedRecords": "", "dashboard.records.search": "Buscar \"{searchText}\"", "dashboard.records.templates": "", "dashboard.records.userDetail": "", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "", "editor.record.publishSuccess.body": "", "editor.record.publishSuccess.title": "", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "", diff --git a/translations/fr.json b/translations/fr.json index 1198b1c383..c71c34dfb0 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Catalogue", "dashboard.records.hasDraft": "brouillon", "dashboard.records.myDraft": "Mes brouillons", - "dashboard.records.myLibrary": "Ma bibliothèque", "dashboard.records.myRecords": "Mes fiches publiées", - "dashboard.records.publishedRecords": "{count, plural, =1{donnée publiée} other{données publiées}}", "dashboard.records.search": "Résultats pour \"{searchText}\"", "dashboard.records.templates": "Modèles pré-remplis", "dashboard.records.userDetail": "Nom", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "Erreur lors de la publication", "editor.record.publishSuccess.body": "La fiche a bien été publiée !", "editor.record.publishSuccess.title": "Publication réussie", + "editor.record.publishVersionError.body": "La fiche ne peut pas être publiée car une version incompatible de GeoNetwork a été détectée. Veuillez contacter un administrateur de la plateforme pour résoudre le problème.\nVersion actuelle : {currentVersion}\nVersion minimum compatible : 4.2.5", + "editor.record.publishVersionError.closeMessage": "Compris", + "editor.record.publishVersionError.title": "Une erreur critique est survenue", "editor.record.resourceError.body": "Une erreur est survenue lors de la gestion des pièces jointes :", "editor.record.resourceError.closeMessage": "Compris", "editor.record.resourceError.title": "Erreur avec les pièces jointes", diff --git a/translations/it.json b/translations/it.json index a5c3b93f39..3e76378bf4 100644 --- a/translations/it.json +++ b/translations/it.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Catalogo", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Le mie bozze", - "dashboard.records.myLibrary": "La mia biblioteca", "dashboard.records.myRecords": "I miei dati", - "dashboard.records.publishedRecords": "dati pubblicati", "dashboard.records.search": "Risultati per \"{searchText}\"", "dashboard.records.templates": "", "dashboard.records.userDetail": "Nome", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "", "editor.record.publishSuccess.body": "", "editor.record.publishSuccess.title": "", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "", diff --git a/translations/nl.json b/translations/nl.json index 3f8cf3fb7c..295e085b2d 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Catalogus", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Mijn concepten", - "dashboard.records.myLibrary": "Mijn bibliotheek", "dashboard.records.myRecords": "Mijn Records", - "dashboard.records.publishedRecords": "", "dashboard.records.search": "Zoeken naar \"{searchText}\"", "dashboard.records.templates": "", "dashboard.records.userDetail": "", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "", "editor.record.publishSuccess.body": "", "editor.record.publishSuccess.title": "", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "", diff --git a/translations/pt.json b/translations/pt.json index 89d4ddc0c3..379707d432 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Catálogo", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Meus rascunhos", - "dashboard.records.myLibrary": "Minha biblioteca", "dashboard.records.myRecords": "Meus Registros", - "dashboard.records.publishedRecords": "", "dashboard.records.search": "Buscar por \"{searchText}\"", "dashboard.records.templates": "", "dashboard.records.userDetail": "", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "", "editor.record.publishSuccess.body": "", "editor.record.publishSuccess.title": "", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "", diff --git a/translations/sk.json b/translations/sk.json index 7af8363d38..bed0575d39 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -36,9 +36,7 @@ "dashboard.records.all": "Katalóg", "dashboard.records.hasDraft": "", "dashboard.records.myDraft": "Moje koncepty", - "dashboard.records.myLibrary": "Moja knižnica", "dashboard.records.myRecords": "Moje záznamy", - "dashboard.records.publishedRecords": "{count, plural, =1{zverejnený záznam} other{zverejnených záznamov}}", "dashboard.records.search": "Hľadať \"{searchText}\"", "dashboard.records.templates": "", "dashboard.records.userDetail": "Meno", @@ -282,6 +280,9 @@ "editor.record.publishError.title": "", "editor.record.publishSuccess.body": "", "editor.record.publishSuccess.title": "", + "editor.record.publishVersionError.body": "", + "editor.record.publishVersionError.closeMessage": "", + "editor.record.publishVersionError.title": "", "editor.record.resourceError.body": "", "editor.record.resourceError.closeMessage": "", "editor.record.resourceError.title": "",