mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
Aspect List - added spinner and refresh (#6693)
* Adding spinner for loading aspects * Added metadata update and test
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div id="aspect-list-container" class="adf-aspect-list-container">
|
||||
<div id="aspect-list-container" class="adf-aspect-list-container" *ngIf="aspects$ | async as aspects; else loading">
|
||||
<mat-accordion class="adf-accordion-aspect-list">
|
||||
<mat-expansion-panel *ngFor="let aspect of (aspects$ | async); let colIndex = index" [id]="'aspect-list-'+aspect?.entry?.title">
|
||||
<mat-expansion-panel *ngFor="let aspect of aspects; let colIndex = index" [id]="'aspect-list-'+aspect?.entry?.title">
|
||||
<mat-expansion-panel-header [id]="'aspect-list-'+aspect?.entry?.title+'header'">
|
||||
<mat-panel-title>
|
||||
<mat-checkbox class="adf-aspect-list-check-button" [id]="'aspect-list-'+colIndex+'-check'"
|
||||
@@ -36,3 +36,9 @@
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
|
||||
<ng-template #loading>
|
||||
<div class="adf-aspect-list-spinner">
|
||||
<mat-spinner id="adf-aspect-spinner"></mat-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -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 {
|
||||
|
@@ -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(() => {
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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<AspectEntry[]> {
|
||||
@@ -44,17 +46,25 @@ export class AspectListService {
|
||||
|
||||
getStandardAspects(whiteList: string[]): Observable<AspectEntry[]> {
|
||||
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<AspectEntry[]> {
|
||||
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([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -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 = <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 = <CardViewBaseItemModel> { key: 'properties.property-key', value: 'original-value' };
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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 = <MinimalNode> { id: 'Bigfoot'};
|
||||
cardViewUpdateService.updatedAspect$.subscribe((node: MinimalNode) => {
|
||||
expect(node.id).toBe('Bigfoot');
|
||||
});
|
||||
cardViewUpdateService.updateNodeAspect(fakeNode);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -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<UpdateNotification>();
|
||||
itemClicked$ = new Subject<ClickNotification>();
|
||||
updateItem$ = new Subject<CardViewBaseItemModel>();
|
||||
updatedAspect$ = new Subject<MinimalNode>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user