diff --git a/projects/aca-folder-rules/assets/i18n/en.json b/projects/aca-folder-rules/assets/i18n/en.json
index 80d421deb..2484d6988 100644
--- a/projects/aca-folder-rules/assets/i18n/en.json
+++ b/projects/aca-folder-rules/assets/i18n/en.json
@@ -21,8 +21,47 @@
"NO_DESCRIPTION": "No description"
},
"ERROR": {
- "REQUIRED": "This field is required"
- }
+ "REQUIRED": "This field is required",
+ "RULE_COMPOSITE_CONDITION_INVALID": "One or more condition groups is empty"
+ },
+ "COMPARATORS": {
+ "EQUALS": "(=) Equals",
+ "CONTAINS": "Contains",
+ "STARTS_WITH": "Starts with",
+ "ENDS_WITH": "Ends with",
+ "GREATER_THAN": "(>) Greater than",
+ "LESS_THAN": "(<) Less than",
+ "GREATER_THAN_OR_EQUAL": "(>=) Greater than or equal",
+ "LESS_THAN_OR_EQUAL": "(<=) Less than or equal",
+ "ON": "(=) On",
+ "AFTER": "(>) After",
+ "BEFORE": "(<) Before",
+ "ON_OR_AFTER": "(>=) On or after",
+ "ON_OR_BEFORE": "(<=) On or before",
+ "INSTANCE_OF": "(=) Is"
+ },
+ "FIELDS": {
+ "NAME": "Name",
+ "SIZE": "Size",
+ "MIMETYPE": "Mimetype",
+ "ENCODING": "Encoding",
+ "HAS_CATEGORY": "Has category",
+ "HAS_TAG": "Has tag",
+ "HAS_ASPECT": "Has aspect"
+ },
+ "LOGIC_OPERATORS": {
+ "IF": "If",
+ "NOT_IF": "NOT If",
+ "AND": "And",
+ "OR": "Or"
+ },
+ "ACTIONS": {
+ "ADD_CONDITION": "Add condition",
+ "ADD_GROUP": "Add group",
+ "REMOVE": "Remove"
+ },
+ "NO_CONDITIONS": "No conditions",
+ "NO_CONDITIONS_IN_GROUP": "No conditions in the group"
}
}
}
diff --git a/projects/aca-folder-rules/src/lib/folder-rules.module.ts b/projects/aca-folder-rules/src/lib/folder-rules.module.ts
index 4938df587..71de638bb 100644
--- a/projects/aca-folder-rules/src/lib/folder-rules.module.ts
+++ b/projects/aca-folder-rules/src/lib/folder-rules.module.ts
@@ -32,7 +32,9 @@ import { RouterModule, Routes } from '@angular/router';
import { EditRuleDialogSmartComponent } from './rule-details/edit-rule-dialog.smart-component';
import { ManageRulesSmartComponent } from './manage-rules/manage-rules.smart-component';
+import { RuleCompositeConditionUiComponent } from './rule-details/conditions/rule-composite-condition.ui-component';
import { RuleDetailsUiComponent } from './rule-details/rule-details.ui-component';
+import { RuleSimpleConditionUiComponent } from './rule-details/conditions/rule-simple-condition.ui-component';
const routes: Routes = [
{
@@ -44,7 +46,13 @@ const routes: Routes = [
@NgModule({
providers: [provideExtensionConfig(['folder-rules.plugin.json'])],
imports: [CommonModule, RouterModule.forChild(routes), CoreModule.forChild()],
- declarations: [EditRuleDialogSmartComponent, ManageRulesSmartComponent, RuleDetailsUiComponent]
+ declarations: [
+ EditRuleDialogSmartComponent,
+ ManageRulesSmartComponent,
+ RuleCompositeConditionUiComponent,
+ RuleDetailsUiComponent,
+ RuleSimpleConditionUiComponent
+ ]
})
export class AcaFolderRulesModule {
constructor(translation: TranslationService, extensions: ExtensionService) {
diff --git a/projects/aca-folder-rules/src/lib/mock/conditions.mock.ts b/projects/aca-folder-rules/src/lib/mock/conditions.mock.ts
new file mode 100644
index 000000000..7b5bb5d10
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/mock/conditions.mock.ts
@@ -0,0 +1,68 @@
+/*!
+ * @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 .
+ */
+
+import { RuleCompositeCondition } from '../model/rule-composite-condition.model';
+import { RuleSimpleCondition } from '../model/rule-simple-condition.model';
+
+const simpleConditionMock: RuleSimpleCondition = {
+ field: 'cm:name',
+ comparator: 'equals',
+ parameter: ''
+};
+
+const emptyCompositeConditionMock: RuleCompositeCondition = {
+ inverted: false,
+ booleanMode: 'and',
+ compositeConditions: [],
+ simpleConditions: []
+};
+
+const compositeConditionWithOneConditionMock: RuleCompositeCondition = {
+ ...emptyCompositeConditionMock,
+ simpleConditions: [{ ...simpleConditionMock }]
+};
+
+export const compositeConditionWithOneGroupMock: RuleCompositeCondition = {
+ ...emptyCompositeConditionMock,
+ compositeConditions: [{ ...compositeConditionWithOneConditionMock }]
+};
+
+export const compositeConditionWithNestedGroupsMock: RuleCompositeCondition = {
+ ...emptyCompositeConditionMock,
+ compositeConditions: [
+ {
+ ...emptyCompositeConditionMock,
+ compositeConditions: [{ ...compositeConditionWithOneConditionMock }]
+ },
+ { ...compositeConditionWithOneConditionMock }
+ ]
+};
+
+export const compositeConditionWithThreeConditionMock: RuleCompositeCondition = {
+ inverted: false,
+ booleanMode: 'and',
+ compositeConditions: [],
+ simpleConditions: [{ ...simpleConditionMock }, { ...simpleConditionMock }, { ...simpleConditionMock }]
+};
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.html
new file mode 100644
index 000000000..165f74057
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.html
@@ -0,0 +1,68 @@
+
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.scss
new file mode 100644
index 000000000..e9d1709cd
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.scss
@@ -0,0 +1,38 @@
+.aca-rule-composite-condition {
+ display: block;
+ border-radius: 8px;
+ background-color: hsl(0,0%,100%);
+
+ &.childCompositeCondition {
+ padding: 8px 16px;
+ }
+
+ &.secondaryBackground {
+ background-color: hsl(0,0%,95%);
+ }
+
+ &__form {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ &__no-conditions {
+ color: var(--theme-disabled-text-color);
+ margin: 0.5em 0;
+ }
+
+ &__row {
+ display: flex;
+ gap: 8px;
+
+ & > :nth-child(1) {
+ width: 5em;
+ font-size: inherit;
+ }
+
+ & > :nth-child(2) {
+ flex: 1;
+ }
+ }
+ }
+}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.spec.ts
new file mode 100644
index 000000000..ed9cc089c
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.spec.ts
@@ -0,0 +1,143 @@
+/*!
+ * @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 .
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RuleCompositeConditionUiComponent } from './rule-composite-condition.ui-component';
+import { CoreTestingModule } from '@alfresco/adf-core';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+import {
+ compositeConditionWithNestedGroupsMock,
+ compositeConditionWithOneGroupMock,
+ compositeConditionWithThreeConditionMock
+} from '../../mock/conditions.mock';
+import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
+
+describe('RuleCompositeConditionUiComponent', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CoreTestingModule],
+ declarations: [RuleCompositeConditionUiComponent, RuleSimpleConditionUiComponent]
+ });
+
+ fixture = TestBed.createComponent(RuleCompositeConditionUiComponent);
+ });
+
+ describe('No conditions', () => {
+ let noConditionsElement: DebugElement;
+
+ beforeEach(() => {
+ fixture.detectChanges();
+ noConditionsElement = fixture.debugElement.query(By.css(`[data-automation-id="no-conditions"]`));
+ });
+
+ it('should default to no conditions', () => {
+ const rowElements = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition__form__row`));
+
+ expect(rowElements.length).toBe(0);
+ });
+
+ it('should show a message if there are no conditions', () => {
+ fixture.componentInstance.childCondition = false;
+ fixture.detectChanges();
+
+ expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS');
+ });
+
+ it('should show a different message if the component is not a root condition group', () => {
+ fixture.componentInstance.childCondition = true;
+ fixture.detectChanges();
+
+ expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS_IN_GROUP');
+ });
+ });
+
+ describe('Read only mode', () => {
+ it('should hide the add buttons in read only mode', () => {
+ fixture.componentInstance.setDisabledState(true);
+ fixture.detectChanges();
+ const actionsElement = fixture.debugElement.query(By.css(`[data-automation-id="add-actions"]`));
+
+ expect(actionsElement).toBeNull();
+ });
+
+ it('should hide the more actions button on the right side of the condition', () => {
+ fixture.componentInstance.writeValue(compositeConditionWithOneGroupMock);
+ fixture.componentInstance.setDisabledState(true);
+ fixture.detectChanges();
+ const actionsButtonElements = fixture.debugElement.queryAll(By.css(`[data-automation-id="condition-actions-button"]`));
+
+ expect(actionsButtonElements.length).toBe(0);
+ });
+ });
+
+ it('should have as many simple condition components as defined in the simpleConditions array', () => {
+ fixture.componentInstance.writeValue(compositeConditionWithThreeConditionMock);
+ fixture.detectChanges();
+ const simpleConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-simple-condition`));
+
+ expect(simpleConditionComponents.length).toBe(3);
+ });
+
+ it('should have as many composite condition components as defined in the compositeConditions array, including nested', () => {
+ fixture.componentInstance.writeValue(compositeConditionWithNestedGroupsMock);
+ fixture.detectChanges();
+ const compositeConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition`));
+
+ expect(compositeConditionComponents.length).toBe(3);
+ });
+
+ it('should add a simple condition component on click of add condition button', () => {
+ fixture.detectChanges();
+ const predicate = By.css(`.aca-rule-simple-condition`);
+ const simpleConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
+
+ expect(simpleConditionComponentsBeforeClick.length).toBe(0);
+
+ const addConditionButton = fixture.debugElement.query(By.css(`[data-automation-id="add-condition-button"]`));
+ (addConditionButton.nativeElement as HTMLButtonElement).click();
+ fixture.detectChanges();
+ const simpleConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
+
+ expect(simpleConditionComponentsAfterClick.length).toBe(1);
+ });
+
+ it('should add a composite condition component on click of add group button', () => {
+ fixture.detectChanges();
+ const predicate = By.css(`.aca-rule-composite-condition`);
+ const compositeConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
+
+ expect(compositeConditionComponentsBeforeClick.length).toBe(0);
+
+ const addGroupButton = fixture.debugElement.query(By.css(`[data-automation-id="add-group-button"]`));
+ (addGroupButton.nativeElement as HTMLButtonElement).click();
+ fixture.detectChanges();
+ const compositeConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
+
+ expect(compositeConditionComponentsAfterClick.length).toBe(1);
+ });
+});
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.ts
new file mode 100644
index 000000000..f1ae96d81
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.ui-component.ts
@@ -0,0 +1,157 @@
+/*!
+ * @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 .
+ */
+
+import { Component, forwardRef, HostBinding, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
+import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
+import { ControlValueAccessor, FormArray, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
+
+@Component({
+ selector: 'aca-rule-composite-condition',
+ templateUrl: './rule-composite-condition.ui-component.html',
+ styleUrls: ['./rule-composite-condition.ui-component.scss'],
+ encapsulation: ViewEncapsulation.None,
+ host: { class: 'aca-rule-composite-condition' },
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ multi: true,
+ useExisting: forwardRef(() => RuleCompositeConditionUiComponent)
+ }
+ ]
+})
+export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnDestroy {
+ @HostBinding('class.secondaryBackground')
+ @Input()
+ secondaryBackground = false;
+ @HostBinding('class.childCompositeCondition')
+ @Input()
+ childCondition = false;
+
+ form = new FormGroup({
+ inverted: new FormControl(),
+ booleanMode: new FormControl(),
+ compositeConditions: new FormArray([]),
+ simpleConditions: new FormArray([])
+ });
+
+ private formSubscription = this.form.valueChanges.subscribe((value) => {
+ this.onChange(value);
+ this.onTouch();
+ });
+
+ get invertedControl(): FormControl {
+ return this.form.get('inverted') as FormControl;
+ }
+ get booleanModeControl(): FormControl {
+ return this.form.get('booleanMode') as FormControl;
+ }
+ get compositeConditionsFormArray(): FormArray {
+ return this.form.get('compositeConditions') as FormArray;
+ }
+ get simpleConditionsFormArray(): FormArray {
+ return this.form.get('simpleConditions') as FormArray;
+ }
+ get conditionFormControls(): FormControl[] {
+ return [...(this.compositeConditionsFormArray.controls as FormControl[]), ...(this.simpleConditionsFormArray.controls as FormControl[])];
+ }
+ get hasNoConditions(): boolean {
+ return this.conditionFormControls.length === 0;
+ }
+
+ private _readOnly = false;
+ get readOnly(): boolean {
+ return this._readOnly;
+ }
+
+ onChange: (condition: RuleCompositeCondition) => void = () => undefined;
+ onTouch: () => void = () => undefined;
+
+ writeValue(value: RuleCompositeCondition) {
+ this.form.get('inverted').setValue(value.inverted);
+ this.form.get('booleanMode').setValue(value.booleanMode);
+ this.form.setControl('compositeConditions', new FormArray(value.compositeConditions.map((condition) => new FormControl(condition))));
+ this.form.setControl('simpleConditions', new FormArray(value.simpleConditions.map((condition) => new FormControl(condition))));
+ }
+
+ registerOnChange(fn: () => void) {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: () => void) {
+ this.onTouch = fn;
+ }
+
+ setDisabledState(isDisabled: boolean) {
+ if (isDisabled) {
+ this._readOnly = true;
+ this.form.disable();
+ } else {
+ this._readOnly = false;
+ this.form.enable();
+ }
+ }
+
+ setInverted(value: boolean) {
+ this.invertedControl.setValue(value);
+ }
+
+ setBooleanMode(value: 'and' | 'or') {
+ this.booleanModeControl.setValue(value);
+ }
+
+ isFormControlSimpleCondition(control: FormControl): boolean {
+ return control.value.hasOwnProperty('field');
+ }
+
+ removeCondition(control: FormControl) {
+ const formArray = this.isFormControlSimpleCondition(control) ? this.simpleConditionsFormArray : this.compositeConditionsFormArray;
+ const index = (formArray.value as FormControl[]).indexOf(control.value);
+ formArray.removeAt(index);
+ }
+
+ addSimpleCondition() {
+ const newCondition: RuleSimpleCondition = {
+ field: 'cm:name',
+ comparator: 'equals',
+ parameter: ''
+ };
+ this.simpleConditionsFormArray.push(new FormControl(newCondition));
+ }
+
+ addCompositeCondition() {
+ const newCondition: RuleCompositeCondition = {
+ inverted: false,
+ booleanMode: 'and',
+ compositeConditions: [],
+ simpleConditions: []
+ };
+ this.compositeConditionsFormArray.push(new FormControl(newCondition));
+ }
+
+ ngOnDestroy() {
+ this.formSubscription.unsubscribe();
+ }
+}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.validators.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.validators.ts
new file mode 100644
index 000000000..cbd36f5fe
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-composite-condition.validators.ts
@@ -0,0 +1,42 @@
+/*!
+ * @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 .
+ */
+
+import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
+import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
+
+const isCompositeConditionValid = (value: RuleCompositeCondition, isRootCondition = true): boolean => {
+ if (value.compositeConditions.length > 0) {
+ return value.compositeConditions.reduce(
+ (arrayValid: boolean, nestedCondition: RuleCompositeCondition) => arrayValid && isCompositeConditionValid(nestedCondition, false),
+ true
+ );
+ }
+ return !!value.simpleConditions.length || isRootCondition;
+};
+
+export const ruleCompositeConditionValidator =
+ (): ValidatorFn =>
+ (control: AbstractControl): ValidationErrors | null =>
+ isCompositeConditionValid(control.value) ? null : { ruleCompositeConditionInvalid: true };
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-comparators.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-comparators.ts
new file mode 100644
index 000000000..f7b31bb6d
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-comparators.ts
@@ -0,0 +1,95 @@
+/*!
+ * @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 .
+ */
+
+export interface RuleConditionComparator {
+ name: string;
+ labels: {
+ [key: string]: string;
+ };
+}
+
+export const ruleConditionComparators: RuleConditionComparator[] = [
+ {
+ name: 'equals',
+ labels: {
+ string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.EQUALS',
+ number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.EQUALS',
+ date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON',
+ special: ''
+ }
+ },
+ {
+ name: 'contains',
+ labels: {
+ string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.CONTAINS'
+ }
+ },
+ {
+ name: 'startsWith',
+ labels: {
+ string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.STARTS_WITH'
+ }
+ },
+ {
+ name: 'endsWith',
+ labels: {
+ string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ENDS_WITH'
+ }
+ },
+ {
+ name: 'greaterThan',
+ labels: {
+ number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.GREATER_THAN',
+ date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.AFTER'
+ }
+ },
+ {
+ name: 'lessThan',
+ labels: {
+ number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.LESS_THAN',
+ date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.BEFORE'
+ }
+ },
+ {
+ name: 'greaterThanOrEqual',
+ labels: {
+ number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.GREATER_THAN_OR_EQUAL',
+ date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON_OR_AFTER'
+ }
+ },
+ {
+ name: 'lessThanOrEqual',
+ labels: {
+ number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.LESS_THAN_OR_EQUAL',
+ date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON_OR_BEFORE'
+ }
+ },
+ {
+ name: 'instanceOf',
+ labels: {
+ type: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.INSTANCE_OF'
+ }
+ }
+];
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-fields.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-fields.ts
new file mode 100644
index 000000000..d7af1b30e
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-condition-fields.ts
@@ -0,0 +1,70 @@
+/*!
+ * @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 .
+ */
+
+export type RuleConditionFieldType = 'string' | 'number' | 'date' | 'type' | 'special';
+
+export interface RuleConditionField {
+ name: string;
+ label: string;
+ type: RuleConditionFieldType;
+}
+
+export const ruleConditionFields: RuleConditionField[] = [
+ {
+ name: 'cm:name',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.NAME',
+ type: 'string'
+ },
+ {
+ name: 'size',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.SIZE',
+ type: 'number'
+ },
+ {
+ name: 'mimetype',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.MIMETYPE',
+ type: 'string'
+ },
+ {
+ name: 'encoding',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.ENCODING',
+ type: 'string'
+ },
+ {
+ name: 'category',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_CATEGORY',
+ type: 'special'
+ },
+ {
+ name: 'tag',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_TAG',
+ type: 'special'
+ },
+ {
+ name: 'aspect',
+ label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_ASPECT',
+ type: 'special'
+ }
+];
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.html
new file mode 100644
index 000000000..381d127f3
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.html
@@ -0,0 +1,24 @@
+
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.scss
new file mode 100644
index 000000000..1d9e989ed
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.scss
@@ -0,0 +1,18 @@
+.aca-rule-simple-condition {
+ &__form {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ mat-form-field {
+ flex: 2;
+ font-size: inherit;
+ }
+
+ &__comparator-input {
+ &.hidden {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.spec.ts
new file mode 100644
index 000000000..69a943b58
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.spec.ts
@@ -0,0 +1,92 @@
+/*!
+ * @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 .
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
+import { CoreTestingModule } from '@alfresco/adf-core';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+describe('RuleSimpleConditionUiComponent', () => {
+ let fixture: ComponentFixture;
+
+ const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
+ fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
+
+ const changeMatSelectValue = (dataAutomationId: string, value: string) => {
+ const matSelect = getByDataAutomationId(dataAutomationId).nativeElement;
+ matSelect.click();
+ fixture.detectChanges();
+ const matOption = fixture.debugElement.query(By.css(`.mat-option[ng-reflect-value="${value}"]`)).nativeElement;
+ matOption.click();
+ fixture.detectChanges();
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CoreTestingModule],
+ declarations: [RuleSimpleConditionUiComponent]
+ });
+
+ fixture = TestBed.createComponent(RuleSimpleConditionUiComponent);
+ });
+
+ it('should default the field to the name, the comparator to equals and the value empty', () => {
+ fixture.detectChanges();
+
+ expect(getByDataAutomationId('field-select').componentInstance.value).toBe('cm:name');
+ expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals');
+ expect(getByDataAutomationId('value-input').nativeElement.value).toBe('');
+ });
+
+ it('should hide the comparator select box if the type of the field is special', () => {
+ fixture.detectChanges();
+ const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement;
+
+ expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
+ expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
+
+ changeMatSelectValue('field-select', 'category');
+
+ expect(fixture.componentInstance.isComparatorHidden).toBeTruthy();
+ expect(getComputedStyle(comparatorFormField).display).toBe('none');
+ });
+
+ it('should set the comparator to equals if the field is set to a type with different comparators', () => {
+ const onChangeFieldSpy = spyOn(fixture.componentInstance, 'onChangeField').and.callThrough();
+ fixture.detectChanges();
+ changeMatSelectValue('comparator-select', 'contains');
+
+ expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('contains');
+ changeMatSelectValue('field-select', 'mimetype');
+
+ expect(onChangeFieldSpy).toHaveBeenCalledTimes(1);
+ expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('contains');
+ changeMatSelectValue('field-select', 'size');
+
+ expect(onChangeFieldSpy).toHaveBeenCalledTimes(2);
+ expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals');
+ });
+});
diff --git a/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.ts
new file mode 100644
index 000000000..660b70cf0
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-details/conditions/rule-simple-condition.ui-component.ts
@@ -0,0 +1,105 @@
+/*!
+ * @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 .
+ */
+
+import { Component, forwardRef, OnDestroy, ViewEncapsulation } from '@angular/core';
+import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
+import { RuleConditionField, ruleConditionFields } from './rule-condition-fields';
+import { RuleConditionComparator, ruleConditionComparators } from './rule-condition-comparators';
+
+@Component({
+ selector: 'aca-rule-simple-condition',
+ templateUrl: './rule-simple-condition.ui-component.html',
+ styleUrls: ['./rule-simple-condition.ui-component.scss'],
+ encapsulation: ViewEncapsulation.None,
+ host: { class: 'aca-rule-simple-condition' },
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ multi: true,
+ useExisting: forwardRef(() => RuleSimpleConditionUiComponent)
+ }
+ ]
+})
+export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnDestroy {
+ readonly fields = ruleConditionFields;
+
+ form = new FormGroup({
+ field: new FormControl('cm:name'),
+ comparator: new FormControl('equals'),
+ parameter: new FormControl()
+ });
+
+ private formSubscription = this.form.valueChanges.subscribe((value) => {
+ this.onChange(value);
+ this.onTouch();
+ });
+
+ get selectedField(): RuleConditionField {
+ return this.fields.find((field) => field.name === this.form.get('field').value);
+ }
+ get selectedFieldComparators(): RuleConditionComparator[] {
+ return ruleConditionComparators.filter((comparator) => Object.keys(comparator.labels).includes(this.selectedField.type));
+ }
+ get isComparatorHidden(): boolean {
+ return this.selectedField?.type === 'special';
+ }
+ get comparatorControl(): AbstractControl {
+ return this.form.get('comparator');
+ }
+
+ onChange: (condition: RuleSimpleCondition) => void = () => undefined;
+ onTouch: () => void = () => undefined;
+
+ writeValue(value: RuleSimpleCondition) {
+ this.form.setValue(value);
+ }
+
+ registerOnChange(fn: () => void) {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: () => void) {
+ this.onTouch = fn;
+ }
+
+ setDisabledState(isDisabled: boolean) {
+ if (isDisabled) {
+ this.form.disable();
+ } else {
+ this.form.enable();
+ }
+ }
+
+ onChangeField() {
+ if (!this.selectedFieldComparators.find((comparator) => comparator.name === this.comparatorControl.value)) {
+ this.comparatorControl.setValue('equals');
+ }
+ }
+
+ ngOnDestroy() {
+ this.formSubscription.unsubscribe();
+ }
+}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/edit-rule-dialog.smart-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/edit-rule-dialog.smart-component.spec.ts
index 84fc7f947..0c339758e 100644
--- a/projects/aca-folder-rules/src/lib/rule-details/edit-rule-dialog.smart-component.spec.ts
+++ b/projects/aca-folder-rules/src/lib/rule-details/edit-rule-dialog.smart-component.spec.ts
@@ -29,6 +29,7 @@ import { By } from '@angular/platform-browser';
import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CoreTestingModule } from '@alfresco/adf-core';
+import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component';
describe('EditRuleDialogComponent', () => {
let fixture: ComponentFixture;
@@ -41,7 +42,7 @@ describe('EditRuleDialogComponent', () => {
const setupBeforeEach = (dialogOptions: EditRuleDialogOptions = {}) => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
- declarations: [EditRuleDialogSmartComponent, RuleDetailsUiComponent],
+ declarations: [EditRuleDialogSmartComponent, RuleCompositeConditionUiComponent, RuleDetailsUiComponent],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: dialogOptions }
@@ -105,7 +106,7 @@ describe('EditRuleDialogComponent', () => {
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE');
});
- it('should show a "create" label in the submit button', () => {
+ it('should show an "update" label in the submit button', () => {
fixture.detectChanges();
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement;
diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html
index 492cacfd8..509562fb0 100644
--- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html
+++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.html
@@ -24,4 +24,9 @@
+
+
+
+
+ {{ getErrorMessage(conditions) | translate }}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss
index ac2d8cccf..84186ab77 100644
--- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss
+++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.scss
@@ -33,12 +33,17 @@
border-bottom: 1px solid var(--theme-border-color);
}
- *:disabled {
- color: #000000;
+ *:disabled, .mat-select-disabled .mat-select-value {
+ color: inherit;
}
textarea {
min-height: 4em;
}
+
+ & > .aca-rule-composite-condition + .mat-error {
+ font-size: 75%;
+ margin-left: 16px;
+ }
}
}
diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts
index 2368a33a9..b223ec252 100644
--- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts
+++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.spec.ts
@@ -28,6 +28,7 @@ import { CoreTestingModule } from '@alfresco/adf-core';
import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { Rule } from '../model/rule.model';
import { By } from '@angular/platform-browser';
+import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component';
describe('RuleDetailsUiComponent', () => {
let fixture: ComponentFixture;
@@ -45,7 +46,7 @@ describe('RuleDetailsUiComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
- declarations: [RuleDetailsUiComponent]
+ declarations: [RuleCompositeConditionUiComponent, RuleDetailsUiComponent]
});
fixture = TestBed.createComponent(RuleDetailsUiComponent);
diff --git a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts
index 1ba61a9d8..50939dbd3 100644
--- a/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts
+++ b/projects/aca-folder-rules/src/lib/rule-details/rule-details.ui-component.ts
@@ -24,10 +24,11 @@
*/
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
-import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { Rule } from '../model/rule.model';
+import { ruleCompositeConditionValidator } from './conditions/rule-composite-condition.validators';
@Component({
selector: 'aca-rule-details',
@@ -66,17 +67,26 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
get name(): AbstractControl {
return this.form.get('name');
}
-
get description(): AbstractControl {
return this.form.get('description');
}
-
- constructor(private formBuilder: FormBuilder) {}
+ get conditions(): AbstractControl {
+ return this.form.get('conditions');
+ }
ngOnInit() {
- this.form = this.formBuilder.group({
- name: [this.initialValue.name || '', Validators.required],
- description: [this.initialValue.description || '']
+ this.form = new FormGroup({
+ name: new FormControl(this.initialValue.name || '', Validators.required),
+ description: new FormControl(this.initialValue.description || ''),
+ conditions: new FormControl(
+ this.initialValue.conditions || {
+ inverted: false,
+ booleanMode: 'and',
+ compositeConditions: [],
+ simpleConditions: []
+ },
+ ruleCompositeConditionValidator()
+ )
});
this.readOnly = this._readOnly;
@@ -104,6 +114,8 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
getErrorMessage(control: AbstractControl): string {
if (control.hasError('required')) {
return 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.REQUIRED';
+ } else if (control.hasError('ruleCompositeConditionInvalid')) {
+ return 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.RULE_COMPOSITE_CONDITION_INVALID';
}
return '';
}