mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACS-3576] Map aspect actions with the aspect picker dropdown (#2713)
* rebased * rebased * unit tests * linting * publicApiCall * renamed method formatAspects to parseAspectsToSelectOptions * added getter cardViewStyle * removing business logic from the ui component * moved 'loadAspects()' to actions.service.ts * preview fix * removed duplicate
This commit is contained in:
parent
2d6375aad1
commit
77c5ac0c96
@ -35,6 +35,7 @@ import { By } from '@angular/platform-browser';
|
||||
import { dummyNodeInfo } from '../mock/node.mock';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActionsService } from '../services/actions.service';
|
||||
import { dummyAspects } from '../mock/aspects.mock';
|
||||
|
||||
describe('ManageRulesSmartComponent', () => {
|
||||
let fixture: ComponentFixture<ManageRulesSmartComponent>;
|
||||
@ -60,6 +61,7 @@ describe('ManageRulesSmartComponent', () => {
|
||||
debugElement = fixture.debugElement;
|
||||
folderRulesService = TestBed.inject<FolderRulesService>(FolderRulesService);
|
||||
actionsService = TestBed.inject<ActionsService>(ActionsService);
|
||||
actionsService.aspects$ = of(dummyAspects);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
@ -51,6 +51,12 @@ export const actionDefListMock: ActionDefinitionList = {
|
||||
type: 'd:boolean',
|
||||
multiValued: false,
|
||||
mandatory: false
|
||||
},
|
||||
{
|
||||
name: 'aspect-name',
|
||||
type: 'd:qname',
|
||||
multiValued: false,
|
||||
mandatory: false
|
||||
}
|
||||
],
|
||||
name: 'mock-action-1-definition',
|
||||
@ -85,6 +91,14 @@ const actionParam2TransformedMock: ActionParameterDefinitionTransformed = {
|
||||
displayLabel: 'mock-action-parameter-boolean'
|
||||
};
|
||||
|
||||
const actionParam3TransformedMock: ActionParameterDefinitionTransformed = {
|
||||
name: 'aspect-name',
|
||||
type: 'd:qname',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
displayLabel: 'aspect-name'
|
||||
};
|
||||
|
||||
const action1TransformedMock: ActionDefinitionTransformed = {
|
||||
id: 'mock-action-1-definition',
|
||||
name: 'mock-action-1-definition',
|
||||
@ -92,7 +106,7 @@ const action1TransformedMock: ActionDefinitionTransformed = {
|
||||
title: 'Action 1 title',
|
||||
applicableTypes: [],
|
||||
trackStatus: false,
|
||||
parameterDefinitions: [actionParam1TransformedMock, actionParam2TransformedMock]
|
||||
parameterDefinitions: [actionParam1TransformedMock, actionParam2TransformedMock, actionParam3TransformedMock]
|
||||
};
|
||||
|
||||
const action2TransformedMock: ActionDefinitionTransformed = {
|
||||
|
41
projects/aca-folder-rules/src/lib/mock/aspects.mock.ts
Normal file
41
projects/aca-folder-rules/src/lib/mock/aspects.mock.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Aspect } from '../model/aspect.model';
|
||||
|
||||
export const dummyAspects: Aspect[] = [
|
||||
{
|
||||
value: 'cm:aspect1',
|
||||
label: 'Label 1'
|
||||
},
|
||||
{
|
||||
value: 'cm:aspect2',
|
||||
label: 'Label 2'
|
||||
},
|
||||
{
|
||||
value: 'cm:aspect3',
|
||||
label: 'Label 3'
|
||||
}
|
||||
];
|
@ -23,18 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface AspectModel {
|
||||
id: string;
|
||||
description: string;
|
||||
namespaceUri: string;
|
||||
namespacePrefix: string;
|
||||
}
|
||||
|
||||
export interface Aspect {
|
||||
includedInSupertypeQuery: boolean;
|
||||
isContainer: boolean;
|
||||
model: AspectModel;
|
||||
id: string;
|
||||
title: string;
|
||||
parentId: string;
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<div class="aca-rule-action-list__item " *ngFor="let control of formControls">
|
||||
<aca-rule-action
|
||||
[actionDefinitions]="actionDefinitions"
|
||||
[aspects]="aspects"
|
||||
[readOnly]="readOnly"
|
||||
[formControl]="control">
|
||||
</aca-rule-action>
|
||||
|
@ -28,6 +28,7 @@ import { ControlValueAccessor, FormArray, FormControl, NG_VALUE_ACCESSOR, Valida
|
||||
import { ActionDefinitionTransformed, RuleAction } from '../../model/rule-action.model';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ruleActionValidator } from '../validators/rule-actions.validator';
|
||||
import { Aspect } from '../../model/aspect.model';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-rule-action-list',
|
||||
@ -48,6 +49,8 @@ export class RuleActionListUiComponent implements ControlValueAccessor, OnDestro
|
||||
actionDefinitions: ActionDefinitionTransformed[] = [];
|
||||
@Input()
|
||||
readOnly = false;
|
||||
@Input()
|
||||
aspects: Aspect[] = [];
|
||||
|
||||
formArray = new FormArray([]);
|
||||
private formArraySubscription: Subscription;
|
||||
|
@ -16,6 +16,7 @@
|
||||
<adf-card-view
|
||||
data-automation-id="rule-action-card-view"
|
||||
[properties]="cardViewItems"
|
||||
[ngStyle]="cardViewStyle"
|
||||
[editable]="!readOnly">
|
||||
</adf-card-view>
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CardViewBoolItemModel, CardViewComponent, CardViewTextItemModel, CoreTestingModule } from '@alfresco/adf-core';
|
||||
import { CardViewBoolItemModel, CardViewComponent, CardViewSelectItemModel, CardViewTextItemModel, CoreTestingModule } from '@alfresco/adf-core';
|
||||
import { RuleActionUiComponent } from './rule-action.ui-component';
|
||||
import { actionsTransformedListMock } from '../../mock/actions.mock';
|
||||
import { DebugElement } from '@angular/core';
|
||||
@ -77,9 +77,10 @@ describe('RuleActionUiComponent', () => {
|
||||
expect(cardView.properties.length).toBe(0);
|
||||
|
||||
changeMatSelectValue('rule-action-select', 'mock-action-1-definition');
|
||||
expect(cardView.properties.length).toBe(2);
|
||||
expect(cardView.properties.length).toBe(3);
|
||||
expect(cardView.properties[0]).toBeInstanceOf(CardViewTextItemModel);
|
||||
expect(cardView.properties[1]).toBeInstanceOf(CardViewBoolItemModel);
|
||||
expect(cardView.properties[2]).toBeInstanceOf(CardViewSelectItemModel);
|
||||
|
||||
changeMatSelectValue('rule-action-select', 'mock-action-2-definition');
|
||||
expect(cardView.properties.length).toBe(0);
|
||||
|
@ -27,10 +27,18 @@ import { Component, forwardRef, Input, OnDestroy, OnInit, ViewEncapsulation } fr
|
||||
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import { ActionDefinitionTransformed, RuleAction } from '../../model/rule-action.model';
|
||||
import { CardViewItem } from '@alfresco/adf-core/lib/card-view/interfaces/card-view-item.interface';
|
||||
import { CardViewBoolItemModel, CardViewTextItemModel, CardViewUpdateService, UpdateNotification } from '@alfresco/adf-core';
|
||||
import {
|
||||
CardViewBoolItemModel,
|
||||
CardViewSelectItemModel,
|
||||
CardViewSelectItemOption,
|
||||
CardViewTextItemModel,
|
||||
CardViewUpdateService,
|
||||
UpdateNotification
|
||||
} from '@alfresco/adf-core';
|
||||
import { ActionParameterDefinition } from '@alfresco/js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Aspect } from '../../model/aspect.model';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-rule-action',
|
||||
@ -66,6 +74,17 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
|
||||
this.setDisabledState(isReadOnly);
|
||||
}
|
||||
|
||||
private _aspects;
|
||||
@Input()
|
||||
get aspects(): Aspect[] {
|
||||
return this._aspects;
|
||||
}
|
||||
set aspects(value) {
|
||||
this._aspects = this.parseAspectsToSelectOptions(value);
|
||||
}
|
||||
|
||||
isFullWidth = false;
|
||||
|
||||
form = new FormGroup({
|
||||
actionDefinitionId: new FormControl('', Validators.required)
|
||||
});
|
||||
@ -82,6 +101,10 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
|
||||
return this.actionDefinitions.find((actionDefinition: ActionDefinitionTransformed) => actionDefinition.id === this.selectedActionDefinitionId);
|
||||
}
|
||||
|
||||
get cardViewStyle() {
|
||||
return this.isFullWidth ? { width: '100%' } : {};
|
||||
}
|
||||
|
||||
onChange: (action: RuleAction) => void = () => undefined;
|
||||
onTouch: () => void = () => undefined;
|
||||
|
||||
@ -137,6 +160,7 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
|
||||
|
||||
setCardViewProperties() {
|
||||
this.cardViewItems = (this.selectedActionDefinition?.parameterDefinitions ?? []).map((paramDef) => {
|
||||
this.isFullWidth = false;
|
||||
const cardViewPropertiesModel = {
|
||||
label: paramDef.displayLabel + (paramDef.mandatory ? ' *' : ''),
|
||||
key: paramDef.name,
|
||||
@ -158,6 +182,16 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
|
||||
...cardViewPropertiesModel,
|
||||
value: this.parameters[paramDef.name] ?? false
|
||||
});
|
||||
case 'd:qname':
|
||||
if (paramDef.name === 'aspect-name' && !this.readOnly) {
|
||||
this.isFullWidth = true;
|
||||
return new CardViewSelectItemModel({
|
||||
...cardViewPropertiesModel,
|
||||
value: (this.parameters[paramDef.name] as string) ?? '',
|
||||
options$: of(this._aspects)
|
||||
});
|
||||
}
|
||||
/* falls through */
|
||||
default:
|
||||
return new CardViewTextItemModel({
|
||||
...cardViewPropertiesModel,
|
||||
@ -189,4 +223,21 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
|
||||
this.form.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private parseAspectsToSelectOptions(aspects: Aspect[]): CardViewSelectItemOption<unknown>[] {
|
||||
return aspects
|
||||
.sort((a, b) => {
|
||||
if (!a.label && b.label) {
|
||||
return 1;
|
||||
}
|
||||
if (!b.label && a.label) {
|
||||
return -1;
|
||||
}
|
||||
return a.label?.localeCompare(b.label) ?? -1;
|
||||
})
|
||||
.map((aspect) => ({
|
||||
key: aspect.value,
|
||||
label: aspect.label ? `${aspect.label} [${aspect.value}]` : aspect.value
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
<ng-template #ruleDetails>
|
||||
<aca-rule-details
|
||||
[actionDefinitions]="actionDefinitions$ | async"
|
||||
[aspects]="aspects$ | async"
|
||||
[value]="model"
|
||||
(formValueChanged)="formValue = $event"
|
||||
(formValidationChanged)="formValid = $event">
|
||||
|
@ -46,6 +46,7 @@ export class EditRuleDialogSmartComponent implements OnInit {
|
||||
@Output() submitted = new EventEmitter<Partial<Rule>>();
|
||||
actionDefinitions$ = this.actionsService.actionDefinitionsListing$;
|
||||
loading$ = this.actionsService.loading$;
|
||||
aspects$ = this.actionsService.aspects$;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: EditRuleDialogOptions, private actionsService: ActionsService) {
|
||||
this.model = this.data?.model || {};
|
||||
@ -68,6 +69,7 @@ export class EditRuleDialogSmartComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.actionsService.loadAspects();
|
||||
this.actionsService.loadActionDefinitions();
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@
|
||||
<aca-rule-action-list
|
||||
formControlName="actions"
|
||||
[actionDefinitions]="actionDefinitions"
|
||||
[aspects]="aspects"
|
||||
[readOnly]="readOnly">
|
||||
</aca-rule-action-list>
|
||||
</div>
|
||||
|
@ -32,6 +32,7 @@ import { ruleCompositeConditionValidator } from './validators/rule-composite-con
|
||||
import { FolderRulesService } from '../services/folder-rules.service';
|
||||
import { ActionDefinitionTransformed } from '../model/rule-action.model';
|
||||
import { ruleActionsValidator } from './validators/rule-actions.validator';
|
||||
import { Aspect } from '../model/aspect.model';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-rule-details',
|
||||
@ -84,6 +85,8 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
|
||||
preview: boolean;
|
||||
@Input()
|
||||
actionDefinitions: ActionDefinitionTransformed[] = [];
|
||||
@Input()
|
||||
aspects: Aspect[] = [];
|
||||
|
||||
@Output()
|
||||
formValidationChanged = new EventEmitter<boolean>();
|
||||
|
@ -32,6 +32,8 @@ import { take } from 'rxjs/operators';
|
||||
|
||||
describe('ActionsService', () => {
|
||||
let actionsService: ActionsService;
|
||||
let apiCallSpy;
|
||||
const params = [{}, {}, {}, {}, {}, ['application/json'], ['application/json']];
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -62,4 +64,13 @@ describe('ActionsService', () => {
|
||||
expect(await loadingTruePromise).toBeTrue();
|
||||
expect(await loadingFalsePromise).toBeFalse();
|
||||
});
|
||||
|
||||
it('loadAspects should send correct GET request', async () => {
|
||||
apiCallSpy = spyOn<any>(actionsService, 'publicApiCall').withArgs(`/action-parameter-constraints/ac-aspects`, 'GET', params).and.returnValue([]);
|
||||
|
||||
actionsService.loadAspects();
|
||||
|
||||
expect(apiCallSpy).toHaveBeenCalled();
|
||||
expect(apiCallSpy).toHaveBeenCalledWith(`/action-parameter-constraints/ac-aspects`, 'GET', params);
|
||||
});
|
||||
});
|
||||
|
@ -26,9 +26,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionDefinition, ActionDefinitionEntry, ActionDefinitionList, ActionParameterDefinition, ActionsApi } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { BehaviorSubject, from } from 'rxjs';
|
||||
import { BehaviorSubject, from, Observable } from 'rxjs';
|
||||
import { ActionDefinitionTransformed, ActionParameterDefinitionTransformed } from '../model/rule-action.model';
|
||||
import { finalize, map } from 'rxjs/operators';
|
||||
import { Aspect } from '../model/aspect.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ActionsService {
|
||||
@ -36,6 +37,8 @@ export class ActionsService {
|
||||
actionDefinitionsListing$ = this.actionDefinitionsListingSource.asObservable();
|
||||
private loadingSource = new BehaviorSubject<boolean>(false);
|
||||
loading$ = this.loadingSource.asObservable();
|
||||
private aspectsSource = new BehaviorSubject<Aspect[]>([]);
|
||||
aspects$: Observable<Aspect[]> = this.aspectsSource.asObservable();
|
||||
|
||||
private _actionsApi: ActionsApi;
|
||||
get actionsApi(): ActionsApi {
|
||||
@ -59,6 +62,14 @@ export class ActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
loadAspects(): void {
|
||||
from(this.publicApiCall('/action-parameter-constraints/ac-aspects', 'GET', [{}, {}, {}, {}, {}, ['application/json'], ['application/json']]))
|
||||
.pipe(map((res) => res.entry.constraintValues.map((entry) => this.formatAspect(entry))))
|
||||
.subscribe((res) => {
|
||||
this.aspectsSource.next(res);
|
||||
});
|
||||
}
|
||||
|
||||
private transformActionDefinition(obj: ActionDefinition | ActionDefinitionEntry): ActionDefinitionTransformed {
|
||||
if (this.isActionDefinitionEntry(obj)) {
|
||||
obj = obj.entry;
|
||||
@ -89,4 +100,15 @@ export class ActionsService {
|
||||
private isActionDefinitionEntry(obj): obj is ActionDefinitionEntry {
|
||||
return typeof obj.entry !== 'undefined';
|
||||
}
|
||||
|
||||
private publicApiCall(path: string, httpMethod: string, params?: any[]): Promise<any> {
|
||||
return this.apiService.getInstance().contentClient.callApi(path, httpMethod, ...params);
|
||||
}
|
||||
|
||||
private formatAspect(aspect): Aspect {
|
||||
return {
|
||||
value: aspect.value ?? '',
|
||||
label: aspect.label ?? ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -126,19 +126,6 @@ describe('FolderRulesService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadAspects', () => {
|
||||
beforeEach(async () => {
|
||||
apiCallSpy = spyOn<any>(folderRulesService, 'apiCall').withArgs(`/aspects`, 'GET', params).and.returnValue([]);
|
||||
|
||||
folderRulesService.loadAspects();
|
||||
});
|
||||
|
||||
it('should send correct GET request', async () => {
|
||||
expect(apiCallSpy).toHaveBeenCalled();
|
||||
expect(apiCallSpy).toHaveBeenCalledWith(`/aspects`, 'GET', params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRule', () => {
|
||||
beforeEach(async () => {
|
||||
spyOn<any>(folderRulesService, 'apiCall')
|
||||
|
@ -32,7 +32,6 @@ import { ContentApiService } from '@alfresco/aca-shared';
|
||||
import { NodeInfo } from '@alfresco/aca-shared/store';
|
||||
import { RuleCompositeCondition } from '../model/rule-composite-condition.model';
|
||||
import { RuleSimpleCondition } from '../model/rule-simple-condition.model';
|
||||
import { Aspect, AspectModel } from '../model/aspect.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -71,8 +70,6 @@ export class FolderRulesService {
|
||||
loading$ = this.loadingSource.asObservable();
|
||||
private deletedRuleIdSource = new BehaviorSubject<string>(null);
|
||||
deletedRuleId$: Observable<string> = this.deletedRuleIdSource.asObservable();
|
||||
private aspectsSource = new BehaviorSubject<Aspect[]>([]);
|
||||
aspects$: Observable<Aspect[]> = this.aspectsSource.asObservable();
|
||||
|
||||
constructor(private apiService: AlfrescoApiService, private contentApi: ContentApiService) {}
|
||||
|
||||
@ -173,14 +170,6 @@ export class FolderRulesService {
|
||||
).subscribe({ error: (error) => console.error(error) });
|
||||
}
|
||||
|
||||
loadAspects(): void {
|
||||
from(this.apiCall('/aspects', 'GET', [{}, {}, {}, {}, {}, ['application/json'], ['application/json']]))
|
||||
.pipe(map((res) => res.list.entries.map((entry) => this.formatAspect(entry.entry))))
|
||||
.subscribe((res) => {
|
||||
this.aspectsSource.next(res);
|
||||
});
|
||||
}
|
||||
|
||||
private apiCall(path: string, httpMethod: string, params?: any[]): Promise<any> {
|
||||
// APIs used by this service are still private and not yet available for public use
|
||||
return this.apiService.getInstance().contentPrivateClient.callApi(path, httpMethod, ...params);
|
||||
@ -233,24 +222,4 @@ export class FolderRulesService {
|
||||
parameter: obj.parameter || ''
|
||||
};
|
||||
}
|
||||
|
||||
private formatAspect(obj): Aspect {
|
||||
return {
|
||||
includedInSupertypeQuery: obj.includedInSupertypeQuery ?? false,
|
||||
isContainer: obj.isContainer ?? false,
|
||||
model: this.formatAspectModel(obj.model),
|
||||
id: obj.id ?? '',
|
||||
title: obj.title ?? '',
|
||||
parentId: obj.parentId ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
private formatAspectModel(obj): AspectModel {
|
||||
return {
|
||||
id: obj.id ?? '',
|
||||
description: obj.description ?? '',
|
||||
namespaceUri: obj.namespaceUri ?? '',
|
||||
namespacePrefix: obj.namespacePrefix ?? ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user