diff --git a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts index fc263c9018..52430368e9 100644 --- a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -33,6 +33,8 @@ describe('ContentMetadataComponent', () => { let component: ContentMetadataComponent; let fixture: ComponentFixture; let contentMetadataService: ContentMetadataService; + let updateService: CardViewUpdateService; + let nodesApiService: NodesApiService; let node: Node; let folderNode: Node; const preset = 'custom-preset'; @@ -46,6 +48,9 @@ describe('ContentMetadataComponent', () => { fixture = TestBed.createComponent(ContentMetadataComponent); component = fixture.componentInstance; contentMetadataService = TestBed.get(ContentMetadataService); + updateService = TestBed.get(CardViewUpdateService); + nodesApiService = TestBed.get(NodesApiService); + node = { id: 'node-id', aspectNames: [], @@ -104,9 +109,7 @@ describe('ContentMetadataComponent', () => { describe('Saving', () => { it('should save the node on itemUpdate', () => { - const property = { key: 'property-key', value: 'original-value' }, - updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), - nodesApiService: NodesApiService = TestBed.get(NodesApiService); + const property = { key: 'property-key', value: 'original-value' }; spyOn(nodesApiService, 'updateNode').and.callThrough(); updateService.update(property, 'updated-value'); @@ -117,10 +120,8 @@ describe('ContentMetadataComponent', () => { }); it('should update the node on successful save', async(() => { - const property = { key: 'property-key', value: 'original-value' }, - updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), - nodesApiService: NodesApiService = TestBed.get(NodesApiService), - expectedNode = Object.assign({}, node, { name: 'some-modified-value' }); + const property = { key: 'property-key', value: 'original-value' }; + const expectedNode = Object.assign({}, node, { name: 'some-modified-value' }); spyOn(nodesApiService, 'updateNode').and.callFake(() => { return of(expectedNode); @@ -134,10 +135,8 @@ describe('ContentMetadataComponent', () => { })); it('should throw error on unsuccessful save', () => { - const property = { key: 'property-key', value: 'original-value' }, - updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), - nodesApiService: NodesApiService = TestBed.get(NodesApiService), - logService: LogService = TestBed.get(LogService); + const property = { key: 'property-key', value: 'original-value' }; + const logService: LogService = TestBed.get(LogService); spyOn(nodesApiService, 'updateNode').and.callFake(() => { return throwError(new Error('My bad')); @@ -147,6 +146,23 @@ describe('ContentMetadataComponent', () => { expect(logService.error).toHaveBeenCalledWith(new Error('My bad')); }); + + it('should raise error message', (done) => { + const property = { key: 'property-key', value: 'original-value' }; + + const sub = contentMetadataService.error.subscribe((err) => { + expect(err.statusCode).toBe(0); + expect(err.message).toBe('METADATA.ERRORS.GENERIC'); + sub.unsubscribe(); + done(); + }); + + spyOn(nodesApiService, 'updateNode').and.callFake(() => { + return throwError(new Error('My bad')); + }); + + updateService.update(property, 'updated-value'); + }); }); describe('Properties loading', () => { diff --git a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts index cfeb2893ee..877f4ade03 100644 --- a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts @@ -17,11 +17,11 @@ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Node } from '@alfresco/js-api'; -import { Observable, Subscription } from 'rxjs'; -import { CardViewItem, NodesApiService, LogService, CardViewUpdateService, AlfrescoApiService } from '@alfresco/adf-core'; +import { Observable, Subject, of } from 'rxjs'; +import { CardViewItem, NodesApiService, LogService, CardViewUpdateService, AlfrescoApiService, TranslationService } from '@alfresco/adf-core'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { CardViewGroup } from '../../interfaces/content-metadata.interfaces'; -import { switchMap } from 'rxjs/operators'; +import { switchMap, takeUntil, catchError } from 'rxjs/operators'; @Component({ selector: 'adf-content-metadata', @@ -31,6 +31,9 @@ import { switchMap } from 'rxjs/operators'; encapsulation: ViewEncapsulation.None }) export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { + + protected onDestroy$ = new Subject(); + /** (required) The node entity to fetch metadata about */ @Input() node: Node; @@ -67,32 +70,62 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { basicProperties$: Observable; groupedProperties$: Observable; - disposableNodeUpdate: Subscription; constructor( private contentMetadataService: ContentMetadataService, private cardViewUpdateService: CardViewUpdateService, private nodesApiService: NodesApiService, private logService: LogService, - private alfrescoApiService: AlfrescoApiService + private alfrescoApiService: AlfrescoApiService, + private translationService: TranslationService ) {} ngOnInit() { - this.disposableNodeUpdate = this.cardViewUpdateService.itemUpdated$ + this.cardViewUpdateService.itemUpdated$ .pipe( - switchMap(this.saveNode.bind(this)) + switchMap((changes) => + this.saveNode(changes).pipe( + catchError((err) => { + this.handleUpdateError(err); + return of(null); + }) + ) + ), + takeUntil(this.onDestroy$) ) .subscribe( (updatedNode) => { - Object.assign(this.node, updatedNode); - this.alfrescoApiService.nodeUpdated.next(this.node); - }, - (error) => this.logService.error(error) + if (updatedNode) { + Object.assign(this.node, updatedNode); + this.alfrescoApiService.nodeUpdated.next(this.node); + } + } ); this.loadProperties(this.node); } + protected handleUpdateError(error: Error) { + this.logService.error(error); + + let statusCode = 0; + + try { + statusCode = JSON.parse(error.message).error.statusCode; + } catch {} + + let message = `METADATA.ERRORS.${statusCode}`; + + if (this.translationService.instant(message) === message) { + message = 'METADATA.ERRORS.GENERIC'; + } + + this.contentMetadataService.error.next({ + statusCode, + message + }); + } + ngOnChanges(changes: SimpleChanges) { if (changes.node && !changes.node.firstChange) { this.loadProperties(changes.node.currentValue); @@ -119,7 +152,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } ngOnDestroy() { - this.disposableNodeUpdate.unsubscribe(); + this.onDestroy$.next(true); + this.onDestroy$.complete(); } public canExpandTheCard(group: CardViewGroup): boolean { diff --git a/lib/content-services/content-metadata/services/content-metadata.service.ts b/lib/content-services/content-metadata/services/content-metadata.service.ts index 678978cb6e..f57c0fa5c0 100644 --- a/lib/content-services/content-metadata/services/content-metadata.service.ts +++ b/lib/content-services/content-metadata/services/content-metadata.service.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { Node } from '@alfresco/js-api'; import { BasicPropertiesService } from './basic-properties.service'; -import { Observable, of, iif } from 'rxjs'; +import { Observable, of, iif, Subject } from 'rxjs'; import { PropertyGroupTranslatorService } from './property-groups-translator.service'; import { CardViewItem } from '@alfresco/adf-core'; import { CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces'; @@ -31,6 +31,8 @@ import { map, switchMap } from 'rxjs/operators'; }) export class ContentMetadataService { + error = new Subject<{ statusCode: number, message: string }>(); + constructor(private basicPropertiesService: BasicPropertiesService, private contentMetadataConfigFactory: ContentMetadataConfigFactory, private propertyGroupTranslatorService: PropertyGroupTranslatorService, diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 9034f6f2ee..ddccdd430a 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -277,6 +277,11 @@ "CREATED_DATE": "Created Date", "MODIFIER": "Modifier", "MODIFIED_DATE": "Modified Date" + }, + "ERRORS": { + "GENERIC": "Error updating property", + "409": "Duplicate child name not allowed [409]", + "422": "Invalid property value" } }, "SHARE": { diff --git a/lib/core/card-view/services/card-view-update.service.ts b/lib/core/card-view/services/card-view-update.service.ts index b27fa09481..04d742ab47 100644 --- a/lib/core/card-view/services/card-view-update.service.ts +++ b/lib/core/card-view/services/card-view-update.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; +import { Subject } from 'rxjs'; import { CardViewBaseItemModel } from '../models/card-view-baseitem.model'; export interface UpdateNotification { @@ -41,23 +41,18 @@ export function transformKeyToObject(key: string, value): Object { }) export class CardViewUpdateService { - // Observable sources - private itemUpdatedSource = new Subject(); - private itemClickedSource = new Subject(); - - // Observable streams - public itemUpdated$ = > this.itemUpdatedSource.asObservable(); - public itemClicked$ = > this.itemClickedSource.asObservable(); + itemUpdated$ = new Subject(); + itemClicked$ = new Subject(); update(property: CardViewBaseItemModel, newValue: any) { - this.itemUpdatedSource.next({ + this.itemUpdated$.next({ target: property, changed: transformKeyToObject(property.key, newValue) }); } clicked(property: CardViewBaseItemModel) { - this.itemClickedSource.next({ + this.itemClicked$.next({ target: property }); }