Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADF-5316] - Content Type #6602

Merged
merged 14 commits into from
Feb 12, 2021
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