diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.component.html b/lib/content-services/src/lib/aspect-list/aspect-list.component.html index e90a4fd387..dd39db15ae 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.component.html +++ b/lib/content-services/src/lib/aspect-list/aspect-list.component.html @@ -1,6 +1,6 @@ -
+
- +
+ + +
+ +
+
diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.component.scss b/lib/content-services/src/lib/aspect-list/aspect-list.component.scss index 5632bd12f8..e9a06f4677 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.component.scss +++ b/lib/content-services/src/lib/aspect-list/aspect-list.component.scss @@ -8,10 +8,17 @@ .adf { + &-aspect-list-spinner { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; + } + &-aspect-list-container { padding-top: 3px; - max-height: 400px; + height: 400px; overflow: auto; .adf-aspect-list-check-button { diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.component.spec.ts b/lib/content-services/src/lib/aspect-list/aspect-list.component.spec.ts index c0ee0c2b60..7c528ef776 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.component.spec.ts +++ b/lib/content-services/src/lib/aspect-list/aspect-list.component.spec.ts @@ -23,6 +23,7 @@ import { AspectListComponent } from './aspect-list.component'; import { AspectListService } from './aspect-list.service'; import { of } from 'rxjs'; import { AspectEntry } from '@alfresco/js-api'; +import { delay } from 'rxjs/operators'; const aspectListMock: AspectEntry[] = [{ entry: { @@ -80,6 +81,26 @@ describe('AspectListComponent', () => { providers: [AspectListService] }); + describe('Loading', () => { + + beforeEach(() => { + fixture = TestBed.createComponent(AspectListComponent); + component = fixture.componentInstance; + nodeService = TestBed.inject(NodesApiService); + aspectListService = TestBed.inject(AspectListService); + }); + + it('should show the loading spinner when result is loading', () => { + const delayReusult = of([]).pipe(delay(0)); + spyOn(nodeService, 'getNode').and.returnValue(delayReusult); + spyOn(aspectListService, 'getAspects').and.returnValue(delayReusult); + fixture.detectChanges(); + const spinner = fixture.nativeElement.querySelector('#adf-aspect-spinner'); + expect(spinner).toBeDefined(); + expect(spinner).not.toBeNull(); + }); + }); + describe('When passing a node id', () => { beforeEach(() => { diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.module.ts b/lib/content-services/src/lib/aspect-list/aspect-list.module.ts index dce4bab26a..9b448d49e8 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.module.ts +++ b/lib/content-services/src/lib/aspect-list/aspect-list.module.ts @@ -27,6 +27,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { AspectListDialogComponent } from './aspect-list-dialog.component'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @NgModule({ imports: [ @@ -38,7 +39,8 @@ import { MatTooltipModule } from '@angular/material/tooltip'; TranslateModule, MatDialogModule, MatButtonModule, - MatTooltipModule + MatTooltipModule, + MatProgressSpinnerModule ], exports: [ AspectListComponent, diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.service.spec.ts b/lib/content-services/src/lib/aspect-list/aspect-list.service.spec.ts index 437eb5cf44..1db6c5aa47 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.service.spec.ts +++ b/lib/content-services/src/lib/aspect-list/aspect-list.service.spec.ts @@ -19,8 +19,8 @@ import { AspectEntry, AspectPaging } from '@alfresco/js-api'; import { async, TestBed } from '@angular/core/testing'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { TranslateModule } from '@ngx-translate/core'; -import { AlfrescoApiService, AppConfigService, setupTestBed } from 'core'; -import { of, Subject } from 'rxjs'; +import { AlfrescoApiService, AppConfigService, LogService, setupTestBed } from 'core'; +import { of, Subject, throwError } from 'rxjs'; import { ContentTestingModule } from '../testing/content.testing.module'; import { AspectListService } from './aspect-list.service'; @@ -146,11 +146,12 @@ describe('AspectListService', () => { const aspectTypesApi = jasmine.createSpyObj('AspectsApi', ['listAspects']); const apiService: AlfrescoApiService = new AlfrescoApiService(null, null); + const logService: LogService = new LogService(appConfigService); beforeEach(() => { spyOn(appConfigService, 'get').and.returnValue({ 'default': ['frs:AspectOne'] }); spyOnProperty(apiService, 'aspectsApi').and.returnValue(aspectTypesApi); - service = new AspectListService(apiService, appConfigService, null); + service = new AspectListService(apiService, appConfigService, null, logService); }); it('should get the list of only available aspects', async(() => { @@ -161,6 +162,26 @@ describe('AspectListService', () => { expect(list[1].entry.id).toBe('frs:AspectCustom'); }); })); + + it('should return a value when the standard aspect call fails', async(() => { + spyOn(logService, 'error').and.stub(); + aspectTypesApi.listAspects.and.returnValues(throwError('Insert Coin'), of(customListAspectResp)); + service.getAspects().subscribe((list) => { + expect(list.length).toBe(1); + expect(list[0].entry.id).toBe('frs:AspectCustom'); + expect(logService.error).toHaveBeenCalled(); + }); + })); + + it('should return a value when the custom aspect call fails', async(() => { + spyOn(logService, 'error').and.stub(); + aspectTypesApi.listAspects.and.returnValues(of(listAspectResp), throwError('Insert Coin')); + service.getAspects().subscribe((list) => { + expect(list.length).toBe(1); + expect(list[0].entry.id).toBe('frs:AspectOne'); + expect(logService.error).toHaveBeenCalled(); + }); + })); }); }); diff --git a/lib/content-services/src/lib/aspect-list/aspect-list.service.ts b/lib/content-services/src/lib/aspect-list/aspect-list.service.ts index 79d9a3852a..cf9dcb9f15 100644 --- a/lib/content-services/src/lib/aspect-list/aspect-list.service.ts +++ b/lib/content-services/src/lib/aspect-list/aspect-list.service.ts @@ -17,11 +17,11 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core'; -import { from, Observable, Subject, zip } from 'rxjs'; +import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { from, Observable, of, Subject, zip } from 'rxjs'; import { AspectListDialogComponentData } from './aspect-list-dialog-data.interface'; import { AspectListDialogComponent } from './aspect-list-dialog.component'; -import { map } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { AspectEntry, AspectPaging } from '@alfresco/js-api'; @Injectable({ @@ -30,7 +30,9 @@ import { AspectEntry, AspectPaging } from '@alfresco/js-api'; export class AspectListService { constructor(private alfrescoApiService: AlfrescoApiService, - private appConfigService: AppConfigService, private dialog: MatDialog) { + private appConfigService: AppConfigService, + private dialog: MatDialog, + private logService: LogService) { } getAspects(): Observable { @@ -44,17 +46,25 @@ export class AspectListService { getStandardAspects(whiteList: string[]): Observable { const where = `(modelIds in ('cm:contentmodel', 'emailserver:emailserverModel', 'smf:smartFolder', 'app:applicationmodel' ))`; - return from(this.alfrescoApiService.aspectsApi.listAspects(where)) + return from(this.alfrescoApiService.aspectsApi.listAspects({where})) .pipe( - map((result: AspectPaging) => this.filterAspectByConfig(whiteList, result?.list?.entries)) + map((result: AspectPaging) => this.filterAspectByConfig(whiteList, result?.list?.entries)), + catchError((error) => { + this.logService.error(error); + return of([]); + }) ); } getCustomAspects(): Observable { - const where = `(not namespaceUri matches('http://www.alfresco.*')`; - return from(this.alfrescoApiService.aspectsApi.listAspects(where)) + const where = `(not namespaceUri matches('http://www.alfresco.*'))`; + return from(this.alfrescoApiService.aspectsApi.listAspects({where})) .pipe( - map((result: AspectPaging) => result?.list?.entries) + map((result: AspectPaging) => result?.list?.entries), + catchError((error) => { + this.logService.error(error); + return of([]); + }) ); } diff --git a/lib/content-services/src/lib/aspect-list/node-aspect.service.spec.ts b/lib/content-services/src/lib/aspect-list/node-aspect.service.spec.ts index b60c6e1471..31acf9561e 100644 --- a/lib/content-services/src/lib/aspect-list/node-aspect.service.spec.ts +++ b/lib/content-services/src/lib/aspect-list/node-aspect.service.spec.ts @@ -17,7 +17,7 @@ import { TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { AlfrescoApiService, NodesApiService, setupTestBed } from 'core'; +import { AlfrescoApiService, CardViewUpdateService, NodesApiService, setupTestBed } from 'core'; import { of } from 'rxjs'; import { ContentTestingModule } from '../testing/content.testing.module'; import { AspectListService } from './aspect-list.service'; @@ -29,6 +29,7 @@ describe('NodeAspectService', () => { let nodeAspectService: NodeAspectService; let nodeApiService: NodesApiService; let alfrescoApiService: AlfrescoApiService; + let cardViewUpdateService: CardViewUpdateService; setupTestBed({ imports: [ @@ -42,6 +43,7 @@ describe('NodeAspectService', () => { nodeAspectService = TestBed.inject(NodeAspectService); nodeApiService = TestBed.inject(NodesApiService); alfrescoApiService = TestBed.inject(AlfrescoApiService); + cardViewUpdateService = TestBed.inject(CardViewUpdateService); }); it('should open the aspect list dialog', () => { @@ -71,4 +73,16 @@ describe('NodeAspectService', () => { nodeAspectService.updateNodeAspects('fake-node-id'); }); + it('should send and update node aspect once the node has been updated', (done) => { + cardViewUpdateService.updatedAspect$.subscribe((nodeUpdated) => { + expect(nodeUpdated.id).toBe('fake-node-id'); + expect(nodeUpdated.aspectNames).toEqual(['a', 'b', 'c']); + done(); + }); + const fakeNode = { id: 'fake-node-id', aspectNames: ['a', 'b', 'c'] }; + spyOn(aspectListService, 'openAspectListDialog').and.returnValue(of(['a', 'b', 'c'])); + spyOn(nodeApiService, 'updateNode').and.returnValue(of(fakeNode)); + nodeAspectService.updateNodeAspects('fake-node-id'); + }); + }); diff --git a/lib/content-services/src/lib/aspect-list/node-aspect.service.ts b/lib/content-services/src/lib/aspect-list/node-aspect.service.ts index 32fa1ca6be..cc4a1e9b40 100644 --- a/lib/content-services/src/lib/aspect-list/node-aspect.service.ts +++ b/lib/content-services/src/lib/aspect-list/node-aspect.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { AlfrescoApiService, NodesApiService } from '@alfresco/adf-core'; +import { AlfrescoApiService, CardViewUpdateService, NodesApiService } from '@alfresco/adf-core'; import { AspectListService } from './aspect-list.service'; @Injectable({ @@ -26,13 +26,15 @@ export class NodeAspectService { constructor(private alfrescoApiService: AlfrescoApiService, private nodesApiService: NodesApiService, - private aspectListService: AspectListService) { + private aspectListService: AspectListService, + private cardViewUpdateService: CardViewUpdateService) { } updateNodeAspects(nodeId: string) { this.aspectListService.openAspectListDialog(nodeId).subscribe((aspectList) => { this.nodesApiService.updateNode(nodeId, { aspectNames: [...aspectList] }).subscribe((updatedNode) => { this.alfrescoApiService.nodeUpdated.next(updatedNode); + this.cardViewUpdateService.updateNodeAspect(updatedNode); }); }); } diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts index ff61a79ca7..9289ca1f83 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -18,7 +18,7 @@ import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; import { SimpleChange } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { Node } from '@alfresco/js-api'; +import { MinimalNode, Node } from '@alfresco/js-api'; import { ContentMetadataComponent } from './content-metadata.component'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { @@ -124,6 +124,17 @@ describe('ContentMetadataComponent', () => { expect(component.changedProperties).toEqual({ properties: { 'property-key': 'updated-value' } }); })); + it('nodeAspectUpdate', fakeAsync(() => { + const fakeNode: MinimalNode = { id: 'fake-minimal-node', aspectNames: ['ft:a', 'ft:b', 'ft:c'], name: 'fake-node'}; + spyOn(contentMetadataService, 'getGroupedProperties').and.stub(); + spyOn(contentMetadataService, 'getBasicProperties').and.stub(); + updateService.updateNodeAspect(fakeNode); + + tick(600); + expect(contentMetadataService.getBasicProperties).toHaveBeenCalled(); + expect(contentMetadataService.getGroupedProperties).toHaveBeenCalled(); + })); + it('should save changedProperties on save click', fakeAsync(async () => { component.editable = true; const property = { key: 'properties.property-key', value: 'original-value' }; diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts index 2d47266e83..8b72b5b255 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts @@ -26,7 +26,8 @@ import { AlfrescoApiService, TranslationService, AppConfigService, - CardViewBaseItemModel + CardViewBaseItemModel, + UpdateNotification } from '@alfresco/adf-core'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { CardViewGroup } from '../../interfaces/content-metadata.interfaces'; @@ -115,13 +116,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { debounceTime(200), takeUntil(this.onDestroy$)) .subscribe( - (updatedNode) => { + (updatedNode: UpdateNotification) => { this.hasMetadataChanged = true; this.targetProperty = updatedNode.target; this.updateChanges(updatedNode.changed); } ); + this.cardViewUpdateService.updatedAspect$.pipe( + debounceTime(200), + takeUntil(this.onDestroy$)) + .subscribe((node) => this.loadProperties(node)); + this.loadProperties(this.node); } diff --git a/lib/core/card-view/services/card-view-update.service.spec.ts b/lib/core/card-view/services/card-view-update.service.spec.ts index 2723cc0bf2..b0c368f5da 100644 --- a/lib/core/card-view/services/card-view-update.service.spec.ts +++ b/lib/core/card-view/services/card-view-update.service.spec.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { MinimalNode } from '@alfresco/js-api'; import { async, TestBed } from '@angular/core/testing'; import { CardViewBaseItemModel } from '../models/card-view-baseitem.model'; import { CardViewUpdateService, transformKeyToObject } from './card-view-update.service'; @@ -82,5 +83,13 @@ describe('CardViewUpdateService', () => { ); cardViewUpdateService.clicked(property); })); + + it('should send updated node when aspect changed', async(() => { + const fakeNode: MinimalNode = { id: 'Bigfoot'}; + cardViewUpdateService.updatedAspect$.subscribe((node: MinimalNode) => { + expect(node.id).toBe('Bigfoot'); + }); + cardViewUpdateService.updateNodeAspect(fakeNode); + })); }); }); 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 c64b19300e..20156a7493 100644 --- a/lib/core/card-view/services/card-view-update.service.ts +++ b/lib/core/card-view/services/card-view-update.service.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { MinimalNode } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { CardViewBaseItemModel } from '../models/card-view-baseitem.model'; @@ -44,6 +45,7 @@ export class CardViewUpdateService { itemUpdated$ = new Subject(); itemClicked$ = new Subject(); updateItem$ = new Subject(); + updatedAspect$ = new Subject(); update(property: CardViewBaseItemModel, newValue: any) { this.itemUpdated$.next({ @@ -66,4 +68,8 @@ export class CardViewUpdateService { this.updateItem$.next(notification); } + updateNodeAspect(node: MinimalNode) { + this.updatedAspect$.next(node); + } + }