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