[ACS-6252] support disabling the tags and categories feature in the applications (#9106)

* ACS-6252 Added rules field to SearchCategory interface

* ACS-6252 Hide aspects related with tags and categories if tags and categories features  are disabled

* ACS-6252 Return from services information if tags and categories are disabled

* ACS-6252 Unit tests for changes in AspectListDialogComponent

* ACS-6252 Unit tests for changes for AspectListComponent

* ACS-6252 Unit tests for DialogAspectListService

* ACS-6252 Unit tests for changes for TagService and CategoryService

* ACS-6252 Updated documentation for changes

* ACS-6252 Fixed imports formatting

* ACS-6252 Fix after rebasing

* ACS-6252 Addressed PR comments

* ACS-6252 Excluded e2es
This commit is contained in:
AleksanderSklorz
2023-11-28 11:41:32 +01:00
committed by GitHub
parent 979bf3ac59
commit 7793aba89e
18 changed files with 337 additions and 154 deletions

View File

@@ -29,6 +29,7 @@ export interface AspectListDialogComponentData {
overTableMessage: string; overTableMessage: string;
select: Subject<string[]>; select: Subject<string[]>;
nodeId?: string; nodeId?: string;
excludedAspects?: string[];
} }
``` ```
@@ -41,6 +42,7 @@ The properties are described in the table below:
| overTableMessage | `string` | "" | Text that will be showed on the top of the aspect list table | | overTableMessage | `string` | "" | Text that will be showed on the top of the aspect list table |
| select | [`Subject<Node>`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/Node.md) | | Event emitted with the current node selection when the dialog closes | | select | [`Subject<Node>`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/Node.md) | | Event emitted with the current node selection when the dialog closes |
| nodeId | `string` | "" | Identifier of a node to apply aspects to. | | nodeId | `string` | "" | Identifier of a node to apply aspects to. |
| excludedAspects | `string[]` | undefined | List of aspects' ids which should not be displayed. |
If you don't want to manage the dialog yourself then it is easier to use the If you don't want to manage the dialog yourself then it is easier to use the
[Aspect List component](aspect-list.component.md), or the [Aspect List component](aspect-list.component.md), or the

View File

@@ -30,6 +30,7 @@ The aspect are filtered via the app.config.json in this way :
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| nodeId | `string` | "" | Node Id of the node that we want to update | | nodeId | `string` | "" | Node Id of the node that we want to update |
| excludedAspects | `string[]` | undefined | List of aspects' ids which should not be displayed. |
### Events ### Events

View File

@@ -65,6 +65,9 @@ Manages categories in Content Services.
- _nodeId:_ `string` - The identifier of a node. - _nodeId:_ `string` - The identifier of a node.
- _categoryLinkBodyCreate:_ [`CategoryLinkBody[]`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryLinkBody.md) - Categories that node will be linked to. - _categoryLinkBodyCreate:_ [`CategoryLinkBody[]`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryLinkBody.md) - Categories that node will be linked to.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`]((https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md))` | `[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>` - Categories that node has been linked to. - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`]((https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md))` | `[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>` - Categories that node has been linked to.
- **areCategoriesEnabled**():`boolean`<br/>
Checks if categories plugin is enabled.
- **Returns** `boolean` - true if categories plugin is enabled, false otherwise.
## Details ## Details

View File

@@ -62,6 +62,9 @@ Manages tags in Content Services.
- _tagId:_ `string` - The identifier of a tag. - _tagId:_ `string` - The identifier of a tag.
- _tagBody:_ `TagBody` - The updated tag. - _tagBody:_ `TagBody` - The updated tag.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`>` - Updated tag. - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`>` - Updated tag.
- **areTagsEnabled**():`boolean`<br/>
Checks if tags plugin is enabled.
- **Returns** `boolean` - true if tags plugin is enabled, false otherwise.
## Details ## Details

View File

@@ -277,6 +277,9 @@ export interface SearchCategory {
selector: string; selector: string;
settings: SearchWidgetSettings; settings: SearchWidgetSettings;
}; };
rules?: {
visible: string;
};
} }
``` ```

View File

@@ -19,5 +19,10 @@
"C279971": "https://alfresco.atlassian.net/browse/ACS-6233", "C279971": "https://alfresco.atlassian.net/browse/ACS-6233",
"C279972": "https://alfresco.atlassian.net/browse/ACS-6233", "C279972": "https://alfresco.atlassian.net/browse/ACS-6233",
"C260181": "https://alfresco.atlassian.net/browse/ACS-6233", "C260181": "https://alfresco.atlassian.net/browse/ACS-6233",
"C297692": "https://alfresco.atlassian.net/browse/ACS-6244" "C297692": "https://alfresco.atlassian.net/browse/ACS-6244",
"C260418": "https://alfresco.atlassian.net/browse/ACS-6425",
"C268070": "https://alfresco.atlassian.net/browse/ACS-6425",
"C272812": "https://alfresco.atlassian.net/browse/ACS-6425",
"C274704": "https://alfresco.atlassian.net/browse/ACS-6425",
"C311425": "https://alfresco.atlassian.net/browse/ACS-6425"
} }

View File

@@ -23,4 +23,5 @@ export interface AspectListDialogComponentData {
overTableMessage: string; overTableMessage: string;
select: Subject<string[]>; select: Subject<string[]>;
nodeId?: string; nodeId?: string;
excludedAspects?: string[];
} }

View File

@@ -9,7 +9,7 @@
{{'ADF-ASPECT-LIST.DIALOG.SELECTED' | translate}}</p> {{'ADF-ASPECT-LIST.DIALOG.SELECTED' | translate}}</p>
</div> </div>
<mat-dialog-content class="adf-aspect-dialog-content"> <mat-dialog-content class="adf-aspect-dialog-content">
<adf-aspect-list #aspectList (valueChanged)="onValueChanged($event)" [nodeId]="currentNodeId"> <adf-aspect-list #aspectList (valueChanged)="onValueChanged($event)" [nodeId]="currentNodeId" [excludedAspects]="data.excludedAspects">
</adf-aspect-list> </adf-aspect-list>
</mat-dialog-content> </mat-dialog-content>

View File

@@ -26,6 +26,8 @@ import { AspectListService } from './services/aspect-list.service';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { AspectEntry, Node } from '@alfresco/js-api'; import { AspectEntry, Node } from '@alfresco/js-api';
import { NodesApiService } from '../common/services/nodes-api.service'; import { NodesApiService } from '../common/services/nodes-api.service';
import { By } from '@angular/platform-browser';
import { AspectListComponent } from './aspect-list.component';
const aspectListMock: AspectEntry[] = [ const aspectListMock: AspectEntry[] = [
{ {
@@ -103,35 +105,35 @@ describe('AspectListDialogComponent', () => {
keyCode: 27 keyCode: 27
} as KeyboardEventInit); } as KeyboardEventInit);
describe('Without passing node id', () => { beforeEach(async () => {
beforeEach(async () => { data = {
data = { title: 'Title',
title: 'Title', description: 'Description that can be longer or shorter',
description: 'Description that can be longer or shorter', overTableMessage: 'Over here',
overTableMessage: 'Over here', select: new Subject<string[]>(),
select: new Subject<string[]>() excludedAspects: []
}; };
await TestBed.configureTestingModule({
TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), ContentTestingModule, MatDialogModule],
imports: [TranslateModule.forRoot(), ContentTestingModule, MatDialogModule], providers: [
providers: [ { provide: MAT_DIALOG_DATA, useValue: data },
{ provide: MAT_DIALOG_DATA, useValue: data }, {
{ provide: MatDialogRef,
provide: MatDialogRef, useValue: {
useValue: { keydownEvents: () => of(event),
keydownEvents: () => of(event), backdropClick: () => of(null),
backdropClick: () => of(null), close: jasmine.createSpy('close')
close: jasmine.createSpy('close')
}
} }
] }
}).compileComponents(); ]
}); }).compileComponents();
fixture = TestBed.createComponent(AspectListDialogComponent);
});
describe('Without passing node id', () => {
beforeEach(() => { beforeEach(() => {
aspectListService = TestBed.inject(AspectListService); aspectListService = TestBed.inject(AspectListService);
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock)); spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
fixture = TestBed.createComponent(AspectListDialogComponent);
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -243,32 +245,7 @@ describe('AspectListDialogComponent', () => {
describe('Passing the node id', () => { describe('Passing the node id', () => {
beforeEach(async () => { beforeEach(async () => {
data = { data.nodeId = 'fake-node-id';
title: 'Title',
description: 'Description that can be longer or shorter',
overTableMessage: 'Over here',
select: new Subject<string[]>(),
nodeId: 'fake-node-id'
};
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), ContentTestingModule, MatDialogModule],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: data },
{
provide: MatDialogRef,
useValue: {
close: jasmine.createSpy('close'),
keydownEvents: () => of(event),
backdropClick: () => of(null)
}
}
]
});
await TestBed.compileComponents();
});
beforeEach(async () => {
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, 'getAspects').and.returnValue(of([...aspectListMock, ...customAspectListMock]));
@@ -278,7 +255,6 @@ describe('AspectListDialogComponent', () => {
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))
); );
fixture = TestBed.createComponent(AspectListDialogComponent); fixture = TestBed.createComponent(AspectListDialogComponent);
fixture.componentInstance.data.select = new Subject<string[]>();
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
}); });
@@ -328,4 +304,14 @@ describe('AspectListDialogComponent', () => {
expect(applyButton.disabled).toBe(false); expect(applyButton.disabled).toBe(false);
}); });
}); });
describe('AspectListComponent', () => {
it('should have set excludedAspects from dialog data', () => {
data.excludedAspects = ['some aspect 1', 'some aspect 2'];
fixture.detectChanges();
expect(fixture.debugElement.query(By.directive(AspectListComponent)).componentInstance.excludedAspects)
.toBe(data.excludedAspects);
});
});
}); });

View File

@@ -21,9 +21,8 @@ import { ContentTestingModule } from '../testing/content.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AspectListComponent } from './aspect-list.component'; import { AspectListComponent } from './aspect-list.component';
import { AspectListService } from './services/aspect-list.service'; import { AspectListService } from './services/aspect-list.service';
import { of } from 'rxjs'; import { EMPTY, of } from 'rxjs';
import { AspectEntry } from '@alfresco/js-api'; import { AspectEntry } from '@alfresco/js-api';
import { delay } from 'rxjs/operators';
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';
@@ -136,9 +135,8 @@ describe('AspectListComponent', () => {
}); });
it('should show the loading spinner when result is loading', async () => { it('should show the loading spinner when result is loading', async () => {
const delayResult = of(null).pipe(delay(0)); spyOn(nodeService, 'getNode').and.returnValue(EMPTY);
spyOn(nodeService, 'getNode').and.returnValue(delayResult); spyOn(aspectListService, 'getAspects').and.returnValue(EMPTY);
spyOn(aspectListService, 'getAspects').and.returnValue(delayResult);
fixture.detectChanges(); fixture.detectChanges();
expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(true); expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(true);
@@ -156,7 +154,6 @@ describe('AspectListComponent', () => {
nodeService = TestBed.inject(NodesApiService); nodeService = TestBed.inject(NodesApiService);
spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne'] } as any)); spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne'] } as any));
component.nodeId = 'fake-node-id'; component.nodeId = 'fake-node-id';
fixture.detectChanges();
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
}); });
@@ -164,89 +161,105 @@ describe('AspectListComponent', () => {
fixture.destroy(); fixture.destroy();
}); });
it('should return true when same aspect list selected', () => { describe('without excluding aspects', () => {
expect(component.hasEqualAspect).toBe(true); beforeEach(() => {
fixture.detectChanges();
});
it('should return true when same aspect list selected', () => {
expect(component.hasEqualAspect).toBe(true);
});
it('should return false when different aspect list selected', () => {
component.clear();
expect(component.hasEqualAspect).toBe(false);
});
it('should show all the aspects', async () => {
expect(await loader.hasHarness(MatExpansionPanelHarness.with({selector: '#aspect-list-FirstAspect'}))).toBe(true);
expect(await loader.hasHarness(MatExpansionPanelHarness.with({selector: '#aspect-list-SecondAspect'}))).toBe(true);
});
it('should show aspect id when name or title is not set', () => {
const noNameAspect = fixture.nativeElement.querySelector('#aspect-list-cst-nonamedAspect .adf-aspect-list-element-title');
expect(noNameAspect).toBeDefined();
expect(noNameAspect).not.toBeNull();
expect(noNameAspect.innerText).toBe('cst:nonamedAspect');
});
it('should show the details when a row is clicked', async () => {
const panel = await loader.getHarness(MatExpansionPanelHarness);
await panel.expand();
expect(await panel.getDescription()).not.toBeNull();
const table = await panel.getHarness(MatTableHarness);
const [row1, row2] = await table.getRows();
const [r1c1, r1c2, r1c3] = await row1.getCells();
expect(await r1c1.getText()).toBe('channelPassword');
expect(await r1c2.getText()).toBe('The authenticated channel password');
expect(await r1c3.getText()).toBe('d:propA');
const [r2c1, r2c2, r2c3] = await row2.getCells();
expect(await r2c1.getText()).toBe('channelUsername');
expect(await r2c2.getText()).toBe('The authenticated channel username');
expect(await r2c3.getText()).toBe('d:propB');
});
it('should show as checked the node properties', async () => {
const panel = await loader.getHarness(MatExpansionPanelHarness);
await panel.expand();
const checkbox = await panel.getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(true);
});
it('should remove aspects unchecked', async () => {
const panel = await loader.getAllHarnesses(MatExpansionPanelHarness);
await panel[1].expand();
const checkbox = await panel[1].getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(false);
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(2);
expect(component.nodeAspects[1]).toBe('frs:SecondAspect');
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(1);
expect(component.nodeAspects[0]).toBe('frs:AspectOne');
});
it('should reset the properties on reset', async () => {
const panel = await loader.getAllHarnesses(MatExpansionPanelHarness);
await panel[1].expand();
const checkbox = await panel[1].getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(false);
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(2);
component.reset();
expect(component.nodeAspects.length).toBe(1);
});
it('should clear all the properties on clear', async () => {
expect(component.nodeAspects.length).toBe(1);
component.clear();
expect(component.nodeAspects.length).toBe(0);
});
}); });
it('should return false when different aspect list selected', () => { describe('with excluded aspects', () => {
component.clear(); it('should not show aspect if it is excluded aspect', () => {
expect(component.hasEqualAspect).toBe(false); component.excludedAspects = ['cst:nonamedAspect'];
});
it('should show all the aspects', async () => { fixture.detectChanges();
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBe(true); expect(fixture.nativeElement.querySelector(`#aspect-list-${component.excludedAspects[0].replace(':', '-')}`))
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBe(true); .toBeNull();
}); });
it('should show aspect id when name or title is not set', () => {
const noNameAspect = fixture.nativeElement.querySelector('#aspect-list-cst-nonamedAspect .adf-aspect-list-element-title');
expect(noNameAspect).toBeDefined();
expect(noNameAspect).not.toBeNull();
expect(noNameAspect.innerText).toBe('cst:nonamedAspect');
});
it('should show the details when a row is clicked', async () => {
const panel = await loader.getHarness(MatExpansionPanelHarness);
await panel.expand();
expect(await panel.getDescription()).not.toBeNull();
const table = await panel.getHarness(MatTableHarness);
const [row1, row2] = await table.getRows();
const [r1c1, r1c2, r1c3] = await row1.getCells();
expect(await r1c1.getText()).toBe('channelPassword');
expect(await r1c2.getText()).toBe('The authenticated channel password');
expect(await r1c3.getText()).toBe('d:propA');
const [r2c1, r2c2, r2c3] = await row2.getCells();
expect(await r2c1.getText()).toBe('channelUsername');
expect(await r2c2.getText()).toBe('The authenticated channel username');
expect(await r2c3.getText()).toBe('d:propB');
});
it('should show as checked the node properties', async () => {
const panel = await loader.getHarness(MatExpansionPanelHarness);
await panel.expand();
const checkbox = await panel.getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(true);
});
it('should remove aspects unchecked', async () => {
const panel = await loader.getAllHarnesses(MatExpansionPanelHarness);
await panel[1].expand();
const checkbox = await panel[1].getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(false);
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(2);
expect(component.nodeAspects[1]).toBe('frs:SecondAspect');
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(1);
expect(component.nodeAspects[0]).toBe('frs:AspectOne');
});
it('should reset the properties on reset', async () => {
const panel = await loader.getAllHarnesses(MatExpansionPanelHarness);
await panel[1].expand();
const checkbox = await panel[1].getHarness(MatCheckboxHarness);
expect(await checkbox.isChecked()).toBe(false);
await checkbox.toggle();
expect(component.nodeAspects.length).toBe(2);
component.reset();
expect(component.nodeAspects.length).toBe(1);
});
it('should clear all the properties on clear', async () => {
expect(component.nodeAspects.length).toBe(1);
component.clear();
expect(component.nodeAspects.length).toBe(0);
}); });
}); });
@@ -256,7 +269,6 @@ describe('AspectListComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
aspectListService = TestBed.inject(AspectListService); aspectListService = TestBed.inject(AspectListService);
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock)); spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
fixture.detectChanges();
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
}); });
@@ -265,8 +277,17 @@ describe('AspectListComponent', () => {
}); });
it('should show all the aspects', async () => { it('should show all the aspects', async () => {
fixture.detectChanges();
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBe(true); expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBe(true);
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBe(true); expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBe(true);
}); });
it('should not show excluded aspects', async () => {
component.excludedAspects = ['frs:AspectOne', 'frs:SecondAspect'];
fixture.detectChanges();
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-FirstAspect' }))).toBeFalse();
expect(await loader.hasHarness(MatExpansionPanelHarness.with({ selector: '#aspect-list-SecondAspect' }))).toBeFalse();
});
}); });
}); });

View File

@@ -35,6 +35,10 @@ export class AspectListComponent implements OnInit, OnDestroy {
@Input() @Input()
nodeId: string = ''; nodeId: string = '';
/** List of aspects' ids which should not be displayed. */
@Input()
excludedAspects?: string[] = [];
/** Emitted every time the user select a new aspect */ /** Emitted every time the user select a new aspect */
@Output() @Output()
valueChanged: EventEmitter<string[]> = new EventEmitter<string[]>(); valueChanged: EventEmitter<string[]> = new EventEmitter<string[]>();
@@ -56,13 +60,14 @@ export class AspectListComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
let aspects$: Observable<AspectEntry[]>;
if (this.nodeId) { if (this.nodeId) {
const node$ = this.nodeApiService.getNode(this.nodeId); const node$ = this.nodeApiService.getNode(this.nodeId);
const customAspect$ = this.aspectListService.getCustomAspects(this.aspectListService.getVisibleAspects()) const customAspect$ = this.aspectListService.getCustomAspects(this.aspectListService.getVisibleAspects())
.pipe(map( .pipe(map(
(customAspects) => customAspects.flatMap((customAspect) => customAspect.entry.id) (customAspects) => customAspects.flatMap((customAspect) => customAspect.entry.id)
)); ));
this.aspects$ = zip(node$, customAspect$).pipe( aspects$ = zip(node$, customAspect$).pipe(
tap(([node, customAspects]) => { tap(([node, customAspects]) => {
this.nodeAspects = node.aspectNames.filter((aspect) => this.aspectListService.getVisibleAspects().includes(aspect) || customAspects.includes(aspect)); this.nodeAspects = node.aspectNames.filter((aspect) => this.aspectListService.getVisibleAspects().includes(aspect) || customAspects.includes(aspect));
this.nodeAspectStatus = [ ...this.nodeAspects ]; this.nodeAspectStatus = [ ...this.nodeAspects ];
@@ -71,9 +76,11 @@ export class AspectListComponent implements OnInit, OnDestroy {
concatMap(() => this.aspectListService.getAspects()), concatMap(() => this.aspectListService.getAspects()),
takeUntil(this.onDestroy$)); takeUntil(this.onDestroy$));
} else { } else {
this.aspects$ = this.aspectListService.getAspects() aspects$ = this.aspectListService.getAspects()
.pipe(takeUntil(this.onDestroy$)); .pipe(takeUntil(this.onDestroy$));
} }
this.aspects$ = aspects$.pipe(map((aspects) =>
aspects.filter((aspect) => !this.excludedAspects.includes(aspect.entry.id))));
} }
onCheckBoxClick(event: Event) { onCheckBoxClick(event: Event) {

View File

@@ -17,10 +17,14 @@
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from '../../testing/content.testing.module'; import { ContentTestingModule } from '../../testing/content.testing.module';
import { DialogAspectListService } from '@alfresco/adf-content-services'; import { DialogAspectListService } from './dialog-aspect-list.service';
import { AspectListDialogComponent } from '../aspect-list-dialog.component';
import { AspectListDialogComponentData } from '../aspect-list-dialog-data.interface';
import { CategoryService } from '../../category/services/category.service';
import { TagService } from '../../tag/services/tag.service';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
import { Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
describe('DialogAspectListService', () => { describe('DialogAspectListService', () => {
let dialogAspectListService: DialogAspectListService; let dialogAspectListService: DialogAspectListService;
@@ -71,5 +75,67 @@ describe('DialogAspectListService', () => {
afterClosed$.next(); afterClosed$.next();
expect(document.querySelector).not.toHaveBeenCalled(); expect(document.querySelector).not.toHaveBeenCalled();
}); });
describe('Calling open on dialog', () => {
let expectedDialogConfig: MatDialogConfig<AspectListDialogComponentData>;
beforeEach(() => {
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => new Observable<void>()
} as MatDialogRef<any>);
expectedDialogConfig = {
data: {
title: 'ADF-ASPECT-LIST.DIALOG.TITLE',
description: 'ADF-ASPECT-LIST.DIALOG.DESCRIPTION',
overTableMessage: 'ADF-ASPECT-LIST.DIALOG.OVER-TABLE-MESSAGE',
excludedAspects: [],
select: jasmine.any(Subject) as any,
nodeId: undefined
},
panelClass: 'adf-aspect-list-dialog',
width: '750px',
role: 'dialog',
disableClose: true
};
});
it('should call open on dialog with correct parameters when tagService.areTagsEnabled returns true', () => {
const tagService = TestBed.inject(TagService);
spyOn(tagService, 'areTagsEnabled').and.returnValue(true);
dialogAspectListService.openAspectListDialog();
expect(tagService.areTagsEnabled).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalledWith(AspectListDialogComponent, expectedDialogConfig);
});
it('should call open on dialog with correct parameters when tagService.areTagsEnabled returns false', () => {
const tagService = TestBed.inject(TagService);
spyOn(tagService, 'areTagsEnabled').and.returnValue(false);
expectedDialogConfig.data.excludedAspects = ['cm:taggable'];
dialogAspectListService.openAspectListDialog();
expect(tagService.areTagsEnabled).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalledWith(AspectListDialogComponent, expectedDialogConfig);
});
it('should call open on dialog with correct parameters when categoryService.areCategoriesEnabled returns true', () => {
const categoryService = TestBed.inject(CategoryService);
spyOn(categoryService, 'areCategoriesEnabled').and.returnValue(true);
dialogAspectListService.openAspectListDialog();
expect(categoryService.areCategoriesEnabled).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalledWith(AspectListDialogComponent, expectedDialogConfig);
});
it('should call open on dialog with correct parameters when categoryService.areCategoriesEnabled returns false', () => {
const categoryService = TestBed.inject(CategoryService);
spyOn(categoryService, 'areCategoriesEnabled').and.returnValue(false);
expectedDialogConfig.data.excludedAspects = ['cm:generalclassifiable'];
dialogAspectListService.openAspectListDialog();
expect(categoryService.areCategoriesEnabled).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalledWith(AspectListDialogComponent, expectedDialogConfig);
});
});
}); });
}); });

View File

@@ -21,14 +21,20 @@ import { Observable, Subject } from 'rxjs';
import { AspectListDialogComponentData } from '../aspect-list-dialog-data.interface'; import { AspectListDialogComponentData } from '../aspect-list-dialog-data.interface';
import { AspectListDialogComponent } from '../aspect-list-dialog.component'; import { AspectListDialogComponent } from '../aspect-list-dialog.component';
import { OverlayContainer } from '@angular/cdk/overlay'; import { OverlayContainer } from '@angular/cdk/overlay';
import { TagService } from '../../tag';
import { CategoryService } from '../../category';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DialogAspectListService { export class DialogAspectListService {
constructor(private dialog: MatDialog, private overlayContainer: OverlayContainer) { constructor(
} private dialog: MatDialog,
private overlayContainer: OverlayContainer,
private tagService: TagService,
private categoryService: CategoryService
) {}
openAspectListDialog(nodeId?: string, selectorAutoFocusedOnClose?: string): Observable<string[]> { openAspectListDialog(nodeId?: string, selectorAutoFocusedOnClose?: string): Observable<string[]> {
const select = new Subject<string[]>(); const select = new Subject<string[]>();
@@ -41,7 +47,11 @@ export class DialogAspectListService {
description: 'ADF-ASPECT-LIST.DIALOG.DESCRIPTION', description: 'ADF-ASPECT-LIST.DIALOG.DESCRIPTION',
overTableMessage: 'ADF-ASPECT-LIST.DIALOG.OVER-TABLE-MESSAGE', overTableMessage: 'ADF-ASPECT-LIST.DIALOG.OVER-TABLE-MESSAGE',
select, select,
nodeId nodeId,
excludedAspects: [
...this.tagService.areTagsEnabled() ? [] : ['cm:taggable'],
...this.categoryService.areCategoriesEnabled() ? [] : ['cm:generalclassifiable']
]
}; };
this.openDialog(data, 'adf-aspect-list-dialog', '750px', selectorAutoFocusedOnClose); this.openDialog(data, 'adf-aspect-list-dialog', '750px', selectorAutoFocusedOnClose);

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { CoreTestingModule, UserPreferencesService } from '@alfresco/adf-core'; import { AppConfigService, CoreTestingModule, UserPreferencesService } from '@alfresco/adf-core';
import { import {
CategoryBody, CategoryBody,
CategoryEntry, CategoryEntry,
@@ -177,4 +177,27 @@ describe('CategoryService', () => {
expect(linkCategoriesSpy).toHaveBeenCalledOnceWith(fakeNodeId, fakeCategoriesLinkBodies); expect(linkCategoriesSpy).toHaveBeenCalledOnceWith(fakeNodeId, fakeCategoriesLinkBodies);
}); });
})); }));
describe('areCategoriesEnabled', () => {
let getSpy: jasmine.Spy<(key: string, defaultValue?: boolean) => boolean>;
beforeEach(() => {
getSpy = spyOn(TestBed.inject(AppConfigService), 'get');
});
it('should call get on AppConfigService with correct parameters', () => {
categoryService.areCategoriesEnabled();
expect(getSpy).toHaveBeenCalledWith('plugins.categories', true);
});
it('should return true if get from AppConfigService returns true', () => {
getSpy.and.returnValue(true);
expect(categoryService.areCategoriesEnabled()).toBeTrue();
});
it('should return false if get from AppConfigService returns false', () => {
getSpy.and.returnValue(false);
expect(categoryService.areCategoriesEnabled()).toBeFalse();
});
});
}); });

View File

@@ -16,7 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; import { AlfrescoApiService, AppConfigService, UserPreferencesService } from '@alfresco/adf-core';
import { CategoriesApi, CategoryBody, CategoryEntry, CategoryLinkBody, CategoryPaging, ResultSetPaging, SearchApi } from '@alfresco/js-api'; import { CategoriesApi, CategoryBody, CategoryEntry, CategoryLinkBody, CategoryPaging, ResultSetPaging, SearchApi } from '@alfresco/js-api';
import { from, Observable } from 'rxjs'; import { from, Observable } from 'rxjs';
@@ -35,7 +35,11 @@ export class CategoryService {
return this._searchApi; return this._searchApi;
} }
constructor(private apiService: AlfrescoApiService, private userPreferencesService: UserPreferencesService) {} constructor(
private apiService: AlfrescoApiService,
private userPreferencesService: UserPreferencesService,
private appConfigService: AppConfigService
) {}
/** /**
* Get subcategories of a given parent category * Get subcategories of a given parent category
@@ -152,4 +156,13 @@ export class CategoryService {
linkNodeToCategory(nodeId: string, categoryLinkBodyCreate: CategoryLinkBody[]): Observable<CategoryPaging | CategoryEntry> { linkNodeToCategory(nodeId: string, categoryLinkBodyCreate: CategoryLinkBody[]): Observable<CategoryPaging | CategoryEntry> {
return from(this.categoriesApi.linkNodeToCategory(nodeId, categoryLinkBodyCreate)); return from(this.categoriesApi.linkNodeToCategory(nodeId, categoryLinkBodyCreate));
} }
/**
* Checks if categories plugin is enabled.
*
* @returns boolean true if categories plugin is enabled, false otherwise.
*/
areCategoriesEnabled(): boolean {
return this.appConfigService.get('plugins.categories', true);
}
} }

View File

@@ -27,4 +27,7 @@ export interface SearchCategory {
selector: string; selector: string;
settings: SearchWidgetSettings; settings: SearchWidgetSettings;
}; };
rules?: {
visible: string;
};
} }

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { UserPreferencesService } from '@alfresco/adf-core'; import { AppConfigService, UserPreferencesService } from '@alfresco/adf-core';
import { TagService } from './tag.service'; import { TagService } from './tag.service';
import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ContentTestingModule } from '../../testing/content.testing.module'; import { ContentTestingModule } from '../../testing/content.testing.module';
@@ -339,5 +339,28 @@ describe('TagService', () => {
tick(); tick();
})); }));
}); });
describe('areTagsEnabled', () => {
let getSpy: jasmine.Spy<(key: string, defaultValue?: boolean) => boolean>;
beforeEach(() => {
getSpy = spyOn(TestBed.inject(AppConfigService), 'get');
});
it('should call get on AppConfigService with correct parameters', () => {
service.areTagsEnabled();
expect(getSpy).toHaveBeenCalledWith('plugins.tags', true);
});
it('should return true if get from AppConfigService returns true', () => {
getSpy.and.returnValue(true);
expect(service.areTagsEnabled()).toBeTrue();
});
it('should return false if get from AppConfigService returns false', () => {
getSpy.and.returnValue(false);
expect(service.areTagsEnabled()).toBeFalse();
});
});
}); });
}); });

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; import { AlfrescoApiService, AppConfigService, UserPreferencesService } from '@alfresco/adf-core';
import { EventEmitter, Injectable, Output } from '@angular/core'; import { EventEmitter, Injectable, Output } from '@angular/core';
import { from, Observable } from 'rxjs'; import { from, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
@@ -35,7 +35,11 @@ export class TagService {
@Output() @Output()
refresh = new EventEmitter(); refresh = new EventEmitter();
constructor(private apiService: AlfrescoApiService, private userPreferencesService: UserPreferencesService) {} constructor(
private apiService: AlfrescoApiService,
private userPreferencesService: UserPreferencesService,
private appConfigService: AppConfigService
) {}
/** /**
* Gets a list of tags added to a node. * Gets a list of tags added to a node.
@@ -172,4 +176,13 @@ export class TagService {
assignTagsToNode(nodeId: string, tags: TagBody[]): Observable<TagPaging | TagEntry> { assignTagsToNode(nodeId: string, tags: TagBody[]): Observable<TagPaging | TagEntry> {
return from(this.tagsApi.assignTagsToNode(nodeId, tags)).pipe(tap((data) => this.refresh.emit(data))); return from(this.tagsApi.assignTagsToNode(nodeId, tags)).pipe(tap((data) => this.refresh.emit(data)));
} }
/**
* Checks if tags plugin is enabled.
*
* @returns boolean true if tags plugin is enabled, false otherwise.
*/
areTagsEnabled(): boolean {
return this.appConfigService.get('plugins.tags', true);
}
} }