mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
[ADF-5305] - Added aspect list component (#6549)
* [ADF-5305] - Creation of aspect list and aspect list dialog components * [ADF-5305] - unit test for aspect list * [ADF-5305] - added filtering for aspects * [ADF-5305] - enabling tests * [ADF-5305] - added filtering and unit test * [ADF-5305] - added context action to demo shell * [ADF-5305] - added button on metadata card for opening aspects * [ADF-5305] - fixed unit test for filtering aspects * [ADF-5305] - added documentation * [ADF-5305] - fixed lint * [ADF-5305] - Updated the js-api calls * [ADF-5305] - Removed circle dependency * [ADF-5305] - Simplified code * [ADF-5305] - revert changes on package.json * [ADF-5305] - removed extra cspell word * [ADF-5305] - added filtering on aspect list service * [ADF-5305] - fix unit test for aspect service * [ADF-5305] - reverted changes to package-loc * [ADF-5305] - removed unused changes * [ADF-5305] - attempt to fix PR #§ * [ADF-5305] - attempt to fix PR #2 * [ADF-5305] - attempt to fix PR #3 * [ADF-5305] - attempt to fix PR #4 * [ADF-5305] - attempt to fix PR #5 Co-authored-by: Vito Albano <vitoalbano@vitoalbano-mbp-0120.local>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Subject } from 'rxjs';
|
||||
|
||||
export interface AspectListDialogComponentData {
|
||||
title: string;
|
||||
description: string;
|
||||
overTableMessage: string;
|
||||
select: Subject<string[]>;
|
||||
nodeId?: string;
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<header mat-dialog-title data-automation-id="aspect-list-dialog-title" class="adf-aspect-list-dialog">
|
||||
<h4 class="adf-aspect-list-dialog-title">{{title | translate}}</h4>
|
||||
<div class="adf-aspect-list-dialog-description">{{description | translate}}</div>
|
||||
</header>
|
||||
|
||||
<div class="adf-aspect-list-dialog-information">
|
||||
<p id="aspect-list-dialog-over-table-message">{{overTableMessage | translate}}</p>
|
||||
<p id="aspect-list-dialog-counter">{{currentAspectSelection ? currentAspectSelection.length : 0}}
|
||||
{{'ADF-ASPECT-LIST.DIALOG.SELECTED' | translate}}</p>
|
||||
</div>
|
||||
<mat-dialog-content class="adf-aspect-dialog-content">
|
||||
<adf-aspect-list #aspectList (valueChanged)="onValueChanged($event)" [nodeId]="currentNodeId">
|
||||
</adf-aspect-list>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<div>
|
||||
<button mat-button (click)="aspectList.reset()" id="aspect-list-dialog-actions-reset"
|
||||
data-automation-id="aspect-list-dialog-actions-reset">{{
|
||||
'ADF-ASPECT-LIST.DIALOG.RESET' | translate }}
|
||||
</button>
|
||||
|
||||
<button mat-button (click)="aspectList.clear()" id="aspect-list-dialog-actions-clear"
|
||||
data-automation-id="aspect-list-dialog-actions-clear">{{
|
||||
'ADF-ASPECT-LIST.DIALOG.CLEAR' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button mat-button (click)="onCancel()" id="aspect-list-dialog-actions-cancel"
|
||||
data-automation-id="aspect-list-dialog-actions-cancel">{{
|
||||
'ADF-ASPECT-LIST.DIALOG.CANCEL' | translate }}
|
||||
</button>
|
||||
|
||||
<button mat-button (click)="onApply()" id="aspect-list-dialog-actions-apply"
|
||||
data-automation-id="aspect-list-dialog-actions-apply">{{
|
||||
'ADF-ASPECT-LIST.DIALOG.APPLY' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
@@ -0,0 +1,55 @@
|
||||
@mixin adf-aspect-list-dialog-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
|
||||
.adf {
|
||||
|
||||
&-aspect-list-dialog-title {
|
||||
font-size: large;
|
||||
font-weight: 200;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&-aspect-list-dialog-description {
|
||||
font-size: small;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&-aspect-list-dialog-information {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
&-aspect-list-dialog {
|
||||
.mat-dialog-actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&-aspect-dialog-content {
|
||||
padding-top: 3px;
|
||||
|
||||
.adf-aspect-property-table {
|
||||
|
||||
.mat-cell {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.mat-column-name {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,272 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AspectListDialogComponent } from './aspect-list-dialog.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { AspectListDialogComponentData } from './aspect-list-dialog-data.interface';
|
||||
import { NodesApiService } from 'core';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { AspectEntry } from '@alfresco/js-api';
|
||||
|
||||
const aspectListMock: AspectEntry[] = [{
|
||||
entry: {
|
||||
parentId: 'frs:aspectZero',
|
||||
id: 'frs:AspectOne',
|
||||
description: 'First Aspect with random description',
|
||||
title: 'FirstAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'channelPassword',
|
||||
title: 'The authenticated channel password',
|
||||
dataType: 'd:encrypted'
|
||||
},
|
||||
{
|
||||
id: 'channelUsername',
|
||||
title: 'The authenticated channel username',
|
||||
dataType: 'd:encrypted'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
parentId: 'frs:AspectZer',
|
||||
id: 'frs:SecondAspect',
|
||||
description: 'Second Aspect description',
|
||||
title: 'SecondAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'assetId',
|
||||
title: 'Published Asset Id',
|
||||
dataType: 'd:text'
|
||||
},
|
||||
{
|
||||
id: 'assetUrl',
|
||||
title: 'Published Asset URL',
|
||||
dataType: 'd:text'
|
||||
}
|
||||
]
|
||||
}
|
||||
}];
|
||||
|
||||
describe('AspectListDialogComponent', () => {
|
||||
let fixture: ComponentFixture<AspectListDialogComponent>;
|
||||
let aspectListService: AspectListService;
|
||||
let nodeService: NodesApiService;
|
||||
let data: AspectListDialogComponentData;
|
||||
|
||||
describe('Without passing node id', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
data = <AspectListDialogComponentData> {
|
||||
title: 'Title',
|
||||
description: 'Description that can be longer or shorter',
|
||||
overTableMessage: 'Over here',
|
||||
select: new Subject<string[]>()
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule,
|
||||
MatDialogModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MAT_DIALOG_DATA, useValue: data },
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {
|
||||
keydownEvents: () => of(null),
|
||||
backdropClick: () => of(null),
|
||||
close: jasmine.createSpy('close')
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
aspectListService = TestBed.inject(AspectListService);
|
||||
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
|
||||
fixture = TestBed.createComponent(AspectListDialogComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should show 4 actions : CLEAR, RESET, CANCEL and APPLY', () => {
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel')).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-apply')).not.toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('#aspect-list-dialog-actions-apply')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should show basic information for the dialog', () => {
|
||||
const dialogTitle = fixture.nativeElement.querySelector('[data-automation-id="aspect-list-dialog-title"] .adf-aspect-list-dialog-title');
|
||||
expect(dialogTitle).not.toBeNull();
|
||||
expect(dialogTitle.innerText).toBe(data.title);
|
||||
|
||||
const dialogDescription = fixture.nativeElement.querySelector('[data-automation-id="aspect-list-dialog-title"] .adf-aspect-list-dialog-description');
|
||||
expect(dialogDescription).not.toBeNull();
|
||||
expect(dialogDescription.innerText).toBe(data.description);
|
||||
|
||||
const overTableMessage = fixture.nativeElement.querySelector('#aspect-list-dialog-over-table-message');
|
||||
expect(overTableMessage).not.toBeNull();
|
||||
expect(overTableMessage.innerText).toBe(data.overTableMessage);
|
||||
|
||||
const selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
});
|
||||
|
||||
it('should update the counter when an option is selcted and unselected', async () => {
|
||||
const firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
let selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('1 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
selectionCounter = fixture.nativeElement.querySelector('#aspect-list-dialog-counter');
|
||||
expect(selectionCounter).not.toBeNull();
|
||||
expect(selectionCounter.innerText).toBe('0 ADF-ASPECT-LIST.DIALOG.SELECTED');
|
||||
});
|
||||
|
||||
it('should reset to the node values when Reset button is clicked', async () => {
|
||||
let firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const resetButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-reset');
|
||||
expect(resetButton).toBeDefined();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
resetButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
firstAspectCheckbox = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox.checked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear all the value when Clear button is clicked', async () => {
|
||||
let firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
firstAspectCheckbox.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const clearButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-clear');
|
||||
expect(clearButton).toBeDefined();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
clearButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
firstAspectCheckbox = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox.checked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should complete the select stream Cancel button is clicked', (done) => {
|
||||
data.select.subscribe(() => { }, () => { }, () => done());
|
||||
const cancelButton: HTMLButtonElement = fixture.nativeElement.querySelector('#aspect-list-dialog-actions-cancel');
|
||||
expect(cancelButton).toBeDefined();
|
||||
cancelButton.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Passing the node id', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
data = <AspectListDialogComponentData> {
|
||||
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(null),
|
||||
backdropClick: () => of(null)
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
await TestBed.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
aspectListService = TestBed.inject(AspectListService);
|
||||
nodeService = TestBed.inject(NodesApiService);
|
||||
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
|
||||
spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']);
|
||||
spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne'] }).pipe(delay(0)));
|
||||
fixture = TestBed.createComponent(AspectListDialogComponent);
|
||||
fixture.componentInstance.data.select = new Subject<string[]>();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should show checked the current aspects of the node', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenRenderingDone();
|
||||
const firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AspectListDialogComponentData } from './aspect-list-dialog-data.interface';
|
||||
@Component({
|
||||
selector: 'adf-aspect-list-dialog',
|
||||
templateUrl: './aspect-list-dialog.component.html',
|
||||
styleUrls: ['./aspect-list-dialog.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AspectListDialogComponent implements OnInit {
|
||||
|
||||
title: string;
|
||||
description: string;
|
||||
currentNodeId: string;
|
||||
overTableMessage: string;
|
||||
|
||||
currentAspectSelection: string[] = [];
|
||||
|
||||
constructor(private dialog: MatDialogRef<AspectListDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: AspectListDialogComponentData) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.overTableMessage = data.overTableMessage;
|
||||
this.currentNodeId = data.nodeId;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dialog.backdropClick().subscribe(() => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onValueChanged(aspectList: string[]) {
|
||||
this.currentAspectSelection = aspectList;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.data.select.complete();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
onApply() {
|
||||
this.data.select.next(this.currentAspectSelection);
|
||||
this.close();
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<div id="aspect-list-container" class="adf-aspect-list-container">
|
||||
<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-header [id]="'aspect-list-'+aspect?.entry?.title+'header'">
|
||||
<mat-panel-title>
|
||||
<mat-checkbox class="adf-aspect-list-check-button" [id]="'aspect-list-'+colIndex+'-check'"
|
||||
[checked]="nodeAspects?.includes(aspect?.entry?.id)"
|
||||
(click)="onCheckBoxClick($event)"
|
||||
(change)="onChange($event, aspect?.entry?.id)">
|
||||
</mat-checkbox>
|
||||
<p class="adf-aspect-list-element-title">{{aspect?.entry?.title}}</p>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description [id]="'aspect-list-'+colIndex+'-title'"
|
||||
[matTooltip]="aspect?.entry?.title">
|
||||
{{aspect?.entry?.title}}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<p class="adf-property-paragraph" [id]="'aspect-list-'+colIndex+'-description'"> {{aspect?.entry?.description}}</p>
|
||||
|
||||
<table mat-table [dataSource]="aspect?.entry?.properties" *ngIf="aspect?.entry?.properties?.length > 0" class="adf-aspect-property-table" [id]="'aspect-list-'+colIndex+'-properties-table'">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'ADF-ASPECT-LIST.PROPERTY_NAME' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.id}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'ADF-ASPECT-LIST.DESCRIPTION' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.title}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="dataType">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'ADF-ASPECT-LIST.DATA_TYPE' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.dataType}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="propertyColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: propertyColumns;"></tr>
|
||||
</table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
@@ -0,0 +1,72 @@
|
||||
@mixin adf-aspect-list-theme($theme) {
|
||||
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.adf {
|
||||
|
||||
&-aspect-list-container {
|
||||
|
||||
padding-top: 3px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
|
||||
.adf-aspect-list-check-button {
|
||||
margin-right: 5px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.adf-aspect-list-element-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.adf-accordion-aspect-list {
|
||||
|
||||
.mat-expansion-panel-spacing {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
font-size: smaller;
|
||||
border-right-style: inset;
|
||||
border-left-style: outset;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex: 1 1 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-aspect-property-table {
|
||||
width: 100%;
|
||||
|
||||
.mat-column-name {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.mat-column-description {
|
||||
width: 65%;
|
||||
}
|
||||
|
||||
.mat-column-type {
|
||||
width: 20%;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,221 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NodesApiService, setupTestBed } from '@alfresco/adf-core';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AspectListComponent } from './aspect-list.component';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
import { of } from 'rxjs';
|
||||
import { AspectEntry } from '@alfresco/js-api';
|
||||
|
||||
const aspectListMock: AspectEntry[] = [{
|
||||
entry: {
|
||||
parentId: 'frs:aspectZero',
|
||||
id: 'frs:AspectOne',
|
||||
description: 'First Aspect with random description',
|
||||
title: 'FirstAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'channelPassword',
|
||||
title: 'The authenticated channel password',
|
||||
dataType: 'd:propA'
|
||||
},
|
||||
{
|
||||
id: 'channelUsername',
|
||||
title: 'The authenticated channel username',
|
||||
dataType: 'd:propB'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
parentId: 'frs:AspectZer',
|
||||
id: 'frs:SecondAspect',
|
||||
description: 'Second Aspect description',
|
||||
title: 'SecondAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'assetId',
|
||||
title: 'Published Asset Id',
|
||||
dataType: 'd:text'
|
||||
},
|
||||
{
|
||||
id: 'assetUrl',
|
||||
title: 'Published Asset URL',
|
||||
dataType: 'd:text'
|
||||
}
|
||||
]
|
||||
}
|
||||
}];
|
||||
|
||||
describe('AspectListComponent', () => {
|
||||
|
||||
let component: AspectListComponent;
|
||||
let fixture: ComponentFixture<AspectListComponent>;
|
||||
let aspectListService: AspectListService;
|
||||
let nodeService: NodesApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule
|
||||
],
|
||||
providers: [AspectListService]
|
||||
});
|
||||
|
||||
describe('When passing a node id', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AspectListComponent);
|
||||
component = fixture.componentInstance;
|
||||
aspectListService = TestBed.inject(AspectListService);
|
||||
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
|
||||
spyOn(aspectListService, 'getVisibleAspects').and.returnValue(['frs:AspectOne']);
|
||||
nodeService = TestBed.inject(NodesApiService);
|
||||
spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node-id', aspectNames: ['frs:AspectOne'] }));
|
||||
component.nodeId = 'fake-node-id';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should show all the aspects', () => {
|
||||
const firstElement = fixture.nativeElement.querySelector('#aspect-list-FirstAspect');
|
||||
const secondElement = fixture.nativeElement.querySelector('#aspect-list-SecondAspect');
|
||||
|
||||
expect(firstElement).not.toBeNull();
|
||||
expect(firstElement).toBeDefined();
|
||||
expect(secondElement).not.toBeNull();
|
||||
expect(secondElement).toBeDefined();
|
||||
});
|
||||
|
||||
it('should show the details when a row is clicked', () => {
|
||||
const firstElement = fixture.nativeElement.querySelector('#aspect-list-FirstAspect');
|
||||
firstElement.click();
|
||||
fixture.detectChanges();
|
||||
const firstElementDesc = fixture.nativeElement.querySelector('#aspect-list-0-description');
|
||||
expect(firstElementDesc).not.toBeNull();
|
||||
expect(firstElementDesc).toBeDefined();
|
||||
|
||||
const firstElementPropertyTable = fixture.nativeElement.querySelector('#aspect-list-0-properties-table');
|
||||
expect(firstElementPropertyTable).not.toBeNull();
|
||||
expect(firstElementPropertyTable).toBeDefined();
|
||||
const nameProperties = fixture.nativeElement.querySelectorAll('#aspect-list-0-properties-table tbody .mat-column-name');
|
||||
expect(nameProperties[0]).not.toBeNull();
|
||||
expect(nameProperties[0]).toBeDefined();
|
||||
expect(nameProperties[0].innerText).toBe('channelPassword');
|
||||
expect(nameProperties[1]).not.toBeNull();
|
||||
expect(nameProperties[1]).toBeDefined();
|
||||
expect(nameProperties[1].innerText).toBe('channelUsername');
|
||||
|
||||
const titleProperties = fixture.nativeElement.querySelectorAll('#aspect-list-0-properties-table tbody .mat-column-title');
|
||||
expect(titleProperties[0]).not.toBeNull();
|
||||
expect(titleProperties[0]).toBeDefined();
|
||||
expect(titleProperties[0].innerText).toBe('The authenticated channel password');
|
||||
expect(titleProperties[1]).not.toBeNull();
|
||||
expect(titleProperties[1]).toBeDefined();
|
||||
expect(titleProperties[1].innerText).toBe('The authenticated channel username');
|
||||
|
||||
const dataTypeProperties = fixture.nativeElement.querySelectorAll('#aspect-list-0-properties-table tbody .mat-column-dataType');
|
||||
expect(dataTypeProperties[0]).not.toBeNull();
|
||||
expect(dataTypeProperties[0]).toBeDefined();
|
||||
expect(dataTypeProperties[0].innerText).toBe('d:propA');
|
||||
expect(dataTypeProperties[1]).not.toBeNull();
|
||||
expect(dataTypeProperties[1]).toBeDefined();
|
||||
expect(dataTypeProperties[1].innerText).toBe('d:propB');
|
||||
});
|
||||
|
||||
it('should show as checked the node properties', () => {
|
||||
const firstAspectCheckbox: HTMLInputElement = fixture.nativeElement.querySelector('#aspect-list-0-check-input');
|
||||
expect(firstAspectCheckbox).toBeDefined();
|
||||
expect(firstAspectCheckbox).not.toBeNull();
|
||||
expect(firstAspectCheckbox.checked).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should remove aspects unchecked', (done) => {
|
||||
const secondElement = fixture.nativeElement.querySelector('#aspect-list-1-check-input');
|
||||
expect(secondElement).toBeDefined();
|
||||
expect(secondElement).not.toBeNull();
|
||||
expect(secondElement.checked).toBeFalsy();
|
||||
secondElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.nodeAspects.length).toBe(2);
|
||||
expect(component.nodeAspects[1]).toBe('frs:SecondAspect');
|
||||
component.valueChanged.subscribe((aspects) => {
|
||||
expect(aspects.length).toBe(1);
|
||||
expect(aspects[0]).toBe('frs:AspectOne');
|
||||
done();
|
||||
});
|
||||
secondElement.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should reset the properties on reset', (done) => {
|
||||
const secondElement = fixture.nativeElement.querySelector('#aspect-list-1-check-input');
|
||||
expect(secondElement).toBeDefined();
|
||||
expect(secondElement).not.toBeNull();
|
||||
expect(secondElement.checked).toBeFalsy();
|
||||
secondElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.nodeAspects.length).toBe(2);
|
||||
component.valueChanged.subscribe((aspects) => {
|
||||
expect(aspects.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
component.reset();
|
||||
});
|
||||
|
||||
it('should clear all the properties on clear', (done) => {
|
||||
expect(component.nodeAspects.length).toBe(1);
|
||||
component.valueChanged.subscribe((aspects) => {
|
||||
expect(aspects.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
component.clear();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When no node id is passed', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AspectListComponent);
|
||||
component = fixture.componentInstance;
|
||||
aspectListService = TestBed.inject(AspectListService);
|
||||
spyOn(aspectListService, 'getAspects').and.returnValue(of(aspectListMock));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should show all the aspects', () => {
|
||||
const firstElement = fixture.nativeElement.querySelector('#aspect-list-FirstAspect');
|
||||
const secondElement = fixture.nativeElement.querySelector('#aspect-list-SecondAspect');
|
||||
|
||||
expect(firstElement).not.toBeNull();
|
||||
expect(firstElement).toBeDefined();
|
||||
expect(secondElement).not.toBeNull();
|
||||
expect(secondElement).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,99 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { NodesApiService } from '@alfresco/adf-core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { concatMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||
import { AspectEntry } from '@alfresco/js-api';
|
||||
@Component({
|
||||
selector: 'adf-aspect-list',
|
||||
templateUrl: './aspect-list.component.html',
|
||||
styleUrls: ['./aspect-list.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
|
||||
export class AspectListComponent implements OnInit, OnDestroy {
|
||||
|
||||
/** Node Id of the node that we want to update */
|
||||
@Input()
|
||||
nodeId: string = '';
|
||||
|
||||
/** Emitted every time the user select a new aspect */
|
||||
@Output()
|
||||
valueChanged: EventEmitter<string[]> = new EventEmitter<string[]>();
|
||||
|
||||
propertyColumns: string[] = ['name', 'title', 'dataType'];
|
||||
aspects$: Observable<AspectEntry[]> = null;
|
||||
nodeAspects: string[] = [];
|
||||
nodeAspectStatus: string[] = null;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(private aspectListService: AspectListService, private nodeApiService: NodesApiService) {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.nodeId) {
|
||||
this.aspects$ = this.nodeApiService.getNode(this.nodeId).pipe(
|
||||
tap((node) => {
|
||||
this.nodeAspects = node.aspectNames.filter((aspect) => this.aspectListService.getVisibleAspects().includes(aspect));
|
||||
this.nodeAspectStatus = Array.from(node.aspectNames);
|
||||
this.valueChanged.emit(this.nodeAspects);
|
||||
}),
|
||||
concatMap(() => this.aspectListService.getAspects()),
|
||||
takeUntil(this.onDestroy$));
|
||||
} else {
|
||||
this.aspects$ = this.aspectListService.getAspects()
|
||||
.pipe(takeUntil(this.onDestroy$));
|
||||
}
|
||||
}
|
||||
|
||||
onCheckBoxClick(event: Event) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
onChange(change: MatCheckboxChange, prefixedName: string) {
|
||||
if (change.checked) {
|
||||
this.nodeAspects.push(prefixedName);
|
||||
} else {
|
||||
this.nodeAspects.splice(this.nodeAspects.indexOf(prefixedName), 1);
|
||||
}
|
||||
this.valueChanged.emit(this.nodeAspects);
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.nodeAspectStatus && this.nodeAspectStatus.length > 0) {
|
||||
this.nodeAspects.splice(0, this.nodeAspects.length, ...this.nodeAspectStatus);
|
||||
this.valueChanged.emit(this.nodeAspects);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.nodeAspects = [];
|
||||
this.valueChanged.emit(this.nodeAspects);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AspectListComponent } from './aspect-list.component';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { PipeModule } from '@alfresco/adf-core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
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';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatTableModule,
|
||||
MatExpansionModule,
|
||||
MatCheckboxModule,
|
||||
PipeModule,
|
||||
TranslateModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
MatTooltipModule
|
||||
],
|
||||
exports: [
|
||||
AspectListComponent,
|
||||
AspectListDialogComponent
|
||||
],
|
||||
declarations: [
|
||||
AspectListComponent,
|
||||
AspectListDialogComponent
|
||||
]
|
||||
})
|
||||
export class AspectListModule { }
|
@@ -0,0 +1,166 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { 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 { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
|
||||
const aspectListMock: AspectEntry[] = [{
|
||||
entry: {
|
||||
parentId: 'frs:aspectZero',
|
||||
id: 'frs:AspectOne',
|
||||
description: 'First Aspect with random description',
|
||||
title: 'FirstAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'channelPassword',
|
||||
title: 'The authenticated channel password',
|
||||
dataType: 'd:propA'
|
||||
},
|
||||
{
|
||||
id: 'channelUsername',
|
||||
title: 'The authenticated channel username',
|
||||
dataType: 'd:propB'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
parentId: 'frs:AspectZer',
|
||||
id: 'frs:SecondAspect',
|
||||
description: 'Second Aspect description',
|
||||
title: 'SecondAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'assetId',
|
||||
title: 'Published Asset Id',
|
||||
dataType: 'd:text'
|
||||
},
|
||||
{
|
||||
id: 'assetUrl',
|
||||
title: 'Published Asset URL',
|
||||
dataType: 'd:text'
|
||||
}
|
||||
]
|
||||
}
|
||||
}];
|
||||
|
||||
const customAspectListMock: AspectEntry[] = [{
|
||||
entry: {
|
||||
parentId: 'frs:aspectZero',
|
||||
id: 'frs:AspectCustom',
|
||||
description: 'First Aspect with random description',
|
||||
title: 'FirstAspect',
|
||||
properties: [
|
||||
{
|
||||
id: 'channelPassword',
|
||||
title: 'The authenticated channel password',
|
||||
dataType: 'd:propA'
|
||||
},
|
||||
{
|
||||
id: 'channelUsername',
|
||||
title: 'The authenticated channel username',
|
||||
dataType: 'd:propB'
|
||||
}
|
||||
]
|
||||
}
|
||||
}];
|
||||
|
||||
const listAspectResp: AspectPaging = {
|
||||
list : {
|
||||
entries: aspectListMock
|
||||
}
|
||||
};
|
||||
|
||||
const customListAspectResp: AspectPaging = {
|
||||
list : {
|
||||
entries: customAspectListMock
|
||||
}
|
||||
};
|
||||
|
||||
describe('AspectListService', () => {
|
||||
|
||||
describe('should open the dialog', () => {
|
||||
let service: AspectListService;
|
||||
let materialDialog: MatDialog;
|
||||
let spyOnDialogOpen: jasmine.Spy;
|
||||
let spyOnDialogClose: jasmine.Spy;
|
||||
const afterOpenObservable: Subject<any> = new Subject();
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule,
|
||||
MatDialogModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(AspectListService);
|
||||
materialDialog = TestBed.inject(MatDialog);
|
||||
spyOnDialogOpen = spyOn(materialDialog, 'open').and.returnValue({
|
||||
afterOpen: () => afterOpenObservable,
|
||||
afterClosed: () => of({}),
|
||||
componentInstance: {
|
||||
error: new Subject<any>()
|
||||
}
|
||||
});
|
||||
spyOnDialogClose = spyOn(materialDialog, 'closeAll');
|
||||
});
|
||||
|
||||
it('should open the aspect list dialog', () => {
|
||||
service.openAspectListDialog();
|
||||
expect(spyOnDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close the dialog', () => {
|
||||
service.close();
|
||||
expect(spyOnDialogClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should fetch the list of the aspects', () => {
|
||||
|
||||
let service: AspectListService;
|
||||
const appConfigService: AppConfigService = new AppConfigService(null);
|
||||
|
||||
const aspectTypesApi = jasmine.createSpyObj('AspectsApi', ['listAspects']);
|
||||
const apiService: AlfrescoApiService = new AlfrescoApiService(null, null);
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(appConfigService, 'get').and.returnValue({ 'default': ['frs:AspectOne'] });
|
||||
spyOnProperty(apiService, 'aspectsApi').and.returnValue(aspectTypesApi);
|
||||
service = new AspectListService(apiService, appConfigService, null);
|
||||
});
|
||||
|
||||
it('should get the list of only available aspects', async(() => {
|
||||
aspectTypesApi.listAspects.and.returnValues(of(listAspectResp), of(customListAspectResp));
|
||||
service.getAspects().subscribe((list) => {
|
||||
expect(list.length).toBe(2);
|
||||
expect(list[0].entry.id).toBe('frs:AspectOne');
|
||||
expect(list[1].entry.id).toBe('frs:AspectCustom');
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
112
lib/content-services/src/lib/aspect-list/aspect-list.service.ts
Normal file
112
lib/content-services/src/lib/aspect-list/aspect-list.service.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { 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 { AspectListDialogComponentData } from './aspect-list-dialog-data.interface';
|
||||
import { AspectListDialogComponent } from './aspect-list-dialog.component';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AspectEntry, AspectPaging } from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AspectListService {
|
||||
|
||||
constructor(private alfrescoApiService: AlfrescoApiService,
|
||||
private appConfigService: AppConfigService, private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
getAspects(): Observable<AspectEntry[]> {
|
||||
const visibleAspectList = this.getVisibleAspects();
|
||||
const standardAspects$ = this.getStandardAspects(visibleAspectList);
|
||||
const customAspects$ = this.getCustomAspects();
|
||||
return zip(standardAspects$, customAspects$).pipe(
|
||||
map(([standardAspectList, customAspectList]) => [...standardAspectList, ...customAspectList])
|
||||
);
|
||||
}
|
||||
|
||||
getStandardAspects(whiteList: string[]): Observable<AspectEntry[]> {
|
||||
const where = `(modelIds in ('cm:contentmodel', 'emailserver:emailserverModel', 'smf:smartFolder', 'app:applicationmodel' ))`;
|
||||
return from(this.alfrescoApiService.aspectsApi.listAspects(where))
|
||||
.pipe(
|
||||
map((result: AspectPaging) => this.filterAspectByConfig(whiteList, result?.list?.entries))
|
||||
);
|
||||
}
|
||||
|
||||
getCustomAspects(): Observable<AspectEntry[]> {
|
||||
const where = `(not namespaceUri matches('http://www.alfresco.*')`;
|
||||
return from(this.alfrescoApiService.aspectsApi.listAspects(where))
|
||||
.pipe(
|
||||
map((result: AspectPaging) => result?.list?.entries)
|
||||
);
|
||||
}
|
||||
|
||||
private filterAspectByConfig(visibleAspectList: string[], aspectEntries: AspectEntry[]): AspectEntry[] {
|
||||
let result = aspectEntries ? aspectEntries : [];
|
||||
if (visibleAspectList?.length > 0 && aspectEntries) {
|
||||
result = aspectEntries.filter((value) => {
|
||||
return visibleAspectList.includes(value?.entry?.id);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getVisibleAspects(): string[] {
|
||||
let visibleAspectList: string[] = [];
|
||||
const aspectVisibleConfig = this.appConfigService.get('aspect-visible');
|
||||
if (aspectVisibleConfig) {
|
||||
for (const aspectGroup of Object.keys(aspectVisibleConfig)) {
|
||||
visibleAspectList = visibleAspectList.concat(aspectVisibleConfig[aspectGroup]);
|
||||
}
|
||||
}
|
||||
return visibleAspectList;
|
||||
}
|
||||
|
||||
openAspectListDialog(nodeId?: string): Observable<string[]> {
|
||||
const select = new Subject<string[]>();
|
||||
select.subscribe({
|
||||
complete: this.close.bind(this)
|
||||
});
|
||||
|
||||
const data: AspectListDialogComponentData = {
|
||||
title: 'ADF-ASPECT-LIST.DIALOG.TITLE',
|
||||
description: 'ADF-ASPECT-LIST.DIALOG.DESCRIPTION',
|
||||
overTableMessage: 'ADF-ASPECT-LIST.DIALOG.OVER-TABLE-MESSAGE',
|
||||
select,
|
||||
nodeId
|
||||
};
|
||||
|
||||
this.openDialog(data, 'adf-aspect-list-dialog', '750px');
|
||||
return select;
|
||||
}
|
||||
|
||||
private openDialog(data: AspectListDialogComponentData, panelClass: string, width: string) {
|
||||
this.dialog.open(AspectListDialogComponent, {
|
||||
data,
|
||||
panelClass,
|
||||
width,
|
||||
disableClose: true
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialog.closeAll();
|
||||
}
|
||||
}
|
18
lib/content-services/src/lib/aspect-list/index.ts
Normal file
18
lib/content-services/src/lib/aspect-list/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
@@ -0,0 +1,74 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AlfrescoApiService, NodesApiService, setupTestBed } from 'core';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
import { NodeAspectService } from './node-aspect.service';
|
||||
|
||||
describe('NodeAspectService', () => {
|
||||
|
||||
let aspectListService: AspectListService;
|
||||
let nodeAspectService: NodeAspectService;
|
||||
let nodeApiService: NodesApiService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
aspectListService = TestBed.inject(AspectListService);
|
||||
nodeAspectService = TestBed.inject(NodeAspectService);
|
||||
nodeApiService = TestBed.inject(NodesApiService);
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
});
|
||||
|
||||
it('should open the aspect list dialog', () => {
|
||||
spyOn(aspectListService, 'openAspectListDialog').and.returnValue(of([]));
|
||||
spyOn(nodeApiService, 'updateNode').and.returnValue(of({}));
|
||||
nodeAspectService.updateNodeAspects('fake-node-id');
|
||||
expect(aspectListService.openAspectListDialog).toHaveBeenCalledWith('fake-node-id');
|
||||
});
|
||||
|
||||
it('should update the node when the aspect dialog apply the changes', () => {
|
||||
const expectedParameters = { aspectNames: ['a', 'b', 'c'] };
|
||||
spyOn(aspectListService, 'openAspectListDialog').and.returnValue(of(['a', 'b', 'c']));
|
||||
spyOn(nodeApiService, 'updateNode').and.returnValue(of({}));
|
||||
nodeAspectService.updateNodeAspects('fake-node-id');
|
||||
expect(nodeApiService.updateNode).toHaveBeenCalledWith('fake-node-id', expectedParameters);
|
||||
});
|
||||
|
||||
it('should send and update node event once the node has been updated', (done) => {
|
||||
alfrescoApiService.nodeUpdated.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');
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService, NodesApiService } from '@alfresco/adf-core';
|
||||
import { AspectListService } from './aspect-list.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NodeAspectService {
|
||||
|
||||
constructor(private alfrescoApiService: AlfrescoApiService,
|
||||
private nodesApiService: NodesApiService,
|
||||
private aspectListService: AspectListService) {
|
||||
}
|
||||
|
||||
updateNodeAspects(nodeId: string) {
|
||||
this.aspectListService.openAspectListDialog(nodeId).subscribe((aspectList) => {
|
||||
this.nodesApiService.updateNode(nodeId, { aspectNames: [...aspectList] }).subscribe((updatedNode) => {
|
||||
this.alfrescoApiService.nodeUpdated.next(updatedNode);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
25
lib/content-services/src/lib/aspect-list/public-api.ts
Normal file
25
lib/content-services/src/lib/aspect-list/public-api.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './aspect-list.component';
|
||||
export * from './aspect-list-dialog.component';
|
||||
export * from './aspect-list.service';
|
||||
export * from './node-aspect.service';
|
||||
|
||||
export * from './aspect-list-dialog-data.interface';
|
||||
|
||||
export * from './aspect-list.module';
|
@@ -13,6 +13,14 @@
|
||||
</mat-card-content>
|
||||
<mat-card-footer class="adf-content-metadata-card-footer" fxLayout="row" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<button *ngIf="!readOnly && hasAllowableOperations()"
|
||||
mat-icon-button
|
||||
(click)="openAspectDialog()"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT_ASPECTS' | translate"
|
||||
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT_ASPECTS' | translate"
|
||||
data-automation-id="meta-data-card-edit-aspect">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="!readOnly && hasAllowableOperations()"
|
||||
mat-icon-button
|
||||
(click)="toggleEdit()"
|
||||
|
@@ -24,6 +24,7 @@ import { setupTestBed, AllowableOperationsEnum } from '@alfresco/adf-core';
|
||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NodeAspectService } from 'content-services/src/lib/aspect-list';
|
||||
import { ContentMetadataService } from '../../services/content-metadata.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@@ -34,6 +35,7 @@ describe('ContentMetadataCardComponent', () => {
|
||||
let contentMetadataService: ContentMetadataService;
|
||||
let node: Node;
|
||||
const preset = 'custom-preset';
|
||||
let nodeAspectService: NodeAspectService = null;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
@@ -57,6 +59,7 @@ describe('ContentMetadataCardComponent', () => {
|
||||
|
||||
component.node = node;
|
||||
component.preset = preset;
|
||||
nodeAspectService = TestBed.inject(NodeAspectService);
|
||||
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -211,4 +214,18 @@ describe('ContentMetadataCardComponent', () => {
|
||||
component.ngOnChanges({ displayAspect });
|
||||
expect(component.expanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call the aspect dialog when edit aspect is clicked', () => {
|
||||
component.editable = true;
|
||||
component.node.id = 'fake-node-id';
|
||||
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
|
||||
spyOn(nodeAspectService, 'updateNodeAspects').and.stub();
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-edit-aspect"]'));
|
||||
button.triggerEventHandler('click', {});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(nodeAspectService.updateNodeAspects).toHaveBeenCalledWith('fake-node-id');
|
||||
});
|
||||
});
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { ContentService, AllowableOperationsEnum } from '@alfresco/adf-core';
|
||||
|
||||
import { NodeAspectService } from '../../../aspect-list/node-aspect.service';
|
||||
@Component({
|
||||
selector: 'adf-content-metadata-card',
|
||||
templateUrl: './content-metadata-card.component.html',
|
||||
@@ -27,6 +27,7 @@ import { ContentService, AllowableOperationsEnum } from '@alfresco/adf-core';
|
||||
host: { 'class': 'adf-content-metadata-card' }
|
||||
})
|
||||
export class ContentMetadataCardComponent implements OnChanges {
|
||||
|
||||
/** (required) The node entity to fetch metadata about */
|
||||
@Input()
|
||||
node: Node;
|
||||
@@ -80,7 +81,7 @@ export class ContentMetadataCardComponent implements OnChanges {
|
||||
|
||||
expanded: boolean;
|
||||
|
||||
constructor(private contentService: ContentService) {
|
||||
constructor(private contentService: ContentService, private nodeAspectService: NodeAspectService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@@ -104,4 +105,8 @@ export class ContentMetadataCardComponent implements OnChanges {
|
||||
hasAllowableOperations() {
|
||||
return this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATE);
|
||||
}
|
||||
|
||||
openAspectDialog() {
|
||||
this.nodeAspectService.updateNodeAspects(this.node.id);
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ import { ContentMetadataModule } from './content-metadata/content-metadata.modul
|
||||
import { PermissionManagerModule } from './permission-manager/permission-manager.module';
|
||||
import { TreeViewModule } from './tree-view/tree-view.module';
|
||||
import { ContentTypeModule } from './content-type/content-type.module';
|
||||
import { AspectListModule } from './aspect-list/aspect-list.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -65,7 +66,8 @@ import { ContentTypeModule } from './content-type/content-type.module';
|
||||
PermissionManagerModule,
|
||||
VersionManagerModule,
|
||||
TreeViewModule,
|
||||
ContentTypeModule
|
||||
ContentTypeModule,
|
||||
AspectListModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -95,6 +97,7 @@ import { ContentTypeModule } from './content-type/content-type.module';
|
||||
PermissionManagerModule,
|
||||
VersionManagerModule,
|
||||
TreeViewModule,
|
||||
AspectListModule,
|
||||
ContentTypeModule
|
||||
]
|
||||
})
|
||||
|
@@ -459,5 +459,20 @@
|
||||
"ACCESSIBILITY": {
|
||||
"ARIA_LABEL": "Open {{ name }}"
|
||||
}
|
||||
},
|
||||
"ADF-ASPECT-LIST" : {
|
||||
"PROPERTY_NAME": "Property Name",
|
||||
"DESCRIPTION": "Description",
|
||||
"DATA_TYPE": "Data Type",
|
||||
"DIALOG" : {
|
||||
"TITLE" : "Customize Properties",
|
||||
"DESCRIPTION": "Manage the properties associated with selected file(s). Choose from property aspects listed below, to expose and apply additional metadata and funcitonality",
|
||||
"RESET": "Reset",
|
||||
"CLEAR": "Clear",
|
||||
"CANCEL": "Cancel",
|
||||
"APPLY": "Apply",
|
||||
"OVER-TABLE-MESSAGE" : "Select property aspects",
|
||||
"SELECTED": "Selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,8 @@
|
||||
@import '../tree-view/components/tree-view.component';
|
||||
@import '../version-manager/version-comparison.component';
|
||||
@import '../content-type/content-type-dialog.component';
|
||||
@import '../aspect-list/aspect-list.component';
|
||||
@import '../aspect-list//aspect-list-dialog.component';
|
||||
|
||||
@mixin adf-content-services-theme($theme) {
|
||||
@include adf-breadcrumb-theme($theme);
|
||||
@@ -54,4 +56,6 @@
|
||||
@include adf-search-chip-list-theme($theme);
|
||||
@include adf-version-comparison-theme($theme);
|
||||
@include adf-content-type-dialog-theme($theme);
|
||||
@include adf-aspect-list-theme($theme);
|
||||
@include adf-aspect-list-dialog-theme($theme);
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ export * from './lib/permission-manager/index';
|
||||
export * from './lib/content-node-share/index';
|
||||
export * from './lib/tree-view/index';
|
||||
export * from './lib/group/index';
|
||||
export * from './lib/aspect-list/index';
|
||||
export * from './lib/content-type/index';
|
||||
|
||||
export * from './lib/content.module';
|
||||
|
Reference in New Issue
Block a user