[ACS-3259] Create / Update rule dialog - options section (#2651)

* first commit

* intermediate commit

* some progress

* [ACS-3259] Create / Update rule dialog - options section
This commit is contained in:
Nikita Maliarchuk 2022-09-20 11:14:49 +02:00 committed by GitHub
parent e6f1a1bd4a
commit c2b9c6cc55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 256 additions and 6 deletions

View File

@ -14,7 +14,8 @@
"LABEL": { "LABEL": {
"NAME": "Name", "NAME": "Name",
"DESCRIPTION": "Description", "DESCRIPTION": "Description",
"WHEN": "When" "WHEN": "When",
"OPTIONS": "Other options"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"NAME": "Enter rule name", "NAME": "Enter rule name",
@ -31,6 +32,13 @@
"UPDATE": "Items are updated", "UPDATE": "Items are updated",
"OUTBOUND": "Items are deleted or leave this folder" "OUTBOUND": "Items are deleted or leave this folder"
}, },
"OPTIONS": {
"CASCADE": "Rule applies to subfolders",
"ASYNCHRONOUS": "Run rule in the background",
"ENABLED": "Disable rule",
"ERROR_SCRIPT": "If errors occur run script",
"SELECT_ACTION": "Select action"
},
"COMPARATORS": { "COMPARATORS": {
"EQUALS": "(=) Equals", "EQUALS": "(=) Equals",
"CONTAINS": "Contains", "CONTAINS": "Contains",

View File

@ -39,6 +39,7 @@ import { BreadcrumbModule, DocumentListModule } from '@alfresco/adf-content-serv
import { RuleListItemUiComponent } from './rules-list/rule/rule-list-item.ui-component'; import { RuleListItemUiComponent } from './rules-list/rule/rule-list-item.ui-component';
import { RulesListUiComponent } from './rules-list/rules-list.ui-component'; import { RulesListUiComponent } from './rules-list/rules-list.ui-component';
import { RuleTriggersUiComponent } from './rule-details/triggers/rule-triggers.ui-component'; import { RuleTriggersUiComponent } from './rule-details/triggers/rule-triggers.ui-component';
import { RuleOptionsUiComponent } from './rule-details/options/rule-options.ui-component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -67,7 +68,8 @@ const routes: Routes = [
RuleSimpleConditionUiComponent, RuleSimpleConditionUiComponent,
RulesListUiComponent, RulesListUiComponent,
RuleListItemUiComponent, RuleListItemUiComponent,
RuleTriggersUiComponent RuleTriggersUiComponent,
RuleOptionsUiComponent
] ]
}) })
export class AcaFolderRulesModule { export class AcaFolderRulesModule {

View File

@ -0,0 +1,35 @@
<div class="options-list" [formGroup]="form">
<div class="options-list__asynchronous">
<mat-checkbox
formControlName="asynchronous"
(change)="toggleScriptSelector()"
[attr.data-automation-id]="'rule-option-checkbox-asynchronous'">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.ASYNCHRONOUS' | translate }}
</mat-checkbox>
<div class="select-action" *ngIf="!preview">
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.ERROR_SCRIPT' | translate}}:</span>
<mat-form-field>
<mat-select [disabled]="disableSelector" formControlName="errorScript"
placeholder="{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.SELECT_ACTION' | translate}}"
[attr.data-automation-id]="'rule-option-select-errorScript'">
<mat-option>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.SELECT_ACTION' | translate}}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="options-list__rest">
<mat-checkbox
formControlName="cascade"
[attr.data-automation-id]="'rule-option-checkbox-cascade'">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.CASCADE' | translate }}
</mat-checkbox>
<mat-checkbox
[attr.data-automation-id]="'rule-option-checkbox-enabled'"
[checked]="!form.value.enabled" *ngIf="!preview"
(change)="form.get('enabled').setValue(!$event.checked)">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.ENABLED' | translate }}
</mat-checkbox>
</div>
</div>

View File

@ -0,0 +1,20 @@
.options-list {
display: flex;
column-gap: 25px;
padding: 0.75em 0;
&__rest {
display: flex;
flex-wrap: wrap;
column-gap: 25px;
}
}
.select-action {
margin-left: 5px;
margin-top: 12px;
span {
display: block;
}
}

View File

@ -0,0 +1,94 @@
/*!
* @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 { ComponentFixture, TestBed, inject, waitForAsync } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms';
import { RuleOptionsUiComponent } from './rule-options.ui-component';
import { CoreTestingModule } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
describe('RuleOptionsUiComponent', () => {
let component: RuleOptionsUiComponent;
let fixture: ComponentFixture<RuleOptionsUiComponent>;
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const toggleMatCheckbox = (dataAutomationId: string) => {
((getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).children[0] as HTMLElement).click();
};
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [FormsModule, ReactiveFormsModule, CoreTestingModule],
declarations: [RuleOptionsUiComponent]
}).compileComponents();
})
);
beforeEach(inject([FormBuilder], (fb: FormBuilder) => {
fixture = TestBed.createComponent(RuleOptionsUiComponent);
component = fixture.componentInstance;
component.form = fb.group({
asynchronous: [false],
cascade: [false],
enabled: [true],
errorScript: ['']
});
fixture.detectChanges();
}));
it('checkboxes should be falsy by default, selector is disabled', () => {
expect(component).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-cascade').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-enabled').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeTruthy();
});
it('should enable selector when async checkbox is truthy', () => {
toggleMatCheckbox('rule-option-checkbox-asynchronous');
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy();
});
it('should hide some fields in preview mode', () => {
component.preview = true;
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-cascade')).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript')).toBeFalsy();
});
});

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'aca-rule-options',
templateUrl: 'rule-options.ui-component.html',
styleUrls: ['rule-options.ui-component.scss']
})
export class RuleOptionsUiComponent {
@Input() form: FormGroup;
@Input() preview: boolean;
disableSelector = true;
toggleScriptSelector() {
this.disableSelector = !this.disableSelector;
}
}

View File

@ -42,4 +42,12 @@
<aca-rule-composite-condition formControlName="conditions"></aca-rule-composite-condition> <aca-rule-composite-condition formControlName="conditions"></aca-rule-composite-condition>
<mat-error class="rule-details-error">{{ getErrorMessage(conditions) | translate }}</mat-error> <mat-error class="rule-details-error">{{ getErrorMessage(conditions) | translate }}</mat-error>
<hr>
<div class="aca-rule-details__form__row">
<div class="label">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.OPTIONS' | translate }}</div>
<aca-rule-options [form]="form" [preview]="preview" data-automation-id="rule-details-options-component"></aca-rule-options>
</div>
</form> </form>

View File

@ -30,6 +30,8 @@ import { Rule } from '../model/rule.model';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component'; import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component';
import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component'; import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component';
import { MatCheckbox } from '@angular/material/checkbox';
import { RuleOptionsUiComponent } from './options/rule-options.ui-component';
describe('RuleDetailsUiComponent', () => { describe('RuleDetailsUiComponent', () => {
let fixture: ComponentFixture<RuleDetailsUiComponent>; let fixture: ComponentFixture<RuleDetailsUiComponent>;
@ -39,7 +41,10 @@ describe('RuleDetailsUiComponent', () => {
id: 'rule-id', id: 'rule-id',
name: 'Rule name', name: 'Rule name',
description: 'This is the description of the rule', description: 'This is the description of the rule',
triggers: ['update', 'outbound'] triggers: ['update', 'outbound'],
asynchronous: true,
cascade: true,
enabled: true
}; };
const getHtmlElement = <T>(dataAutomationId: string) => const getHtmlElement = <T>(dataAutomationId: string) =>
@ -51,7 +56,7 @@ describe('RuleDetailsUiComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CoreTestingModule], imports: [CoreTestingModule],
declarations: [RuleCompositeConditionUiComponent, RuleDetailsUiComponent, RuleTriggersUiComponent] declarations: [RuleCompositeConditionUiComponent, RuleDetailsUiComponent, RuleTriggersUiComponent, RuleOptionsUiComponent]
}); });
fixture = TestBed.createComponent(RuleDetailsUiComponent); fixture = TestBed.createComponent(RuleDetailsUiComponent);
@ -65,10 +70,16 @@ describe('RuleDetailsUiComponent', () => {
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input'); const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea'); const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component'); const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionAsynchronous = getComponentInstance<MatCheckbox>('rule-option-checkbox-asynchronous');
const ruleOptionCascade = getComponentInstance<MatCheckbox>('rule-option-checkbox-cascade');
const ruleOptionDisabled = getComponentInstance<MatCheckbox>('rule-option-checkbox-enabled');
expect(nameInput.value).toBe(testValue.name); expect(nameInput.value).toBe(testValue.name);
expect(descriptionTextarea.value).toBe(testValue.description); expect(descriptionTextarea.value).toBe(testValue.description);
expect(ruleTriggersComponent.value).toEqual(testValue.triggers); expect(ruleTriggersComponent.value).toEqual(testValue.triggers);
expect(ruleOptionAsynchronous.checked).toBe(testValue.asynchronous);
expect(ruleOptionCascade.checked).toBe(testValue.cascade);
expect(ruleOptionDisabled.checked).toBe(!testValue.enabled);
}); });
it('should modify the form if the value input property is modified', () => { it('should modify the form if the value input property is modified', () => {
@ -95,10 +106,16 @@ describe('RuleDetailsUiComponent', () => {
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input'); const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea'); const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component'); const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionAsynchronous = getComponentInstance<MatCheckbox>('rule-option-checkbox-asynchronous');
const ruleOptionCascade = getComponentInstance<MatCheckbox>('rule-option-checkbox-cascade');
const ruleOptionDisabled = getComponentInstance<MatCheckbox>('rule-option-checkbox-enabled');
expect(nameInput.disabled).toBeFalsy(); expect(nameInput.disabled).toBeFalsy();
expect(descriptionTextarea.disabled).toBeFalsy(); expect(descriptionTextarea.disabled).toBeFalsy();
expect(ruleTriggersComponent.readOnly).toBeFalsy(); expect(ruleTriggersComponent.readOnly).toBeFalsy();
expect(ruleOptionAsynchronous.disabled).toBeFalsy();
expect(ruleOptionCascade.disabled).toBeFalsy();
expect(ruleOptionDisabled.disabled).toBeFalsy();
}); });
it('should not be editable if read-only', () => { it('should not be editable if read-only', () => {
@ -108,9 +125,13 @@ describe('RuleDetailsUiComponent', () => {
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input'); const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea'); const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component'); const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionAsynchronous = getComponentInstance<MatCheckbox>('rule-option-checkbox-asynchronous');
const ruleOptionCascade = getComponentInstance<MatCheckbox>('rule-option-checkbox-cascade');
expect(nameInput.disabled).toBeTruthy(); expect(nameInput.disabled).toBeTruthy();
expect(descriptionTextarea.disabled).toBeTruthy(); expect(descriptionTextarea.disabled).toBeTruthy();
expect(ruleTriggersComponent.readOnly).toBeTruthy(); expect(ruleTriggersComponent.readOnly).toBeTruthy();
expect(ruleOptionAsynchronous.disabled).toBeTruthy();
expect(ruleOptionCascade.disabled).toBeTruthy();
}); });
}); });

View File

@ -64,7 +64,11 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
name: newValue.name || FolderRulesService.emptyRule.name, name: newValue.name || FolderRulesService.emptyRule.name,
description: newValue.description || FolderRulesService.emptyRule.description, description: newValue.description || FolderRulesService.emptyRule.description,
triggers: newValue.triggers || FolderRulesService.emptyRule.triggers, triggers: newValue.triggers || FolderRulesService.emptyRule.triggers,
conditions: newValue.conditions || FolderRulesService.emptyRule.conditions conditions: newValue.conditions || FolderRulesService.emptyRule.conditions,
asynchronous: newValue.asynchronous || FolderRulesService.emptyRule.asynchronous,
errorScript: newValue.errorScript || FolderRulesService.emptyRule.errorScript,
cascade: newValue.cascade || FolderRulesService.emptyRule.cascade,
enabled: newValue.enabled || FolderRulesService.emptyRule.enabled
}; };
if (this.form) { if (this.form) {
this.form.setValue(newValue); this.form.setValue(newValue);
@ -95,6 +99,18 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
get conditions(): UntypedFormControl { get conditions(): UntypedFormControl {
return this.form.get('conditions') as UntypedFormControl; return this.form.get('conditions') as UntypedFormControl;
} }
get asynchronous(): UntypedFormControl {
return this.form.get('asynchronous') as UntypedFormControl;
}
get errorScript(): UntypedFormControl {
return this.form.get('errorScript') as UntypedFormControl;
}
get cascade(): UntypedFormControl {
return this.form.get('cascade') as UntypedFormControl;
}
get enabled(): UntypedFormControl {
return this.form.get('enabled') as UntypedFormControl;
}
ngOnInit() { ngOnInit() {
this.form = new UntypedFormGroup({ this.form = new UntypedFormGroup({
@ -109,7 +125,11 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
simpleConditions: [] simpleConditions: []
}, },
ruleCompositeConditionValidator() ruleCompositeConditionValidator()
) ),
asynchronous: new UntypedFormControl(this.value.asynchronous),
errorScript: new UntypedFormControl(this.value.errorScript),
cascade: new UntypedFormControl(this.value.cascade),
enabled: new UntypedFormControl(this.value.enabled)
}); });
this.readOnly = this._readOnly; this.readOnly = this._readOnly;