Skip to content

Commit

Permalink
[ADF-5316] - Content Type (#6602)
Browse files Browse the repository at this point in the history
* [ADF-5316] - added content type editing for nodes

* [ADF-5316] - added content type editing for nodes

* [ADF-5316] - fix unit test #1

* [ADF-5316] - fix unit test #2

* [ADF-5316] - fix unit test - final

* Removed failing lint word

* [ADF-5316] - added alfresco api real calls

* Build fixed

* [ADF-5316] - fixed second loop trigger on model

* [ADF-5316] - fixed unit tests

* [ADF-5316] - removed unused stream

* [ADF-5316] - fixed package.json

* [ADF-5316] - added missing unit tests

* [ADF-5316] - fixed wrong import

Co-authored-by: Vito Albano <vitoalbano@vitoalbano-mbp-0120.local>
  • Loading branch information
VitoAlbano and Vito Albano committed Feb 12, 2021
1 parent 0b66ee8 commit eb9e555
Show file tree
Hide file tree
Showing 31 changed files with 981 additions and 33 deletions.
86 changes: 86 additions & 0 deletions docs/content-services/components/content-type-dialog.component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
Title: Content Type Dialog component
Added: v2.0.0
Status: Active
Last reviewed: 2021-01-20
---

# [Content Type Dialog component](../../../lib/content-services/src/lib/content-type/content-type-dialog.component.ts "Defined in content-type-dialog.component.ts")

Confirm dialog when user changes content type of a node.

## Details

The [Content Type Dialog component](content-type-dialog.component.md) works as a dialog showing a confirm message when the user changes the conten type of a node. It is showing the properties of the new content type selected.

### Showing the dialog

Unlike most components, the [Content Type Dialog component](content-type-dialog.component.md) is typically shown in a dialog box
rather than the main page and you are responsible for opening the dialog yourself. You can use the
[Angular Material Dialog](https://material.angular.io/components/dialog/overview) for this,
as shown in the usage example. ADF provides the [`ContentTypeDialogComponentData`](../../../lib/content-services/src/lib/content-type/content-type-metadata.interface.ts) interface
to work with the Dialog's
[data option](https://material.angular.io/components/dialog/overview#sharing-data-with-the-dialog-component-):

```ts
export interface ContentTypeDialogComponentData {
title: string;
description: string;
confirmMessage: string;
select: Subject<boolean>;
nodeType?: string;
}
```

The properties are described in the table below:

| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| title | `string` | "" | Dialog title |
| description | `string` | "" | Text to appear as description under the dialog title |
| confirmMessage | `string` | "" | Text that will be showed on the top of properties list accordion |
| select | [`Subject<Node>`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/Node.md) | | Event emitted when apply button is clicked |
| nodeType | `string` | "" | current prefixed name of the content type selected |

If you don't want to manage the dialog yourself then it is easier to use the
methods of the Content Type Property Service, which create
the dialog for you.

### Usage example

```ts
import { MatDialog } from '@angular/material/dialog';
import { AspectListDialogComponentData, AspectListDialogComponent} from '@adf/content-services'
import { Subject } from 'rxjs/Subject';
...
constructor(dialog: MatDialog ... ) {}
openSelectorDialog() {
const data: ContentTypeDialogComponentData = {
title: 'CORE.METADATA.CONTENT_TYPE.DIALOG.TITLE',
description: 'CORE.METADATA.CONTENT_TYPE.DIALOG.DESCRIPTION',
confirmMessage: 'CORE.METADATA.CONTENT_TYPE.DIALOG.CONFIRM',
select: select,
nodeType
};
this.dialog.open(
ContentTypeDialogComponent,
{
data, panelClass: 'adf-content-type-dialog',
width: '630px'
}
);
data.select.subscribe((selections: Node[]) => {
// Use or store selection...
},
(error)=>{
//your error handling
},
()=>{
//action called when an action or cancel is clicked on the dialog
this.dialog.closeAll();
});
}
```

All the results will be streamed to the select [subject](http://reactivex.io/rxjs/manual/overview.html#subject) present in the [`ContentTypeDialogData`](../../../lib/content-services/src/lib/content-type/content-type-metadata.interface.ts) object passed to the dialog.
When the dialog action is selected by clicking, the `data.select` stream will be completed.
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ 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 { ContentMetadataService } from '../../services/content-metadata.service';
import { of } from 'rxjs';

describe('ContentMetadataCardComponent', () => {

let component: ContentMetadataCardComponent;
let fixture: ComponentFixture<ContentMetadataCardComponent>;
let contentMetadataService: ContentMetadataService;
let node: Node;
const preset = 'custom-preset';

Expand All @@ -41,6 +44,7 @@ describe('ContentMetadataCardComponent', () => {

beforeEach(() => {
fixture = TestBed.createComponent(ContentMetadataCardComponent);
contentMetadataService = TestBed.inject(ContentMetadataService);
component = fixture.componentInstance;
node = <Node> {
aspectNames: [],
Expand All @@ -53,6 +57,7 @@ describe('ContentMetadataCardComponent', () => {

component.node = node;
component.preset = preset;
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
fixture.detectChanges();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
{{ 'CORE.METADATA.BASIC.HEADER' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>

<adf-card-view
(keydown)="keyDown($event)"
[properties]="basicProperties$ | async"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('ContentMetadataComponent', () => {
node = <Node> {
id: 'node-id',
aspectNames: [],
nodeType: '',
nodeType: 'cm:node',
content: {},
properties: {},
createdByUser: {},
Expand All @@ -75,6 +75,7 @@ describe('ContentMetadataComponent', () => {

component.node = node;
component.preset = preset;
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
fixture.detectChanges();
});

Expand Down Expand Up @@ -169,10 +170,33 @@ describe('ContentMetadataComponent', () => {
saveButton.nativeElement.click();
fixture.detectChanges();
}));

it('should open the confirm dialog when content type is changed', fakeAsync(() => {
component.editable = true;
const property = <CardViewBaseItemModel> { key: 'nodeType', value: 'ft:sbiruli' };
const expectedNode = Object.assign({}, node, { nodeType: 'ft:sbiruli' });
spyOn(contentMetadataService, 'openConfirmDialog').and.returnValue(of(true));
spyOn(nodesApiService, 'updateNode').and.callFake(() => {
return of(expectedNode);
});

updateService.update(property, 'ft:poppoli');
tick(600);

fixture.detectChanges();
tick(100);
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
saveButton.nativeElement.click();

tick(100);
expect(component.node).toEqual(expectedNode);
expect(contentMetadataService.openConfirmDialog).toHaveBeenCalledWith({nodeType: 'ft:poppoli'});
expect(nodesApiService.updateNode).toHaveBeenCalled();
}));
});

describe('Reseting', () => {
it('should reset changedProperties on reset click', async(async () => {
it('should reset changedProperties on reset click', async () => {
component.changedProperties = { properties: { 'property-key': 'updated-value' } };
component.hasMetadataChanged = true;
component.editable = true;
Expand All @@ -189,7 +213,7 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges();
expect(component.changedProperties).toEqual({});
expect(nodesApiService.updateNode).not.toHaveBeenCalled();
}));
});
});

describe('Properties loading', () => {
Expand All @@ -205,6 +229,7 @@ describe('ContentMetadataComponent', () => {

component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });

expect(contentMetadataService.getContentTypeProperty).toHaveBeenCalledWith(node.nodeType);
expect(contentMetadataService.getBasicProperties).toHaveBeenCalledWith(expectedNode);
});

Expand All @@ -221,7 +246,7 @@ describe('ContentMetadataComponent', () => {
component.basicProperties$.subscribe(() => {
fixture.detectChanges();
const basicPropertiesComponent = fixture.debugElement.query(By.directive(CardViewComponent)).componentInstance;
expect(basicPropertiesComponent.properties).toBe(expectedProperties);
expect(basicPropertiesComponent.properties.length).toBe(expectedProperties.length);
});
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Node } from '@alfresco/js-api';
import { Observable, Subject, of } from 'rxjs';
import { Observable, Subject, of, zip } from 'rxjs';
import {
CardViewItem,
NodesApiService,
Expand All @@ -30,7 +30,7 @@ import {
} from '@alfresco/adf-core';
import { ContentMetadataService } from '../../services/content-metadata.service';
import { CardViewGroup } from '../../interfaces/content-metadata.interfaces';
import { takeUntil, debounceTime, catchError } from 'rxjs/operators';
import { takeUntil, debounceTime, catchError, map } from 'rxjs/operators';

@Component({
selector: 'adf-content-metadata',
Expand Down Expand Up @@ -155,11 +155,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {

private loadProperties(node: Node) {
if (node) {
this.basicProperties$ = this.contentMetadataService.getBasicProperties(node);
this.basicProperties$ = this.getProperties(node);
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
}
}

private getProperties(node: Node) {
const properties$ = this.contentMetadataService.getBasicProperties(node);
const contentTypeProperty$ = this.contentMetadataService.getContentTypeProperty(node.nodeType);
return zip(properties$, contentTypeProperty$)
.pipe(map(([properties, contentTypeProperty]) => [...properties, ...contentTypeProperty]));
}

updateChanges(updatedNodeChanges) {
Object.keys(updatedNodeChanges).map((propertyGroup: string) => {
if (typeof updatedNodeChanges[propertyGroup] === 'object') {
Expand All @@ -174,6 +181,16 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
}

saveChanges() {
if (this.hasContentTypeChanged(this.changedProperties)) {
this.contentMetadataService.openConfirmDialog(this.changedProperties).subscribe(() => {
this.updateNode();
});
} else {
this.updateNode();
}
}

private updateNode() {
this.nodesApiService.updateNode(this.node.id, this.changedProperties).pipe(
catchError((err) => {
this.cardViewUpdateService.updateElement(this.targetProperty);
Expand All @@ -189,6 +206,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
});
}

private hasContentTypeChanged(changedProperties): boolean {
return !!changedProperties?.nodeType;
}

revertChanges() {
this.changedProperties = {};
this.hasMetadataChanged = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './services/content-metadata.service';
export * from './services/property-descriptors.service';
export * from './services/property-groups-translator.service';
export * from './services/config/content-metadata-config.factory';
export * from './services/content-type-property.service';

export * from './services/config/indifferent-config.service';
export * from './services/config/layout-oriented-config.service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import { Injectable } from '@angular/core';
import { Node } from '@alfresco/js-api';
import { CardViewDateItemModel, CardViewTextItemModel, FileSizePipe } from '@alfresco/adf-core';

@Injectable({
providedIn: 'root'
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
import { AlfrescoApiService, AppConfigService, setupTestBed } from '@alfresco/adf-core';
import { ClassesApi, Node } from '@alfresco/js-api';
import { TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { ContentMetadataService } from './content-metadata.service';
import { of } from 'rxjs';
import { PropertyGroup } from '../interfaces/property-group.interface';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTypePropertiesService } from './content-type-property.service';
import { ContentTestingModule } from '../../testing/content.testing.module';

describe('ContentMetaDataService', () => {

let service: ContentMetadataService;
let classesApi: ClassesApi;
let appConfig: AppConfigService;
let contentPropertyService: ContentTypePropertiesService;

const exifResponse: PropertyGroup = {
name: 'exif:exif',
Expand Down Expand Up @@ -64,6 +66,7 @@ describe('ContentMetaDataService', () => {

beforeEach(() => {
service = TestBed.inject(ContentMetadataService);
contentPropertyService = TestBed.inject(ContentTypePropertiesService);
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
classesApi = alfrescoApiService.classesApi;
appConfig = TestBed.inject(AppConfigService);
Expand All @@ -89,6 +92,28 @@ describe('ContentMetaDataService', () => {
);
});

it('should return the content type property', () => {
spyOn(contentPropertyService, 'getContentTypeCardItem').and.returnValue(of({ label: 'hello i am a weird content type'}));

service.getContentTypeProperty('fn:fakenode').subscribe(
(res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.label).toBe('hello i am a weird content type');
}
);
});

it('should trigger the opening of the content type dialog', () => {
spyOn(contentPropertyService, 'openContentTypeDialogConfirm').and.returnValue(of());

service.openConfirmDialog('fn:fakenode').subscribe(
() => {
expect(contentPropertyService.openContentTypeDialogConfirm).toHaveBeenCalledWith('fn:fakenode');
}
);
});

describe('AspectOriented preset', () => {

it('should return response with exif property', (done) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-met
import { ContentMetadataConfigFactory } from './config/content-metadata-config.factory';
import { PropertyDescriptorsService } from './property-descriptors.service';
import { map, switchMap } from 'rxjs/operators';

import { ContentTypePropertiesService } from './content-type-property.service';
@Injectable({
providedIn: 'root'
})
Expand All @@ -36,13 +36,22 @@ export class ContentMetadataService {
constructor(private basicPropertiesService: BasicPropertiesService,
private contentMetadataConfigFactory: ContentMetadataConfigFactory,
private propertyGroupTranslatorService: PropertyGroupTranslatorService,
private propertyDescriptorsService: PropertyDescriptorsService) {
private propertyDescriptorsService: PropertyDescriptorsService,
private contentTypePropertyService: ContentTypePropertiesService) {
}

getBasicProperties(node: Node): Observable<CardViewItem[]> {
return of(this.basicPropertiesService.getProperties(node));
}

getContentTypeProperty(nodeType: string): Observable<CardViewItem[]> {
return this.contentTypePropertyService.getContentTypeCardItem(nodeType);
}

openConfirmDialog(changedProperties): Observable<any> {
return this.contentTypePropertyService.openContentTypeDialogConfirm(changedProperties.nodeType);
}

getGroupedProperties(node: Node, presetName: string = 'default'): Observable<CardViewGroup[]> {
let groupedProperties = of([]);

Expand Down

0 comments on commit eb9e555

Please sign in to comment.