From c7618f44dfcee2d6b290ee9edac1d614661dd5ba Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 25 Sep 2024 14:39:25 +0200 Subject: [PATCH 1/2] feat(me): Added an effect cleanRecordAttachments on saveRecordSuccess action that delete all ressources that are not used in the record. --- .../gn4/platform/gn4-platform.service.spec.ts | 24 +++++++++ .../lib/gn4/platform/gn4-platform.service.ts | 50 +++++++++++++++++++ .../src/lib/platform.service.interface.ts | 3 +- .../src/lib/+state/editor.effects.spec.ts | 15 ++++++ .../editor/src/lib/+state/editor.effects.ts | 26 +++++++++- 5 files changed, 116 insertions(+), 2 deletions(-) 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 8b76b0452a..d613071672 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 @@ -15,11 +15,13 @@ import { AvatarServiceInterface } from '../auth/avatar.service.interface' import { Gn4PlatformMapper } from './gn4-platform.mapper' import { LangService } from '@geonetwork-ui/util/i18n' import { + datasetRecordsFixture, someUserFeedbacksFixture, userFeedbackFixture, } from '@geonetwork-ui/common/fixtures' import { HttpClientTestingModule } from '@angular/common/http/testing' import { HttpClient, HttpEventType } from '@angular/common/http' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' let geonetworkVersion: string @@ -171,6 +173,11 @@ class LangServiceMock { iso3 = 'fre' } +const associatedResources = { + onlines: [], + thumbnails: [], +} + class RecordsApiServiceMock { getAllResources = jest.fn(() => of([ @@ -184,6 +191,8 @@ class RecordsApiServiceMock { }, ]) ) + getAssociatedResources = jest.fn(() => of(associatedResources)) + delResource = jest.fn(() => of(undefined)) putResource = jest.fn(() => of({ type: HttpEventType.UploadProgress, @@ -769,6 +778,21 @@ describe('Gn4PlatformService', () => { }) }) + describe('cleanRecordAttachments', () => { + it('calls api service', async () => { + const record = datasetRecordsFixture() as unknown as CatalogRecord + + service.cleanRecordAttachments(record) + + expect(recordsApiService.getAssociatedResources).toHaveBeenCalledWith( + record.uniqueIdentifier + ) + expect(recordsApiService.getAllResources).toHaveBeenCalledWith( + record.uniqueIdentifier + ) + }) + }) + describe('attachFileToRecord', () => { let file: File beforeEach(() => { 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 24f3ef0085..6c86f81001 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 @@ -1,5 +1,13 @@ import { Injectable } from '@angular/core' import { combineLatest, Observable, of, switchMap, throwError } from 'rxjs' +import { + combineLatest, + forkJoin, + mergeMap, + Observable, + of, + switchMap, +} from 'rxjs' import { catchError, filter, map, shareReplay, tap } from 'rxjs/operators' import { MeApiService, @@ -16,6 +24,7 @@ import { } from '@geonetwork-ui/common/domain/platform.service.interface' import { UserModel } from '@geonetwork-ui/common/domain/model/user/user.model' import { + CatalogRecord, Keyword, Organization, UserFeedback, @@ -292,6 +301,47 @@ export class Gn4PlatformService implements PlatformServiceInterface { ) } + cleanRecordAttachments(record: CatalogRecord): Observable { + return combineLatest([ + this.recordsApiService.getAssociatedResources(record.uniqueIdentifier), + this.recordsApiService.getAllResources(record.uniqueIdentifier), + ]).pipe( + map(([associatedResources, recordResources]) => { + // Received object from API is not a RelatedResponseApiModel, so we need + // to cast it as any and do the bellow mappings to get the wanted values. + const resourceIdsToKeep = [ + ...((associatedResources as any).onlines ?? []) + .map((o) => o.title) + .map((o) => o['']), + ...((associatedResources as any).thumbnails ?? []) + .map((o) => o.title) + .map((o) => o['']), + ] + + const resourceIdsToRemove = recordResources + .map((r) => r.filename) + .filter((resourceId) => !resourceIdsToKeep.includes(resourceId)) + + return resourceIdsToRemove + }), + mergeMap((resourceIdsToRemove) => + forkJoin( + resourceIdsToRemove.map((attachementId) => + this.recordsApiService.delResource( + record.uniqueIdentifier, + attachementId + ) + ) + ).pipe(map(() => undefined)) + ), + catchError((error) => { + console.error('Error while cleaning attachments:', error) + throw error + }) + ) + } + + attachFileToRecord(recordUuid: string, file: File) { attachFileToRecord(recordUuid: string, file: File): Observable { let sizeBytes = -1 diff --git a/libs/common/domain/src/lib/platform.service.interface.ts b/libs/common/domain/src/lib/platform.service.interface.ts index 008c0f24bc..72f4b1741e 100644 --- a/libs/common/domain/src/lib/platform.service.interface.ts +++ b/libs/common/domain/src/lib/platform.service.interface.ts @@ -1,7 +1,7 @@ import type { Observable } from 'rxjs' import type { UserModel } from './model/user/user.model' import type { Organization } from './model/record/organization.model' -import { Keyword, UserFeedback } from './model/record' +import { CatalogRecord, Keyword, UserFeedback } from './model/record' import { KeywordType } from './model/thesaurus' export interface RecordAttachment { @@ -49,6 +49,7 @@ export abstract class PlatformServiceInterface { abstract getRecordAttachments( recordUuid: string ): Observable + abstract cleanRecordAttachments(recordUuid: CatalogRecord): Observable abstract attachFileToRecord( recordUuid: string, file: File 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 c32c91af4a..b021b49b20 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts @@ -10,6 +10,8 @@ import { datasetRecordsFixture } from '@geonetwork-ui/common/fixtures' import { EditorService } from '../services/editor.service' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { EditorPartialState } from './editor.reducer' +import { MockProvider } from 'ng-mocks' +import { Gn4PlatformService } from '@geonetwork-ui/api/repository' class EditorServiceMock { saveRecord = jest.fn((record) => of([record, 'blabla'])) @@ -54,6 +56,9 @@ describe('EditorEffects', () => { provide: RecordsRepositoryInterface, useClass: RecordsRepositoryMock, }, + MockProvider(Gn4PlatformService, { + cleanRecordAttachments: jest.fn(() => of(undefined)), + }), ], }) @@ -167,6 +172,16 @@ describe('EditorEffects', () => { }) }) + describe('cleanRecordAttachments$', () => { + it('dispatch markRecordAsChanged', () => { + actions = hot('-a-|', { + a: EditorActions.saveRecordSuccess(), + }) + const expected = hot('---|') + expect(effects.cleanRecordAttachments$).toBeObservable(expected) + }) + }) + describe('checkHasChangesOnOpen$', () => { describe('if the record has a draft', () => { it('dispatch markRecordAsChanged', () => { diff --git a/libs/feature/editor/src/lib/+state/editor.effects.ts b/libs/feature/editor/src/lib/+state/editor.effects.ts index bde47e4b4f..0facb33c38 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core' import { Actions, createEffect, ofType } from '@ngrx/effects' -import { debounceTime, filter, of, withLatestFrom } from 'rxjs' +import { debounceTime, EMPTY, filter, of, withLatestFrom } from 'rxjs' import { catchError, map, switchMap } from 'rxjs/operators' import * as EditorActions from './editor.actions' import { EditorService } from '../services/editor.service' @@ -12,12 +12,14 @@ import { selectRecordSource, } from './editor.selectors' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' +import { Gn4PlatformService } from '@geonetwork-ui/api/repository' @Injectable() export class EditorEffects { private actions$ = inject(Actions) private editorService = inject(EditorService) private recordsRepository = inject(RecordsRepositoryInterface) + private gn4PlateformService = inject(Gn4PlatformService) private store = inject(Store) saveRecord$ = createEffect(() => @@ -55,6 +57,28 @@ export class EditorEffects { ) ) + cleanRecordAttachments$ = createEffect( + () => + this.actions$.pipe( + ofType(EditorActions.saveRecordSuccess), + withLatestFrom(this.store.select(selectRecord)), + switchMap(([_, record]) => { + this.gn4PlateformService.cleanRecordAttachments(record).subscribe({ + next: (_) => undefined, + error: (err) => { + console.error(err) + }, + }) + return EMPTY + }), + catchError((error) => { + console.error(error) + return EMPTY + }) + ), + { dispatch: false } + ) + markAsChanged$ = createEffect(() => this.actions$.pipe( ofType(EditorActions.updateRecordField), From 4ee2cf9f8db63f26590ca2c153551a6017b56408 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Mon, 30 Sep 2024 17:19:36 +0200 Subject: [PATCH 2/2] fix test --- .../lib/gn4/platform/gn4-platform.service.ts | 23 +++++++++++-------- .../src/lib/+state/editor.effects.spec.ts | 10 -------- 2 files changed, 14 insertions(+), 19 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 6c86f81001..d187ed8d01 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 @@ -1,14 +1,12 @@ import { Injectable } from '@angular/core' -import { combineLatest, Observable, of, switchMap, throwError } from 'rxjs' import { - combineLatest, - forkJoin, + catchError, + filter, + map, mergeMap, - Observable, - of, - switchMap, -} from 'rxjs' -import { catchError, filter, map, shareReplay, tap } from 'rxjs/operators' + shareReplay, + tap, +} from 'rxjs/operators' import { MeApiService, RecordsApiService, @@ -39,6 +37,14 @@ import { } from '@geonetwork-ui/api/metadata-converter' import { KeywordType } from '@geonetwork-ui/common/domain/model/thesaurus' import { noDuplicateFileName } from '@geonetwork-ui/util/shared' +import { + combineLatest, + forkJoin, + Observable, + of, + switchMap, + throwError, +} from 'rxjs' const minApiVersion = '4.2.2' @@ -341,7 +347,6 @@ export class Gn4PlatformService implements PlatformServiceInterface { ) } - attachFileToRecord(recordUuid: string, file: File) { attachFileToRecord(recordUuid: string, file: File): Observable { let sizeBytes = -1 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 b021b49b20..deaf617dd3 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts @@ -172,16 +172,6 @@ describe('EditorEffects', () => { }) }) - describe('cleanRecordAttachments$', () => { - it('dispatch markRecordAsChanged', () => { - actions = hot('-a-|', { - a: EditorActions.saveRecordSuccess(), - }) - const expected = hot('---|') - expect(effects.cleanRecordAttachments$).toBeObservable(expected) - }) - }) - describe('checkHasChangesOnOpen$', () => { describe('if the record has a draft', () => { it('dispatch markRecordAsChanged', () => {