diff options
Diffstat (limited to 'public/src/app/rule-engine/action-list')
3 files changed, 467 insertions, 0 deletions
diff --git a/public/src/app/rule-engine/action-list/action-list.component.html b/public/src/app/rule-engine/action-list/action-list.component.html new file mode 100644 index 0000000..e7879b7 --- /dev/null +++ b/public/src/app/rule-engine/action-list/action-list.component.html @@ -0,0 +1,100 @@ +<form #actionListFrm="ngForm" class="wrapper" data-tests-id="popupRuleEditor"> + <div class="header"> + <div style="display: flex; justify-content: flex-end; align-items: center;"> + <a (click)="closeDialog()" data-tests-id="btnBackRule" style="cursor: pointer;text-decoration: none; color: #009fdb;"> + <mat-icon fontSet="fontawesome" fontIcon="fa-angle-left" style="height: 22px; width: 22px; font-size: 22px; padding-right: 20px;"></mat-icon> + </a> + <span style="font-size: 18px;">New Rule Editor</span> + </div> + + <div style="display: flex; justify-content: flex-end; align-items: center; padding: 10px;"> + + <button mat-icon-button [disabled]="actions.length === 0" (click)="saveRole()" data-tests-id="btnSave"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'save' | feather:22"></span> + </button> + + <button mat-raised-button [disabled]="actions.length === 0" style="height: 35px; margin-left: 20px;" color="primary" data-tests-id="btnDone" + (click)="saveAndDone()"> + Done + </button> + </div> + </div> + <!-- error container --> + <div *ngIf="error" data-tests-id="errorList" class="error-list"> + <div *ngFor="let item of error"> + {{ item }} + </div> + </div> + + <div class="main-content"> + <div> + <div class="required" style="padding-right: 1rem; width: 100%; padding-bottom: 0.5rem;">Description</div> + <input type="text" [(ngModel)]="description" ngModel required name="descInput" style="padding: 5px; width: 100%;" data-tests-id="inputDescription"> + </div> + + <div style="margin: 1.5rem 0;"> + <div class="pretty p-svg" style="margin: 1rem 0rem;"> + <input type="checkbox" name="isCondition" data-tests-id="isCondition" [checked]="ifStatement" (change)="ifStatement = !ifStatement" + /> + <div class="state"> + <!-- svg path --> + <svg class="svg svg-icon" viewBox="0 0 20 20"> + <path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z" + style="stroke: #009fdb; fill:#009fdb;"></path> + </svg> + <label>Conditional Action</label> + </div> + </div> + + <div *ngIf="ifStatement"> + <app-condition #condition (removeConditionCheck)="removeConditionCheck($event)" (onConditionChange)="updateCondition($event)"></app-condition> + </div> + </div> + + <div> + <div class="required" style="padding-bottom: 0.5rem"> + Action + </div> + <div style="display: flex;"> + <select [(ngModel)]="selectedAction" name="selectedAction" style="height: 2rem; width: 150px; margin-right: 1rem;" data-tests-id="selectAction"> + <option [ngValue]="null" disabled>Select Action</option> + <option value="copy">Copy</option> + <option value="concat">Concat</option> + <option value="map">Map</option> + <option value="date formatter">Date Formatter</option> + </select> + + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" style="height: 24px; width: 24px; display:flex; justify-content: center;" (click)="addAction2list(selectedAction)" + data-tests-id="btnAddAction"> + <span style="display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:16"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Action</span> + </div> + + </div> + + <div> + <ul> + <li *ngFor="let action of actions; let index = index" style="list-style: none; margin: 1rem 0;" (mouseleave)="hoveredIndex=-1" + (mouseover)="hoveredIndex=index" data-tests-id="action"> + <div style="display:flex;"> + <app-action #actions style="width: 100%;" [action]="action"></app-action> + + <div style="height: 45px; display: flex; align-items: center;"> + <button mat-icon-button class='button-remove' (click)="removeAction(action)" data-tests-id="deleteAction"> + <mat-icon>delete</mat-icon> + </button> + </div> + </div> + </li> + </ul> + </div> + + </div> + </div> +</form> diff --git a/public/src/app/rule-engine/action-list/action-list.component.scss b/public/src/app/rule-engine/action-list/action-list.component.scss new file mode 100644 index 0000000..39b9dce --- /dev/null +++ b/public/src/app/rule-engine/action-list/action-list.component.scss @@ -0,0 +1,77 @@ +.wrapper { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + + .header { + display: flex; + justify-content: space-between; + align-items: center; + color: #191919; + border-bottom: 2px solid #d2d2d2; + // padding: 0.4rem 1rem; + } + + .main-content { + display: flex; + flex-direction: column; + flex: 1; + flex-grow: 1; + // overflow-y: auto; + padding: 24px 10px; + width: 100%; + // height: calc(100vh - 54px); + } +} + +.mat-fab, +.mat-mini-fab, +.mat-raised-button { + box-shadow: none; +} + +.button-remove { + display: flex; + justify-content: center; + padding-top: 5px; + color: #a7a7a7; + &:hover { + color: #009fdb; + } +} + +:host { + @mixin md-icon-size($size: 24px) { + // font-size: $size; + height: $size; + width: $size; + } + + .material-icons.mat-icon { + @include md-icon-size(24px); + } + /deep/ .mat-button-wrapper { + padding: 0; + } + .mat-icon { + width: 18px; + height: 18px; + } +} + +.black { + color: black; +} +.highlight { + color: #009fdb; +} + +.error-list { + margin: 10px; + color: white; + background: red; + padding: 1rem; + border-radius: 5px; + font-weight: bold; +} diff --git a/public/src/app/rule-engine/action-list/action-list.component.ts b/public/src/app/rule-engine/action-list/action-list.component.ts new file mode 100644 index 0000000..40ff46d --- /dev/null +++ b/public/src/app/rule-engine/action-list/action-list.component.ts @@ -0,0 +1,290 @@ +import { + Component, + Inject, + ViewChildren, + QueryList, + AfterViewInit, + ViewChild, + Input +} from '@angular/core'; +import { RuleEngineApiService } from '../api/rule-engine-api.service'; +import { Subject } from 'rxjs/Subject'; +import { v1 as uuid } from 'uuid'; +import { environment } from '../../../environments/environment'; +import { ActionComponent } from '../action/action.component'; +import { cloneDeep } from 'lodash'; +import { Store } from '../../store/store'; +import { NgForm } from '@angular/forms'; + +@Component({ + selector: 'app-action-list', + templateUrl: './action-list.component.html', + styleUrls: ['./action-list.component.scss'] +}) +export class ActionListComponent implements AfterViewInit { + error: Array<string>; + condition: any; + eventType: string; + version: string; + params; + selectedAction; + targetSource; + description = ''; + actions = new Array(); + ifStatement = false; + uid = ''; + backupActionForCancel = new Array(); + @ViewChild('actionListFrm') actionListFrm: NgForm; + @ViewChild('condition') conditionRef; + @ViewChildren('actions') actionsRef: QueryList<ActionComponent>; + + constructor(private _ruleApi: RuleEngineApiService, public store: Store) { + this._ruleApi.editorData.subscribe(data => { + this.params = data.params; + console.log('update.. params', data.params); + this.targetSource = data.targetSource; + this.version = data.version; + this.eventType = data.eventType; + if (data.item) { + // edit mode set values to attributes + console.log('actions %o', data.item.actions); + this.actions = this.convertActionDataFromServer(data.item.actions); + this.backupActionForCancel = cloneDeep(this.actions); + this.condition = data.item.condition; + this.uid = data.item.uid; + this.description = data.item.description; + this.ifStatement = this.condition == null ? false : true; + } else { + this.actions = new Array(); + this.backupActionForCancel = new Array(); + this.condition = null; + this.uid = ''; + this.description = ''; + this.ifStatement = false; + } + this.selectedAction = null; + }); + } + + convertActionDataFromServer(actions) { + return actions.map(item => { + if (!item.hasOwnProperty('nodes')) { + return Object.assign({}, item, { nodes: this.targetSource }); + } + }); + } + + ngAfterViewInit() { + // console.log(this.actionsRef.toArray()); + if (this.condition) { + if (this.condition.name === 'condition') { + this.conditionRef.updateMode(true, this.condition); + } else { + const convertedCondition = this.convertConditionFromServer( + this.condition + ); + this.conditionRef.updateMode(false, convertedCondition); + } + } + } + + addAction2list(selectedAction) { + if (selectedAction !== null) { + this.actions.push({ + id: uuid(), + nodes: this.targetSource, + from: { + value: '', + regex: '', + state: 'closed', + values: [{ value: '' }, { value: '' }] + }, + actionType: this.selectedAction, + target: '', + map: { + values: [{ key: '', value: '' }], + haveDefault: false, + default: '' + }, + dateFormatter: { + fromFormat: '', + toFormat: '', + fromTimezone: '', + toTimezone: '' + } + }); + } + } + + removeConditionCheck(flag) { + this.ifStatement = flag; + } + + removeAction(action) { + this.actions = this.actions.filter(item => { + return item.id !== action.id; + }); + } + + updateCondition(data) { + this.condition = data; + } + + changeRightToArrayOrString(data, toArray) { + data.forEach(element => { + if (element.name === 'operator') { + this.changeRightToArrayOrString(element.children, toArray); + } + if (element.name === 'condition') { + if (toArray) { + element.right = element.right.split(','); + } else { + element.right = element.right.join(','); + } + } + }); + console.log(data); + return data; + } + + prepareDataToSaveRule() { + // action array + console.log(this.actions); + const actionSetData = this.actions.map(item => { + return { + id: item.id, + actionType: item.actionType, + from: item.from, + target: + typeof item.selectedNode === 'string' + ? item.selectedNode + : typeof item.selectedNode === 'undefined' + ? item.target + : item.selectedNode.id, + map: item.map, + dateFormatter: item.dateFormatter + }; + }); + let conditionData2server = null; + if (this.ifStatement) { + if (this.conditionRef.conditionTree) { + // change condition right to array + conditionData2server = this.convertConditionToServer( + this.conditionRef.conditionTree + ); + } + } + // data structure + return { + version: this.version, + eventType: this.eventType, + uid: this.uid, + description: this.description, + actions: actionSetData, + condition: this.ifStatement ? conditionData2server : null + }; + } + + errorHandler(error) { + this.store.loader = false; + console.log(error); + this.error = []; + if (typeof error === 'string') { + this.error.push(error); + } else { + console.log(error.notes); + const errorFromServer = Object.values(error)[0] as any; + if (Object.keys(error)[0] === 'serviceExceptions') { + this.error = errorFromServer.map(x => x.formattedErrorMessage); + } else { + this.error.push(errorFromServer.formattedErrorMessage); + } + } + } + + saveAndDone() { + const data = this.prepareDataToSaveRule(); + this.store.loader = true; + this._ruleApi.modifyRule(data).subscribe( + response => { + this.store.loader = false; + this.store.updateRuleInList(response); + this._ruleApi.callUpdateVersionLock(); + this.store.isLeftVisible = true; + }, + error => { + this.errorHandler(error); + }, + () => { + this.store.loader = false; + } + ); + } + + saveRole() { + const actionComp = this.actionsRef.toArray(); + const filterInvalidActions = actionComp.filter(comp => { + return ( + comp.fromInstance.fromFrm.invalid || + comp.targetInstance.targetFrm.invalid || + comp.actionFrm.invalid + ); + }); + if (this.actionListFrm.valid && filterInvalidActions.length === 0) { + const data = this.prepareDataToSaveRule(); + this.store.loader = true; + this._ruleApi.modifyRule(data).subscribe( + response => { + this.store.loader = false; + this.store.updateRuleInList(response); + this._ruleApi.callUpdateVersionLock(); + this.uid = response.uid; + // add toast notification + }, + error => { + this.errorHandler(error); + }, + () => { + this.store.loader = false; + } + ); + } else { + // scroll to first invalid element + const elId = filterInvalidActions[0].action.id; + const el = document.getElementById(elId as string); + const label = el.children.item(0) as HTMLElement; + el.scrollIntoView(); + } + } + + public convertConditionFromServer(condition) { + const temp = new Array(); + temp.push(condition); + const cloneCondition = cloneDeep(temp); + const conditionSetData = this.changeRightToArrayOrString( + cloneCondition, + false + ); + console.log('condition to server:', conditionSetData); + return conditionSetData; + } + + public convertConditionToServer(tree) { + const cloneCondition = cloneDeep(tree); + const conditionSetData = this.changeRightToArrayOrString( + cloneCondition, + true + ); + let simpleCondition = null; + if (conditionSetData[0].children.length === 1) { + simpleCondition = conditionSetData[0].children; + } + console.log('condition to server:', conditionSetData); + return simpleCondition !== null ? simpleCondition[0] : conditionSetData[0]; + } + + closeDialog(): void { + this.actions = this.backupActionForCancel; + this.store.isLeftVisible = true; + } +} |