[MNT-24459] Aspect list get all aspects when not all were fetched by first call (#10980)

* [MNT-24459] Aspect list get all aspects when not all were fetched by first call

* [MNT-24459] CR fixes

* [MNT-24459] Sonar issues fixed
This commit is contained in:
MichalKinas
2025-07-04 07:18:20 +02:00
committed by GitHub
parent 951b22e098
commit c863da0c5d
7 changed files with 288 additions and 114 deletions

View File

@@ -132,7 +132,9 @@ describe('AspectListDialogComponent', () => {
describe('Without passing node id', () => { describe('Without passing node id', () => {
beforeEach(() => { beforeEach(() => {
aspectListService = TestBed.inject(AspectListService); aspectListService = TestBed.inject(AspectListService);
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock)); spyOn(aspectListService, 'getAllAspects').and.returnValue(
of({ standardAspectPaging: { list: { entries: aspectListMock } }, customAspectPaging: { list: { entries: customAspectListMock } } })
);
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -247,9 +249,11 @@ describe('AspectListDialogComponent', () => {
data.nodeId = 'fake-node-id'; data.nodeId = 'fake-node-id';
aspectListService = TestBed.inject(AspectListService); aspectListService = TestBed.inject(AspectListService);
nodeService = TestBed.inject(NodesApiService); nodeService = TestBed.inject(NodesApiService);
spyOn(aspectListService, 'getAspects').and.returnValue(of([...aspectListMock, ...customAspectListMock])); spyOn(aspectListService, 'getAllAspects').and.returnValue(
of({ standardAspectPaging: { list: { entries: aspectListMock } }, customAspectPaging: { list: { entries: customAspectListMock } } })
);
spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']); spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']);
spyOn(aspectListService, 'getCustomAspects').and.returnValue(of(customAspectListMock)); spyOn(aspectListService, 'getAspects').and.returnValue(of({ list: { entries: customAspectListMock } }));
spyOn(nodeService, 'getNode').and.returnValue( spyOn(nodeService, 'getNode').and.returnValue(
of(new Node({ id: 'fake-node-id', aspectNames: ['frs:AspectOne', 'cst:customAspect'] })).pipe(delay(0)) of(new Node({ id: 'fake-node-id', aspectNames: ['frs:AspectOne', 'cst:customAspect'] })).pipe(delay(0))
); );

View File

@@ -19,15 +19,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NodesApiService } from '../common/services/nodes-api.service'; import { NodesApiService } from '../common/services/nodes-api.service';
import { ContentTestingModule } from '../testing/content.testing.module'; import { ContentTestingModule } from '../testing/content.testing.module';
import { AspectListComponent } from './aspect-list.component'; import { AspectListComponent } from './aspect-list.component';
import { AspectListService } from './services/aspect-list.service'; import { AspectListService, CustomAspectsWhere, StandardAspectsWhere } from './services/aspect-list.service';
import { EMPTY, of } from 'rxjs'; import { EMPTY, of } from 'rxjs';
import { AspectEntry } from '@alfresco/js-api'; import { AspectEntry, Pagination } from '@alfresco/js-api';
import { HarnessLoader } from '@angular/cdk/testing'; import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatExpansionPanelHarness } from '@angular/material/expansion/testing'; import { MatExpansionPanelHarness } from '@angular/material/expansion/testing';
import { MatTableHarness } from '@angular/material/table/testing'; import { MatTableHarness } from '@angular/material/table/testing';
import { MatCheckboxHarness } from '@angular/material/checkbox/testing'; import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
import { CustomAspectPaging } from './interfaces/custom-aspect-paging.interface';
const aspectListMock: AspectEntry[] = [ const aspectListMock: AspectEntry[] = [
{ {
@@ -110,6 +111,11 @@ const customAspectListMock: AspectEntry[] = [
} }
]; ];
const allAspectsMock: CustomAspectPaging = {
standardAspectPaging: { list: { entries: aspectListMock } },
customAspectPaging: { list: { entries: customAspectListMock } }
};
describe('AspectListComponent', () => { describe('AspectListComponent', () => {
let loader: HarnessLoader; let loader: HarnessLoader;
let component: AspectListComponent; let component: AspectListComponent;
@@ -122,17 +128,15 @@ describe('AspectListComponent', () => {
imports: [ContentTestingModule, AspectListComponent], imports: [ContentTestingModule, AspectListComponent],
providers: [AspectListService] providers: [AspectListService]
}); });
fixture = TestBed.createComponent(AspectListComponent);
component = fixture.componentInstance;
nodeService = TestBed.inject(NodesApiService);
aspectListService = TestBed.inject(AspectListService);
loader = TestbedHarnessEnvironment.loader(fixture);
}); });
describe('Loading', () => { describe('Loading', () => {
beforeEach(() => {
fixture = TestBed.createComponent(AspectListComponent);
component = fixture.componentInstance;
nodeService = TestBed.inject(NodesApiService);
aspectListService = TestBed.inject(AspectListService);
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should show the loading spinner when result is loading', async () => { it('should show the loading spinner when result is loading', async () => {
spyOn(nodeService, 'getNode').and.returnValue(EMPTY); spyOn(nodeService, 'getNode').and.returnValue(EMPTY);
spyOn(aspectListService, 'getAspects').and.returnValue(EMPTY); spyOn(aspectListService, 'getAspects').and.returnValue(EMPTY);
@@ -144,16 +148,11 @@ describe('AspectListComponent', () => {
describe('When passing a node id', () => { describe('When passing a node id', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AspectListComponent); spyOn(aspectListService, 'getAllAspects').and.returnValue(of(allAspectsMock));
component = fixture.componentInstance; spyOn(aspectListService, 'getAspects').and.returnValue(of({ list: { entries: customAspectListMock } }));
aspectListService = TestBed.inject(AspectListService);
spyOn(aspectListService, 'getAspects').and.returnValue(of([...aspectListMock, ...customAspectListMock]));
spyOn(aspectListService, 'getCustomAspects').and.returnValue(of(customAspectListMock));
spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']); spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']);
nodeService = TestBed.inject(NodesApiService);
spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne', 'stored:aspect'] } as any)); spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne', 'stored:aspect'] } as any));
component.nodeId = 'fake-node-id'; component.nodeId = 'fake-node-id';
loader = TestbedHarnessEnvironment.loader(fixture);
}); });
afterEach(() => { afterEach(() => {
@@ -251,6 +250,13 @@ describe('AspectListComponent', () => {
expect(component.valueChanged.emit).toHaveBeenCalledWith(['frs:AspectOne', 'frs:SecondAspect', ...storedAspect]); expect(component.valueChanged.emit).toHaveBeenCalledWith(['frs:AspectOne', 'frs:SecondAspect', ...storedAspect]);
expect(component.updateCounter.emit).toHaveBeenCalledWith(2); expect(component.updateCounter.emit).toHaveBeenCalledWith(2);
}); });
it('should load aspects with 0 skip count as pagination by default', () => {
expect(aspectListService.getAllAspects).toHaveBeenCalledWith(
{ where: StandardAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 },
{ where: CustomAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 }
);
});
}); });
describe('with excluded aspects', () => { describe('with excluded aspects', () => {
@@ -265,11 +271,7 @@ describe('AspectListComponent', () => {
describe('When no node id is passed', () => { describe('When no node id is passed', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AspectListComponent); spyOn(aspectListService, 'getAllAspects').and.returnValue(of(allAspectsMock));
component = fixture.componentInstance;
aspectListService = TestBed.inject(AspectListService);
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
loader = TestbedHarnessEnvironment.loader(fixture);
}); });
afterEach(() => { afterEach(() => {
@@ -289,5 +291,38 @@ describe('AspectListComponent', () => {
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBeFalse(); expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBeFalse();
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBeFalse(); expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBeFalse();
}); });
it('should load aspects with 0 skip count as pagination by default', () => {
fixture.detectChanges();
expect(aspectListService.getAllAspects).toHaveBeenCalledWith(
{ where: StandardAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 },
{ where: CustomAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 }
);
});
});
it('should load next batch of aspects if not all items were returned by first call', (done) => {
fixture.detectChanges();
const moreItemsPagination: Pagination = { count: 2, hasMoreItems: true };
const allAspectsWithMoreItems: CustomAspectPaging = {
standardAspectPaging: { list: { entries: aspectListMock, pagination: moreItemsPagination } },
customAspectPaging: { list: { entries: customAspectListMock, pagination: moreItemsPagination } }
};
const getAspectsSpy = spyOn(aspectListService, 'getAllAspects').and.returnValues(of(allAspectsWithMoreItems), of(allAspectsMock));
spyOn(aspectListService, 'getAspects').and.returnValues(
of({ list: { entries: aspectListMock } }),
of({ list: { entries: customAspectListMock } })
);
component.aspects$.subscribe(() => {
expect(getAspectsSpy.calls.argsFor(0)[0]).toEqual({ where: StandardAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 });
expect(getAspectsSpy.calls.argsFor(0)[1]).toEqual({ where: CustomAspectsWhere, include: ['properties'], skipCount: 0, maxItems: 100 });
expect(getAspectsSpy.calls.argsFor(1)[0]).toEqual({ where: StandardAspectsWhere, include: ['properties'], skipCount: 2, maxItems: 100 });
expect(getAspectsSpy.calls.argsFor(1)[1]).toEqual({ where: CustomAspectsWhere, include: ['properties'], skipCount: 2, maxItems: 100 });
done();
});
component.ngOnInit();
fixture.detectChanges();
}); });
}); });

View File

@@ -17,11 +17,11 @@
import { Component, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { Component, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { NodesApiService } from '../common/services/nodes-api.service'; import { NodesApiService } from '../common/services/nodes-api.service';
import { Observable, zip } from 'rxjs'; import { EMPTY, Observable, zip } from 'rxjs';
import { concatMap, map, tap } from 'rxjs/operators'; import { concatMap, expand, map, reduce, take, tap } from 'rxjs/operators';
import { AspectListService } from './services/aspect-list.service'; import { AspectListService, CustomAspectsWhere, StandardAspectsWhere } from './services/aspect-list.service';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { AspectEntry } from '@alfresco/js-api'; import { AspectEntry, ContentPagingQuery, ListAspectsOpts } from '@alfresco/js-api';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
@@ -63,6 +63,10 @@ export class AspectListComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private customAspectsLoaded = 0;
private standardAspectsLoaded = 0;
private hasMoreAspects = false;
constructor(private aspectListService: AspectListService, private nodeApiService: NodesApiService) {} constructor(private aspectListService: AspectListService, private nodeApiService: NodesApiService) {}
ngOnInit(): void { ngOnInit(): void {
@@ -70,8 +74,13 @@ export class AspectListComponent implements OnInit {
if (this.nodeId) { if (this.nodeId) {
const node$ = this.nodeApiService.getNode(this.nodeId); const node$ = this.nodeApiService.getNode(this.nodeId);
const customAspect$ = this.aspectListService const customAspect$ = this.aspectListService
.getCustomAspects(this.aspectListService.getVisibleAspects()) .getAspects(this.aspectListService.getVisibleAspects(), {
.pipe(map((customAspects) => customAspects.flatMap((customAspect) => customAspect.entry.id))); where: CustomAspectsWhere,
include: ['properties'],
skipCount: 0,
maxItems: 100
})
.pipe(map((customAspects) => customAspects?.list?.entries.flatMap((customAspect) => customAspect.entry.id)));
aspects$ = zip(node$, customAspect$).pipe( aspects$ = zip(node$, customAspect$).pipe(
tap(([node, customAspects]) => { tap(([node, customAspects]) => {
this.nodeAspects = node.aspectNames.filter( this.nodeAspects = node.aspectNames.filter(
@@ -84,13 +93,19 @@ export class AspectListComponent implements OnInit {
this.valueChanged.emit([...this.nodeAspects, ...this.notDisplayedAspects]); this.valueChanged.emit([...this.nodeAspects, ...this.notDisplayedAspects]);
this.updateCounter.emit(this.nodeAspects.length); this.updateCounter.emit(this.nodeAspects.length);
}), }),
concatMap(() => this.aspectListService.getAspects()), concatMap(() => this.loadAspects({ skipCount: this.standardAspectsLoaded }, { skipCount: this.customAspectsLoaded })),
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
); );
} else { } else {
aspects$ = this.aspectListService.getAspects().pipe(takeUntilDestroyed(this.destroyRef)); aspects$ = this.loadAspects({ skipCount: this.standardAspectsLoaded }, { skipCount: this.customAspectsLoaded });
} }
this.aspects$ = aspects$.pipe(map((aspects) => aspects.filter((aspect) => !this.excludedAspects.includes(aspect.entry.id)))); this.aspects$ = aspects$.pipe(
expand(() =>
this.hasMoreAspects ? this.loadAspects({ skipCount: this.standardAspectsLoaded }, { skipCount: this.customAspectsLoaded }) : EMPTY
),
map((aspects) => aspects.filter((aspect) => !this.excludedAspects.includes(aspect.entry.id))),
reduce((acc, aspects) => [...acc, ...aspects])
);
} }
onCheckBoxClick(event: Event) { onCheckBoxClick(event: Event) {
@@ -141,4 +156,33 @@ export class AspectListComponent implements OnInit {
this.hasEqualAspect = this.nodeAspects.every((aspect) => this.nodeAspectStatus.includes(aspect)); this.hasEqualAspect = this.nodeAspects.every((aspect) => this.nodeAspectStatus.includes(aspect));
} }
} }
private loadAspects(standardAspectsPagination?: ContentPagingQuery, customAspectsPagination?: ContentPagingQuery): Observable<AspectEntry[]> {
const standardAspectOpts: ListAspectsOpts = {
where: StandardAspectsWhere,
include: ['properties'],
skipCount: standardAspectsPagination?.skipCount ?? 0,
maxItems: 100
};
const customAspectOpts: ListAspectsOpts = {
where: CustomAspectsWhere,
include: ['properties'],
skipCount: customAspectsPagination?.skipCount ?? 0,
maxItems: 100
};
return this.aspectListService.getAllAspects(standardAspectOpts, customAspectOpts).pipe(
take(1),
tap((aspectsPaging) => {
this.customAspectsLoaded += aspectsPaging.customAspectPaging?.list?.pagination?.count ?? 0;
this.standardAspectsLoaded += aspectsPaging.standardAspectPaging?.list?.pagination?.count ?? 0;
this.hasMoreAspects =
aspectsPaging.customAspectPaging?.list?.pagination?.hasMoreItems ||
aspectsPaging.standardAspectPaging?.list?.pagination?.hasMoreItems;
}),
map((aspectsPaging) => [
...(aspectsPaging.standardAspectPaging?.list?.entries ?? []),
...(aspectsPaging.customAspectPaging?.list?.entries ?? [])
])
);
}
} }

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AspectPaging } from '@alfresco/js-api';
export interface CustomAspectPaging {
standardAspectPaging: AspectPaging;
customAspectPaging: AspectPaging;
}

View File

@@ -22,5 +22,6 @@ export * from './services/node-aspect.service';
export * from './services/dialog-aspect-list.service'; export * from './services/dialog-aspect-list.service';
export * from './aspect-list-dialog-data.interface'; export * from './aspect-list-dialog-data.interface';
export * from './interfaces/custom-aspect-paging.interface';
export * from './aspect-list.module'; export * from './aspect-list.module';

View File

@@ -16,96 +16,172 @@
*/ */
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { AspectListService } from './aspect-list.service'; import { AspectListService, CustomAspectsWhere, StandardAspectsWhere } from './aspect-list.service';
import { AspectPaging, AspectsApi, AspectEntry } from '@alfresco/js-api'; import { AspectPaging, AspectsApi, AspectEntry, ListAspectsOpts } from '@alfresco/js-api';
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { provideHttpClientTesting } from '@angular/common/http/testing';
import { AlfrescoApiService } from '../../services'; import { AlfrescoApiService } from '../../services';
import { provideHttpClient } from '@angular/common/http';
import { AppConfigService } from '@alfresco/adf-core';
const stdAspect1: AspectEntry = { entry: { id: 'std:standardAspectOne', description: 'Standard Aspect One', title: 'StandardAspectOne' } }; const stdAspect1: AspectEntry = { entry: { id: 'std:standardAspectOne', description: 'Standard Aspect One', title: 'StandardAspectOne' } };
const stdAspect2: AspectEntry = { entry: { id: 'std:standardAspectTwo', description: 'Standard Aspect Two', title: 'StandardAspectTwo' } }; const stdAspect2: AspectEntry = { entry: { id: 'std:standardAspectTwo', description: 'Standard Aspect Two', title: 'StandardAspectTwo' } };
const stdAspect3: AspectEntry = { entry: { id: 'std:standardAspectThree', description: 'Standard Aspect Three', title: 'StandardAspectThree' } }; const stdAspect3: AspectEntry = { entry: { id: 'std:standardAspectThree', description: 'Standard Aspect Three', title: 'StandardAspectThree' } };
const standardAspectPagingMock: AspectPaging = { list: { entries: [stdAspect1, stdAspect2, stdAspect3] } };
const cstAspect1: AspectEntry = { entry: { id: 'cst:customAspectOne', description: 'Custom Aspect One', title: 'CustomAspectOne' } }; const cstAspect1: AspectEntry = { entry: { id: 'cst:customAspectOne', description: 'Custom Aspect One', title: 'CustomAspectOne' } };
const cstAspect2: AspectEntry = { entry: { id: 'cst:customAspectTwo', description: 'Custom Aspect Two', title: 'CustomAspectTwo' } }; const cstAspect2: AspectEntry = { entry: { id: 'cst:customAspectTwo', description: 'Custom Aspect Two', title: 'CustomAspectTwo' } };
const cstAspect3: AspectEntry = { entry: { id: 'cst:customAspectThree', description: 'Custom Aspect Three', title: 'CustomAspectThree' } }; const cstAspect3: AspectEntry = { entry: { id: 'cst:customAspectThree', description: 'Custom Aspect Three', title: 'CustomAspectThree' } };
const customAspectPagingMock: AspectPaging = { list: { entries: [cstAspect1, cstAspect2, cstAspect3] } };
const aspectsOpts: ListAspectsOpts = {
where: StandardAspectsWhere,
include: ['properties'],
skipCount: 0,
maxItems: 100
};
const customAspectsOpts: ListAspectsOpts = {
where: CustomAspectsWhere,
include: ['properties'],
skipCount: 0,
maxItems: 100
};
let standardAspectPagingMock: AspectPaging;
let customAspectPagingMock: AspectPaging;
describe('AspectListService', () => { describe('AspectListService', () => {
let aspectListService: AspectListService; let aspectListService: AspectListService;
let apiService: AlfrescoApiService; let apiService: AlfrescoApiService;
let aspectsApi: AspectsApi; let aspectsApi: AspectsApi;
let appConfigService: AppConfigService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule] providers: [provideHttpClient(), provideHttpClientTesting()]
}); });
aspectListService = TestBed.inject(AspectListService); aspectListService = TestBed.inject(AspectListService);
appConfigService = TestBed.inject(AppConfigService);
apiService = TestBed.inject(AlfrescoApiService); apiService = TestBed.inject(AlfrescoApiService);
aspectsApi = new AspectsApi(apiService.getInstance()); aspectsApi = new AspectsApi(apiService.getInstance());
spyOnProperty(aspectListService, 'aspectsApi', 'get').and.returnValue(aspectsApi); spyOnProperty(aspectListService, 'aspectsApi', 'get').and.returnValue(aspectsApi);
standardAspectPagingMock = { list: { entries: [stdAspect1, stdAspect2, stdAspect3] } };
customAspectPagingMock = { list: { entries: [cstAspect1, cstAspect2, cstAspect3] } };
}); });
it('should get one standard aspect', (done) => { describe('When API returns error', () => {
const visibleAspects = ['std:standardAspectOne']; const visibleAspects: string[] = [];
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(standardAspectPagingMock));
aspectListService.getStandardAspects(visibleAspects).subscribe((response) => { beforeEach(() => {
expect(response).toEqual([stdAspect1]); spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.reject(new Error('API error')));
done(); });
it('should return empty paging list for standard aspects when api returns error', (done) => {
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([]);
done();
});
});
it('should return empty paging list for custom aspects when api returns error', (done) => {
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([]);
done();
});
}); });
}); });
it('should get two standard aspects', (done) => { describe('When aspects are returned', () => {
const visibleAspects = ['std:standardAspectTwo', 'std:standardAspectThree']; beforeEach(() => {
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(standardAspectPagingMock)); spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(standardAspectPagingMock));
aspectListService.getStandardAspects(visibleAspects).subscribe((response) => { });
expect(response).toEqual([stdAspect2, stdAspect3]);
done(); it('should add custom pagination for aspects list request when provided', (done) => {
const visibleAspects: string[] = [];
const customPagination: ListAspectsOpts = { skipCount: 10, maxItems: 20 };
aspectListService.getAspects(visibleAspects, customPagination).subscribe(() => {
expect(aspectsApi.listAspects).toHaveBeenCalledWith(customPagination);
done();
});
});
it('should get one aspect', (done) => {
const visibleAspects = ['std:standardAspectOne'];
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([stdAspect1]);
done();
});
});
it('should get two aspects', (done) => {
const visibleAspects = ['std:standardAspectTwo', 'std:standardAspectThree'];
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([stdAspect2, stdAspect3]);
done();
});
});
it('should get all aspects (visible aspects as undefined)', (done) => {
const visibleAspects: string[] = undefined;
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([stdAspect1, stdAspect2, stdAspect3]);
done();
});
});
it('should get all aspects (visible aspects as empty array)', (done) => {
const visibleAspects: string[] = [];
aspectListService.getAspects(visibleAspects, aspectsOpts).subscribe((response) => {
expect(response.list.entries).toEqual([stdAspect1, stdAspect2, stdAspect3]);
done();
});
}); });
}); });
it('should get one custom aspect', (done) => { describe('getAllAspects', () => {
const visibleAspects = ['cst:customAspectTwo']; beforeEach(() => {
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(customAspectPagingMock)); spyOn(aspectsApi, 'listAspects').and.returnValues(Promise.resolve(standardAspectPagingMock), Promise.resolve(customAspectPagingMock));
aspectListService.getCustomAspects(visibleAspects).subscribe((response) => { });
expect(response).toEqual([cstAspect2]);
done(); it('should get all aspects (standard and custom) when visible aspects are empty', (done) => {
spyOn(appConfigService, 'get').and.returnValue(undefined);
aspectListService.getAllAspects(aspectsOpts, customAspectsOpts).subscribe((aspects) => {
expect(aspects.standardAspectPaging.list.entries).toEqual([stdAspect1, stdAspect2, stdAspect3]);
expect(aspects.customAspectPaging.list.entries).toEqual([cstAspect1, cstAspect2, cstAspect3]);
expect(aspectsApi.listAspects).toHaveBeenCalledTimes(2);
done();
});
});
it('should get all aspects (standard and custom) filtered by visible aspects', (done) => {
const visibleAspectConfig = {
standard: ['std:standardAspectOne', 'std:standardAspectTwo'],
custom: ['cst:customAspectOne']
};
spyOn(appConfigService, 'get').and.returnValue(visibleAspectConfig);
aspectListService.getAllAspects(aspectsOpts, customAspectsOpts).subscribe((aspects) => {
expect(aspects.standardAspectPaging.list.entries).toEqual([stdAspect1, stdAspect2]);
expect(aspects.customAspectPaging.list.entries).toEqual([cstAspect1]);
expect(aspectsApi.listAspects).toHaveBeenCalledTimes(2);
done();
});
}); });
}); });
it('should get two custom aspects', (done) => { describe('getVisibleAspects', () => {
const visibleAspects = ['cst:customAspectOne', 'cst:customAspectThree']; it('should return empty array when visible aspects are not provided in the config', () => {
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(customAspectPagingMock)); spyOn(appConfigService, 'get').and.returnValue(undefined);
aspectListService.getCustomAspects(visibleAspects).subscribe((response) => { const visibleAspects = aspectListService.getVisibleAspects();
expect(response).toEqual([cstAspect1, cstAspect3]); expect(visibleAspects).toEqual([]);
done();
}); });
});
it('should get all custom aspects (visible aspects as undefined)', (done) => { it('should return visible aspects from config when provided', () => {
const visibleAspects = undefined; const visibleAspectConfig = {
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(customAspectPagingMock)); standard: ['std:standardAspectOne', 'std:standardAspectTwo'],
aspectListService.getCustomAspects(visibleAspects).subscribe((response) => { custom: ['cst:customAspectOne']
expect(response).toEqual([cstAspect1, cstAspect2, cstAspect3]); };
done(); spyOn(appConfigService, 'get').and.returnValue(visibleAspectConfig);
}); const visibleAspects = aspectListService.getVisibleAspects();
}); expect(visibleAspects).toEqual(['std:standardAspectOne', 'std:standardAspectTwo', 'cst:customAspectOne']);
it('should get all custom aspects (visible aspects as empty array)', (done) => {
const visibleAspects = [];
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(customAspectPagingMock));
aspectListService.getCustomAspects(visibleAspects).subscribe((response) => {
expect(response).toEqual([cstAspect1, cstAspect2, cstAspect3]);
done();
});
});
it('should get all custom aspects (visible aspects not supplied)', (done) => {
spyOn(aspectsApi, 'listAspects').and.returnValue(Promise.resolve(customAspectPagingMock));
aspectListService.getCustomAspects().subscribe((response) => {
expect(response).toEqual([cstAspect1, cstAspect2, cstAspect3]);
done();
}); });
}); });
}); });

View File

@@ -20,7 +20,11 @@ import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AppConfigService } from '@alfresco/adf-core'; import { AppConfigService } from '@alfresco/adf-core';
import { from, Observable, of, zip } from 'rxjs'; import { from, Observable, of, zip } from 'rxjs';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { AspectEntry, AspectPaging, AspectsApi } from '@alfresco/js-api'; import { AspectEntry, AspectPaging, AspectsApi, ListAspectsOpts } from '@alfresco/js-api';
import { CustomAspectPaging } from '../interfaces/custom-aspect-paging.interface';
export const StandardAspectsWhere = `(modelId in ('cm:contentmodel', 'emailserver:emailserverModel', 'smf:smartFolder', 'app:applicationmodel' ))`;
export const CustomAspectsWhere = `(not namespaceUri matches('http://www.alfresco.*'))`;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -34,37 +38,24 @@ export class AspectListService {
constructor(private alfrescoApiService: AlfrescoApiService, private appConfigService: AppConfigService) {} constructor(private alfrescoApiService: AlfrescoApiService, private appConfigService: AppConfigService) {}
getAspects(): Observable<AspectEntry[]> { getAllAspects(standardOpts?: ListAspectsOpts, customOpts?: ListAspectsOpts): Observable<CustomAspectPaging> {
const visibleAspectList = this.getVisibleAspects(); const visibleAspectList = this.getVisibleAspects();
const standardAspects$ = this.getStandardAspects(visibleAspectList); const standardAspects$ = this.getAspects(visibleAspectList, standardOpts);
const customAspects$ = this.getCustomAspects(visibleAspectList); const customAspects$ = this.getAspects(visibleAspectList, customOpts);
return zip(standardAspects$, customAspects$).pipe( return zip(standardAspects$, customAspects$).pipe(
map(([standardAspectList, customAspectList]) => [...standardAspectList, ...customAspectList]) map(([standardAspectPaging, customAspectPaging]) => ({ standardAspectPaging, customAspectPaging }))
); );
} }
getStandardAspects(whiteList: string[]): Observable<AspectEntry[]> { getAspects(whiteList: string[], opts?: ListAspectsOpts): Observable<AspectPaging> {
const where = `(modelId in ('cm:contentmodel', 'emailserver:emailserverModel', 'smf:smartFolder', 'app:applicationmodel' ))`;
const opts: any = {
where,
include: ['properties']
};
return from(this.aspectsApi.listAspects(opts)).pipe( return from(this.aspectsApi.listAspects(opts)).pipe(
map((result: AspectPaging) => this.filterAspectByConfig(whiteList, result?.list?.entries)), map((result) => {
catchError(() => of([])) if (result?.list?.entries) {
); result.list.entries = this.filterAspectByConfig(whiteList, result.list.entries);
} }
return result;
getCustomAspects(whiteList?: string[]): Observable<AspectEntry[]> { }),
const where = `(not namespaceUri matches('http://www.alfresco.*'))`; catchError(() => of({ list: { entries: [] } }))
const opts: any = {
where,
include: ['properties']
};
return from(this.aspectsApi.listAspects(opts)).pipe(
map((result: AspectPaging) => this.filterAspectByConfig(whiteList, result?.list?.entries)),
catchError(() => of([]))
); );
} }