[ADF-4268] Info drawer fixes (#4709)

* show error popups

* proper error handling

* update tests

* update code as per review

* remove old implementation

* remove old code
This commit is contained in:
Denys Vuika
2019-05-13 07:24:49 +01:00
committed by Eugenio Romano
parent fc9f04c6c6
commit bbea17fe37
5 changed files with 86 additions and 34 deletions

View File

@@ -33,6 +33,8 @@ describe('ContentMetadataComponent', () => {
let component: ContentMetadataComponent; let component: ContentMetadataComponent;
let fixture: ComponentFixture<ContentMetadataComponent>; let fixture: ComponentFixture<ContentMetadataComponent>;
let contentMetadataService: ContentMetadataService; let contentMetadataService: ContentMetadataService;
let updateService: CardViewUpdateService;
let nodesApiService: NodesApiService;
let node: Node; let node: Node;
let folderNode: Node; let folderNode: Node;
const preset = 'custom-preset'; const preset = 'custom-preset';
@@ -46,6 +48,9 @@ describe('ContentMetadataComponent', () => {
fixture = TestBed.createComponent(ContentMetadataComponent); fixture = TestBed.createComponent(ContentMetadataComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
contentMetadataService = TestBed.get(ContentMetadataService); contentMetadataService = TestBed.get(ContentMetadataService);
updateService = TestBed.get(CardViewUpdateService);
nodesApiService = TestBed.get(NodesApiService);
node = <Node> { node = <Node> {
id: 'node-id', id: 'node-id',
aspectNames: [], aspectNames: [],
@@ -104,9 +109,7 @@ describe('ContentMetadataComponent', () => {
describe('Saving', () => { describe('Saving', () => {
it('should save the node on itemUpdate', () => { it('should save the node on itemUpdate', () => {
const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' }, const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' };
updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService),
nodesApiService: NodesApiService = TestBed.get(NodesApiService);
spyOn(nodesApiService, 'updateNode').and.callThrough(); spyOn(nodesApiService, 'updateNode').and.callThrough();
updateService.update(property, 'updated-value'); updateService.update(property, 'updated-value');
@@ -117,10 +120,8 @@ describe('ContentMetadataComponent', () => {
}); });
it('should update the node on successful save', async(() => { it('should update the node on successful save', async(() => {
const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' }, const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' };
updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), const expectedNode = Object.assign({}, node, { name: 'some-modified-value' });
nodesApiService: NodesApiService = TestBed.get(NodesApiService),
expectedNode = Object.assign({}, node, { name: 'some-modified-value' });
spyOn(nodesApiService, 'updateNode').and.callFake(() => { spyOn(nodesApiService, 'updateNode').and.callFake(() => {
return of(expectedNode); return of(expectedNode);
@@ -134,10 +135,8 @@ describe('ContentMetadataComponent', () => {
})); }));
it('should throw error on unsuccessful save', () => { it('should throw error on unsuccessful save', () => {
const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' }, const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' };
updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), const logService: LogService = TestBed.get(LogService);
nodesApiService: NodesApiService = TestBed.get(NodesApiService),
logService: LogService = TestBed.get(LogService);
spyOn(nodesApiService, 'updateNode').and.callFake(() => { spyOn(nodesApiService, 'updateNode').and.callFake(() => {
return throwError(new Error('My bad')); return throwError(new Error('My bad'));
@@ -147,6 +146,23 @@ describe('ContentMetadataComponent', () => {
expect(logService.error).toHaveBeenCalledWith(new Error('My bad')); expect(logService.error).toHaveBeenCalledWith(new Error('My bad'));
}); });
it('should raise error message', (done) => {
const property = <CardViewBaseItemModel> { 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', () => { describe('Properties loading', () => {

View File

@@ -17,11 +17,11 @@
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subject, of } from 'rxjs';
import { CardViewItem, NodesApiService, LogService, CardViewUpdateService, AlfrescoApiService } from '@alfresco/adf-core'; import { CardViewItem, NodesApiService, LogService, CardViewUpdateService, AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
import { ContentMetadataService } from '../../services/content-metadata.service'; import { ContentMetadataService } from '../../services/content-metadata.service';
import { CardViewGroup } from '../../interfaces/content-metadata.interfaces'; import { CardViewGroup } from '../../interfaces/content-metadata.interfaces';
import { switchMap } from 'rxjs/operators'; import { switchMap, takeUntil, catchError } from 'rxjs/operators';
@Component({ @Component({
selector: 'adf-content-metadata', selector: 'adf-content-metadata',
@@ -31,6 +31,9 @@ import { switchMap } from 'rxjs/operators';
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
protected onDestroy$ = new Subject<boolean>();
/** (required) The node entity to fetch metadata about */ /** (required) The node entity to fetch metadata about */
@Input() @Input()
node: Node; node: Node;
@@ -67,32 +70,62 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
basicProperties$: Observable<CardViewItem[]>; basicProperties$: Observable<CardViewItem[]>;
groupedProperties$: Observable<CardViewGroup[]>; groupedProperties$: Observable<CardViewGroup[]>;
disposableNodeUpdate: Subscription;
constructor( constructor(
private contentMetadataService: ContentMetadataService, private contentMetadataService: ContentMetadataService,
private cardViewUpdateService: CardViewUpdateService, private cardViewUpdateService: CardViewUpdateService,
private nodesApiService: NodesApiService, private nodesApiService: NodesApiService,
private logService: LogService, private logService: LogService,
private alfrescoApiService: AlfrescoApiService private alfrescoApiService: AlfrescoApiService,
private translationService: TranslationService
) {} ) {}
ngOnInit() { ngOnInit() {
this.disposableNodeUpdate = this.cardViewUpdateService.itemUpdated$ this.cardViewUpdateService.itemUpdated$
.pipe( .pipe(
switchMap(this.saveNode.bind(this)) switchMap((changes) =>
this.saveNode(changes).pipe(
catchError((err) => {
this.handleUpdateError(err);
return of(null);
})
)
),
takeUntil(this.onDestroy$)
) )
.subscribe( .subscribe(
(updatedNode) => { (updatedNode) => {
if (updatedNode) {
Object.assign(this.node, updatedNode); Object.assign(this.node, updatedNode);
this.alfrescoApiService.nodeUpdated.next(this.node); this.alfrescoApiService.nodeUpdated.next(this.node);
}, }
(error) => this.logService.error(error) }
); );
this.loadProperties(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) { ngOnChanges(changes: SimpleChanges) {
if (changes.node && !changes.node.firstChange) { if (changes.node && !changes.node.firstChange) {
this.loadProperties(changes.node.currentValue); this.loadProperties(changes.node.currentValue);
@@ -119,7 +152,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.disposableNodeUpdate.unsubscribe(); this.onDestroy$.next(true);
this.onDestroy$.complete();
} }
public canExpandTheCard(group: CardViewGroup): boolean { public canExpandTheCard(group: CardViewGroup): boolean {

View File

@@ -18,7 +18,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
import { BasicPropertiesService } from './basic-properties.service'; 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 { PropertyGroupTranslatorService } from './property-groups-translator.service';
import { CardViewItem } from '@alfresco/adf-core'; import { CardViewItem } from '@alfresco/adf-core';
import { CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces'; import { CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces';
@@ -31,6 +31,8 @@ import { map, switchMap } from 'rxjs/operators';
}) })
export class ContentMetadataService { export class ContentMetadataService {
error = new Subject<{ statusCode: number, message: string }>();
constructor(private basicPropertiesService: BasicPropertiesService, constructor(private basicPropertiesService: BasicPropertiesService,
private contentMetadataConfigFactory: ContentMetadataConfigFactory, private contentMetadataConfigFactory: ContentMetadataConfigFactory,
private propertyGroupTranslatorService: PropertyGroupTranslatorService, private propertyGroupTranslatorService: PropertyGroupTranslatorService,

View File

@@ -277,6 +277,11 @@
"CREATED_DATE": "Created Date", "CREATED_DATE": "Created Date",
"MODIFIER": "Modifier", "MODIFIER": "Modifier",
"MODIFIED_DATE": "Modified Date" "MODIFIED_DATE": "Modified Date"
},
"ERRORS": {
"GENERIC": "Error updating property",
"409": "Duplicate child name not allowed [409]",
"422": "Invalid property value"
} }
}, },
"SHARE": { "SHARE": {

View File

@@ -16,7 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model'; import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
export interface UpdateNotification { export interface UpdateNotification {
@@ -41,23 +41,18 @@ export function transformKeyToObject(key: string, value): Object {
}) })
export class CardViewUpdateService { export class CardViewUpdateService {
// Observable sources itemUpdated$ = new Subject<UpdateNotification>();
private itemUpdatedSource = new Subject<UpdateNotification>(); itemClicked$ = new Subject<ClickNotification>();
private itemClickedSource = new Subject<ClickNotification>();
// Observable streams
public itemUpdated$ = <Observable<UpdateNotification>> this.itemUpdatedSource.asObservable();
public itemClicked$ = <Observable<ClickNotification>> this.itemClickedSource.asObservable();
update(property: CardViewBaseItemModel, newValue: any) { update(property: CardViewBaseItemModel, newValue: any) {
this.itemUpdatedSource.next({ this.itemUpdated$.next({
target: property, target: property,
changed: transformKeyToObject(property.key, newValue) changed: transformKeyToObject(property.key, newValue)
}); });
} }
clicked(property: CardViewBaseItemModel) { clicked(property: CardViewBaseItemModel) {
this.itemClickedSource.next({ this.itemClicked$.next({
target: property target: property
}); });
} }