diff --git a/projects/aca-content/folder-rules/src/mock/actions.mock.ts b/projects/aca-content/folder-rules/src/mock/actions.mock.ts index 7f4fc4af6..3d409b00e 100644 --- a/projects/aca-content/folder-rules/src/mock/actions.mock.ts +++ b/projects/aca-content/folder-rules/src/mock/actions.mock.ts @@ -140,6 +140,22 @@ const actionParamLinkToCategoryTransformedMock = { displayLabel: 'Category value' }; +const actionParamSecurityGroup: ActionParameterDefinitionTransformed = { + name: 'securityGroupId', + type: 'd:text', + multiValued: false, + mandatory: true, + displayLabel: 'Security Group Id' +}; + +const actionParamSecurityMark: ActionParameterDefinitionTransformed = { + name: 'securityMarkId', + type: 'd:text', + multiValued: false, + mandatory: true, + displayLabel: 'Security Mark Id' +}; + const action1TransformedMock: ActionDefinitionTransformed = { id: 'mock-action-1-definition', name: 'mock-action-1-definition', @@ -176,6 +192,16 @@ export const actionLinkToCategoryTransformedMock: ActionDefinitionTransformed = parameterDefinitions: [actionParamLinkToCategoryTransformedMock] }; +export const securityActionTransformedMock: ActionDefinitionTransformed = { + id: 'mock-action-4-definition', + name: 'mock-action-4-definition', + description: '', + title: 'mock-action-4-definition', + applicableTypes: [], + trackStatus: false, + parameterDefinitions: [actionParamSecurityGroup, actionParamSecurityMark] +}; + export const actionsTransformedListMock: ActionDefinitionTransformed[] = [action1TransformedMock, action2TransformedMock]; export const validActionMock: RuleAction = { diff --git a/projects/aca-content/folder-rules/src/mock/security-marks.mock.ts b/projects/aca-content/folder-rules/src/mock/security-marks.mock.ts new file mode 100644 index 000000000..02534047d --- /dev/null +++ b/projects/aca-content/folder-rules/src/mock/security-marks.mock.ts @@ -0,0 +1,65 @@ +/*! + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * 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 + * from Hyland Software. If not, see . + */ + +import { SecurityControlsMarkResponse } from '@alfresco/adf-content-services/lib/security/services/models/security-controls-mark-response.interface'; +import { UpdateNotification } from '@alfresco/adf-core'; + +export const securityMarksResponseMock: SecurityControlsMarkResponse = { + pagination: { + count: 2, + hasMoreItems: false, + totalItems: 2, + skipCount: 0, + maxItems: 100 + }, + entries: [ + { + id: 'mark-1-id', + name: 'mark-1-name', + groupId: 'group-1' + }, + { + id: 'mark-2-id', + name: 'mark-2-name', + groupId: 'group-1' + } + ] +}; + +export const updateNotificationMock = (value: string): UpdateNotification => { + return { + changed: { securityGroupId: value }, + target: { + label: 'Security Group Id *', + value: '', + key: 'securityGroupId', + default: undefined, + editable: true, + clickable: true, + isEmpty: () => true, + isValid: () => true, + getValidationErrors: () => [] + } + }; +}; diff --git a/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.spec.ts b/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.spec.ts index 68e99b0a6..8fd5e27bb 100644 --- a/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.spec.ts +++ b/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.spec.ts @@ -25,9 +25,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CardViewBoolItemModel, CardViewComponent, CardViewSelectItemModel, CardViewTextItemModel, CoreTestingModule } from '@alfresco/adf-core'; import { RuleActionUiComponent } from './rule-action.ui-component'; -import { actionLinkToCategoryTransformedMock, actionsTransformedListMock } from '../../mock/actions.mock'; +import { actionLinkToCategoryTransformedMock, actionsTransformedListMock, securityActionTransformedMock } from '../../mock/actions.mock'; import { By } from '@angular/platform-browser'; import { dummyCategoriesConstraints, dummyConstraints, dummyTagsConstraints } from '../../mock/action-parameter-constraints.mock'; +import { securityMarksResponseMock, updateNotificationMock } from '../../mock/security-marks.mock'; import { CategoryService, TagService } from '@alfresco/adf-content-services'; import { MatDialog } from '@angular/material/dialog'; import { HarnessLoader } from '@angular/cdk/testing'; @@ -220,4 +221,35 @@ describe('RuleActionUiComponent', () => { }); }); }); + + describe('Security mark actions', () => { + beforeEach(async () => { + component.actionDefinitions = [securityActionTransformedMock]; + await changeMatSelectValue('mock-action-4-definition'); + }); + + it('should create dropdown selector for security mark action parameter', () => { + expect(getPropertiesCardView().properties[1]).toBeInstanceOf(CardViewSelectItemModel); + }); + + it('should load security marks on security group select and remove them on unselect', async () => { + spyOn(component['securityControlsService'], 'getSecurityMark').and.returnValue(Promise.resolve(securityMarksResponseMock)); + component['cardViewUpdateService'].itemUpdated$.next(updateNotificationMock('group-1')); + await fixture.whenStable(); + fixture.detectChanges(); + (getPropertiesCardView().properties[1] as CardViewSelectItemModel).options$.subscribe((options) => { + expect(options).toEqual([ + { key: 'mark-1-id', label: 'mark-1-name [mark-1-id]' }, + { key: 'mark-2-id', label: 'mark-2-name [mark-2-id]' } + ]); + }); + + component['cardViewUpdateService'].itemUpdated$.next(updateNotificationMock('')); + await fixture.whenStable(); + fixture.detectChanges(); + (getPropertiesCardView().properties[1] as CardViewSelectItemModel).options$.subscribe((options) => { + expect(options).toEqual([]); + }); + }); + }); }); diff --git a/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.ts b/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.ts index c912ed95d..13a085452 100644 --- a/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.ts +++ b/projects/aca-content/folder-rules/src/rule-details/actions/rule-action.ui-component.ts @@ -35,8 +35,8 @@ import { CardViewUpdateService, UpdateNotification } from '@alfresco/adf-core'; -import { ActionParameterDefinition, Category, Node } from '@alfresco/js-api'; -import { of, Subject } from 'rxjs'; +import { ActionParameterDefinition, Category, Node, SecurityMark } from '@alfresco/js-api'; +import { from, of, Subject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { ActionParameterConstraint, ConstraintValue } from '../../model/action-parameter-constraint.model'; import { @@ -46,7 +46,8 @@ import { NodeAction, TagService, CategorySelectorDialogComponent, - CategorySelectorDialogOptions + CategorySelectorDialogOptions, + SecurityControlsService } from '@alfresco/adf-content-services'; import { MatDialog } from '@angular/material/dialog'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; @@ -96,6 +97,7 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh private readonly tagsRelatedPropertiesAndAspects = ['cm:tagscope', 'cm:tagScopeCache', 'cm:taggable']; private readonly categoriesRelatedPropertiesAndAspects = ['cm:categories', 'cm:generalclassifiable']; + private readonly paramsToFormatDisplayedValue = ['securityMarkId', 'securityGroupId']; isFullWidth = false; @@ -123,7 +125,8 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh private dialog: MatDialog, private translate: TranslateService, private tagService: TagService, - private categoryService: CategoryService + private categoryService: CategoryService, + private securityControlsService: SecurityControlsService ) {} writeValue(action: RuleAction) { @@ -135,6 +138,9 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh ...action.params }; this.setCardViewProperties(); + if (this.parameters?.securityGroupId) { + this.loadSecurityMarkOptions(); + } } registerOnChange(fn: (action: RuleAction) => void) { @@ -161,6 +167,11 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh }); this.cardViewUpdateService.itemUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe((updateNotification: UpdateNotification) => { + const isSecurityGroupUpdated = updateNotification.target.key === 'securityGroupId'; + if (isSecurityGroupUpdated) { + this.parameters.securityMarkId = null; + } + this.parameters = this.clearEmptyParameters({ ...this.parameters, ...updateNotification.changed @@ -170,6 +181,11 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh params: this.parameters }); this.onTouch(); + + if (isSecurityGroupUpdated) { + this.setCardViewProperties(); + this.loadSecurityMarkOptions(); + } }); } @@ -186,11 +202,14 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh this.onDestroy$.complete(); } - setCardViewProperties() { + setCardViewProperties(securityMarkOptions?: CardViewSelectItemOption[]) { const disabledTags = !this.tagService.areTagsEnabled(); const disabledCategories = !this.categoryService.areCategoriesEnabled(); this.cardViewItems = (this.selectedActionDefinition?.parameterDefinitions ?? []).map((paramDef) => { - const constraintsForDropdownBox = this._parameterConstraints.find((obj) => obj.name === paramDef.name); + const constraintsForDropdownBox = + paramDef.name === 'securityMarkId' + ? { name: paramDef.name, constraints: securityMarkOptions || [] } + : this._parameterConstraints.find((obj) => obj.name === paramDef.name); const cardViewPropertiesModel = { label: paramDef.displayLabel + (paramDef.mandatory ? ' *' : ''), key: paramDef.name, @@ -253,7 +272,10 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh } return new CardViewTextItemModel({ ...cardViewPropertiesModel, - value: this.parameters[paramDef.name] ?? '' + value: + constraintsForDropdownBox && this.readOnly && this.paramsToFormatDisplayedValue.includes(paramDef.name) + ? constraintsForDropdownBox.constraints.find((constraint) => constraint.key === this.parameters[paramDef.name])?.label ?? '' + : this.parameters[paramDef.name] ?? '' }); } }); @@ -368,4 +390,19 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnCh Object.keys(params).forEach((key) => (params[key] === null || params[key] === undefined || params[key] === '') && delete params[key]); return params; } + + loadSecurityMarkOptions(): void { + if (this.parameters?.securityGroupId) { + from(this.securityControlsService.getSecurityMark(this.parameters.securityGroupId as string)) + .pipe(map((securityMarks) => securityMarks.entries.map((entry) => this.formatSecurityMarkConstraint(entry)))) + .subscribe((res) => this.setCardViewProperties(this.parseConstraintsToSelectOptions(res) as CardViewSelectItemOption[])); + } + } + + private formatSecurityMarkConstraint(securityMark: SecurityMark): ConstraintValue { + return { + value: securityMark.id, + label: securityMark.name + }; + } }