[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:
Vito
2021-02-17 11:13:35 +00:00
committed by GitHub
parent f7f80bc013
commit e62c752f1f
38 changed files with 1690 additions and 11 deletions

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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%;
}
}
}
}
}

View File

@@ -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();
});
});
});

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -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();
});
});
});

View File

@@ -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);
}
}

View File

@@ -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 { }

View File

@@ -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');
});
}));
});
});

View 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();
}
}

View 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';

View File

@@ -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');
});
});

View File

@@ -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);
});
});
}
}

View 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';

View File

@@ -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()"

View File

@@ -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');
});
});

View File

@@ -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);
}
}

View File

@@ -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
]
})

View File

@@ -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"
}
}
}

View File

@@ -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);
}

View File

@@ -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';