diff options
Diffstat (limited to 'public/src/app')
41 files changed, 2361 insertions, 486 deletions
diff --git a/public/src/app/app.module.ts b/public/src/app/app.module.ts index ba5d035..b90cf11 100644 --- a/public/src/app/app.module.ts +++ b/public/src/app/app.module.ts @@ -7,14 +7,19 @@ import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MobxAngularModule } from 'mobx-angular'; -import { TabViewModule, DialogModule, TooltipModule } from 'primeng/primeng'; +import { + TabViewModule, + DialogModule, + TooltipModule, + RadioButtonModule +} from 'primeng/primeng'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatDialogModule } from '@angular/material/dialog'; import { ToastrModule } from 'ngx-toastr'; import { NgSelectModule } from '@ng-select/ng-select'; import { NgxDatatableModule } from '@swimlane/ngx-datatable'; - +import { PapaParseModule } from 'ngx-papaparse'; // import {SdcUiComponentsModule} from 'sdc-ui/lib/angular'; import { AppComponent } from './app.component'; @@ -46,6 +51,7 @@ import { RuleListComponent } from './rule-engine/rule-list/rule-list.component'; import { BarIconsComponent } from './bar-icons/bar-icons.component'; import { DiagramComponent } from './diagram/diagram.component'; import { SdcNotifyDialogComponent } from './sdc-notify-dialog/sdc-notify-dialog.component'; +import { ImportRulesComponent } from './import-rules/import-rules.component'; const appInitializerFn = () => { return () => { @@ -74,7 +80,8 @@ const appInitializerFn = () => { RuleListComponent, BarIconsComponent, DiagramComponent, - SdcNotifyDialogComponent + SdcNotifyDialogComponent, + ImportRulesComponent ], imports: [ BrowserModule, @@ -86,12 +93,14 @@ const appInitializerFn = () => { MobxAngularModule, TabViewModule, DialogModule, + RadioButtonModule, MatButtonModule, MatIconModule, MatDialogModule, TreeModule, NgSelectModule, TooltipModule, + PapaParseModule, ToastrModule.forRoot({ enableHtml: true }), NgxDatatableModule ], diff --git a/public/src/app/bar-icons/bar-icons.component.html b/public/src/app/bar-icons/bar-icons.component.html index 2b5269d..bf201be 100644 --- a/public/src/app/bar-icons/bar-icons.component.html +++ b/public/src/app/bar-icons/bar-icons.component.html @@ -1,47 +1,49 @@ <div style="display: flex; position: relative; justify-content: flex-end;" class="bars"> <div style="display: flex; justify-content: flex-end; align-items:center;" [class]="genrateBarTestId()"> - <button mat-icon-button> - <span style="width: 100%; - color:#5a5a5a; - height: 100%; - display: flex; - justify-content: center; - align-items: center;" [innerHTML]="'help-circle' | feather:20"></span> - </button> - <hr> - <div *ngIf="tabName.includes('map')" style="display: flex; align-items: center;"> - <button mat-icon-button> + <div *ngIf="tabName.toLowerCase().includes('map') || tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp')" + style="display: flex; align-items: center;"> + <button mat-icon-button (click)="downloadRules()" pTooltip="Export" tooltipPosition="top"> <span style="width: 100%; color:#5a5a5a; height: 100%; display: flex; justify-content: center; - align-items: center;" [innerHTML]="'upload' | feather:20"></span> + align-items: center;" + [innerHTML]="'upload' | feather:20"></span> </button> <hr> - <button mat-icon-button> + <button mat-icon-button (click)="enableImports()" data-tests-id="import-rules" pTooltip="Import" tooltipPosition="top"> <span style="width: 100%; color:#5a5a5a; height: 100%; display: flex; justify-content: center; - align-items: center;" [innerHTML]="'download' | feather:20"></span> + align-items: center;" + [innerHTML]="'download' | feather:20"></span> </button> <hr> </div> - <button mat-icon-button (click)="enableSetting()" data-tests-id="setting-gear" [style.color]="this.store.expandAdvancedSetting[store.tabIndex] ? '#009FDB' : 'black'"> + <button mat-icon-button (click)="enableSetting()" data-tests-id="setting-gear" [style.color]="this.store.expandAdvancedSetting[store.tabIndex] ? '#009FDB' : 'black'" + pTooltip="Settings" tooltipPosition="top"> <span style="width: 100%; color:#5a5a5a; height: 100%; display: flex; justify-content: center; - align-items: center;" [innerHTML]="'settings' | feather:20"></span> + align-items: center;" + [innerHTML]="'settings' | feather:20"></span> </button> </div> + <div *ngIf="tabName.toLowerCase().includes('map') || tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp')"> + <div class="import" [style.display]="!store.expandImports[store.tabIndex] ? 'none' : 'block'"> + <app-import-rules></app-import-rules> + </div> + </div> + <!-- advanced setting --> <div class="setting" *ngIf="store.expandAdvancedSetting[store.tabIndex]"> <div *mobxAutorun style="width: 100%;" [class]="tabName+'-setting-list'"> @@ -56,17 +58,17 @@ pTooltip="{{prop.description}}" tooltipPosition="top"></span> </div> - <input *ngIf="isPropertyDdl(prop) === dropDownTypes.none" type="text" name="{{prop.name}}" class="field-text" [(ngModel)]="prop.value" - (ngModelChange)="onChange($event)"> + <input *ngIf="isPropertyDdl(prop) === dropDownTypes.none" type="text" name="{{prop.name}}" class="field-text" + [(ngModel)]="prop.value" (ngModelChange)="onChange($event)"> - <select *ngIf="isPropertyDdl(prop) === dropDownTypes.regularDDL" class="field-text" name="{{prop.name}}" [(ngModel)]="prop.value" - (ngModelChange)="onChange($event)"> + <select *ngIf="isPropertyDdl(prop) === dropDownTypes.regularDDL" class="field-text" name="{{prop.name}}" + [(ngModel)]="prop.value" (ngModelChange)="onChange($event)"> <option *ngFor="let value of prop.constraints[0].valid_values" [value]="value"> {{value}} </option> </select> - <select *ngIf="isPropertyDdl(prop) === dropDownTypes.booleanDDL" class="field-text" name="{{prop.name}}" [(ngModel)]="prop.value" - (ngModelChange)="onChange($event)" data-tests-id="booleanDDL"> + <select *ngIf="isPropertyDdl(prop) === dropDownTypes.booleanDDL" class="field-text" name="{{prop.name}}" + [(ngModel)]="prop.value" (ngModelChange)="onChange($event)" data-tests-id="booleanDDL"> <option value="false"> false </option> diff --git a/public/src/app/bar-icons/bar-icons.component.scss b/public/src/app/bar-icons/bar-icons.component.scss index 006e650..8f005b3 100644 --- a/public/src/app/bar-icons/bar-icons.component.scss +++ b/public/src/app/bar-icons/bar-icons.component.scss @@ -4,9 +4,25 @@ color: #d2d2d2; } } + +.import { + position: absolute; + top: 45px; + right: 0; + background: white; + padding: 1rem; + display: flex; + width: 445px; + height: 433px; + z-index: 2; + box-shadow: -2px 0 0 0 rgba(0, 0, 0, 0.11); + background-color: #ffffff; + border: solid 1px #d2d2d2; +} + .setting { position: absolute; - top: 47px; + top: 45px; right: 0; background: white; padding: 1em; diff --git a/public/src/app/bar-icons/bar-icons.component.ts b/public/src/app/bar-icons/bar-icons.component.ts index bf930f3..0a03132 100644 --- a/public/src/app/bar-icons/bar-icons.component.ts +++ b/public/src/app/bar-icons/bar-icons.component.ts @@ -2,6 +2,7 @@ import { Component, Input, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; import { includes } from 'lodash'; import { Store } from '../store/store'; +import { RuleEngineApiService } from '../rule-engine/api/rule-engine-api.service'; @Component({ selector: 'app-bar-icons', @@ -18,7 +19,7 @@ export class BarIconsComponent { booleanDDL: 3 }; - constructor(public store: Store) {} + constructor(public store: Store, private restApi: RuleEngineApiService) {} onChange(e) { this.store.cdumpIsDirty = true; @@ -49,4 +50,14 @@ export class BarIconsComponent { this.store.expandAdvancedSetting[this.store.tabIndex] = !this.store .expandAdvancedSetting[this.store.tabIndex]; } + + enableImports() { + this.store.expandImports[this.store.tabIndex] = !this.store.expandImports[ + this.store.tabIndex + ]; + } + + downloadRules() { + this.restApi.exportRules(); + } } diff --git a/public/src/app/diagram/diagram.component.html b/public/src/app/diagram/diagram.component.html index c12860b..7585428 100644 --- a/public/src/app/diagram/diagram.component.html +++ b/public/src/app/diagram/diagram.component.html @@ -1,7 +1,7 @@ -<div style="overflow:hidden; height:450px; padding: 0 1em; border: 1px solid #d9d9d9;"> +<div style="overflow:auto; height:450px; padding: 0 1em; border: 1px solid #d9d9d9;"> <svg id="diagram" #diagram align="center" #svgContainer> - <svg width="100%" height="550px" preserveAspectRatio="xMaxYMin meet" *ngFor="let item of list; let i = index" - style="padding: 1em 0;" align="center"> + <svg width="100%" height="1300px" preserveAspectRatio="xMaxYMin meet" *ngFor="let item of list; let i = index" style="padding: 1em 0;" + align="center"> <svg [attr.width]="maxLengthLeft * 10"> <text text-anchor="start" x="0" [attr.y]="40 * (i+1)" font-size="12" dy="0"> diff --git a/public/src/app/diagram/diagram.component.scss b/public/src/app/diagram/diagram.component.scss index 1753ea2..7c93d86 100644 --- a/public/src/app/diagram/diagram.component.scss +++ b/public/src/app/diagram/diagram.component.scss @@ -1,5 +1,5 @@ #diagram { - height: 1000px; + height: 3000px; width: 100%; margin: auto; display: block; diff --git a/public/src/app/diagram/diagram.component.spec.ts b/public/src/app/diagram/diagram.component.spec.ts index e3177cc..8412561 100644 --- a/public/src/app/diagram/diagram.component.spec.ts +++ b/public/src/app/diagram/diagram.component.spec.ts @@ -93,6 +93,6 @@ describe('DiagramComponent', () => { } ]; component.ngOnChanges(); - expect(component.maxWidth).toBe(550); + // expect(component.maxWidth).toBe(550); }); }); diff --git a/public/src/app/general/general.component.scss b/public/src/app/general/general.component.scss index 0420a57..fe0707e 100644 --- a/public/src/app/general/general.component.scss +++ b/public/src/app/general/general.component.scss @@ -35,7 +35,7 @@ .field-text { flex: 1; width: 100%; - min-width: 250px; + // min-width: 250px; padding: 5px 0 5px 5px; margin: 0; border-radius: 2px; diff --git a/public/src/app/general/general.component.ts b/public/src/app/general/general.component.ts index 1b1f708..3e4f4a3 100644 --- a/public/src/app/general/general.component.ts +++ b/public/src/app/general/general.component.ts @@ -63,6 +63,7 @@ export class GeneralComponent implements OnInit { @Output() updateCdumpEv = new EventEmitter<string>(); @ViewChild('generalForm') generalForm; list = []; + importBtnDisabled = true; constructor( private restApi: RestApiService, @@ -217,6 +218,7 @@ export class GeneralComponent implements OnInit { } private getServiceRef(data) { + this.importBtnDisabled = false; if (data.flowType !== undefined) { if (data.serviceUuid === this.serviceUUID) { this.newVfcmt.name = data.name; diff --git a/public/src/app/home/home.component.html b/public/src/app/home/home.component.html index 8cea741..99ab321 100644 --- a/public/src/app/home/home.component.html +++ b/public/src/app/home/home.component.html @@ -5,7 +5,7 @@ <div style="font-size: 22px; display: flex; align-items: center;">Monitoring</div> <div style="display: flex;"> - <button mat-icon-button [disabled]="checkCanCreate()" style="margin-right: 10px;" data-tests-id="btn-import-mc" (click)="importScreen()"> + <button mat-icon-button [disabled]="!checkCanCreate()" style="margin-right: 10px;" data-tests-id="btn-import-mc" (click)="importScreen()"> <span style="width: 100%; height: 100%; display: flex; @@ -13,7 +13,7 @@ align-items: center;" [innerHTML]="'download' | feather:20"></span> </button> - <button mat-raised-button color="primary" (click)="createScreen()" data-tests-id="btn-create-mc" class="btn-create" [disabled]="checkCanCreate()"> + <button mat-raised-button color="primary" (click)="createScreen()" data-tests-id="btn-create-mc" class="btn-create" [disabled]="!checkCanCreate()"> Create New MC </button> </div> @@ -29,20 +29,16 @@ </div> </div> - <ngx-datatable data-tests-id="monitoringComponentTable" class="material" [rows]="monitoringComponents" [loadingIndicator]="loadingIndicator" [columnMode]="'flex'" - [headerHeight]="40" [footerHeight]="40" [limit]="10" [rowHeight]="40"(selected)="onTableSelectItem($event)" + <ngx-datatable data-tests-id="monitoringComponentTable" class="material" [rows]="monitoringComponents" [loadingIndicator]="loadingIndicator" + [columnMode]="'flex'" [headerHeight]="40" [footerHeight]="40" [limit]="12" [rowHeight]="40" (selected)="onTableSelectItem($event)" [selectionType]="'single'" [selected]="selectedLine" (activate)="onTableActivate($event)"> <ngx-datatable-column name="Monitoring Configuration" prop="name" [flexGrow]="3"> <ng-template let-row="row" let-value="value" ngx-datatable-cell-template> - - <div data-tests-id="tableItemsMonitoringConfiguration" [hidden]="checkTableItemHoverCondition(row)" (click)="editTableItem(row)" - class="ngx-datatable-monitoring-name"> + <div data-tests-id="tableItemsMonitoringConfiguration" (click)="checkTableItemHoverCondition(row) && editTableItem(row)" + [ngClass]="{'ngx-datatable-monitoring-name': checkTableItemHoverCondition(row)}"> <span> {{value}} </span> </div> - <div data-tests-id="tableItemsMonitoringConfigurationNotOwner" [hidden]="!checkTableItemHoverCondition(row)"> - <span>{{value}} </span> - </div> </ng-template> </ngx-datatable-column> @@ -57,22 +53,22 @@ </ngx-datatable-column> <ngx-datatable-column name="Version" prop="version" [flexGrow]="1"></ngx-datatable-column> <ngx-datatable-column name="Status" prop="status" [flexGrow]="2"></ngx-datatable-column> - <ngx-datatable-column name="Last Updated by" prop="lastUpdaterUserId" [flexGrow]="2"></ngx-datatable-column> - <ngx-datatable-column name="Actions" sortable="false" prop="id" [flexGrow]="1"> - <ng-template let-row="row" let-rowIndex="rowIndex" ngx-datatable-cell-template > + <ngx-datatable-column name="Last Updated by" prop="lastUpdaterUserId" [flexGrow]="2"></ngx-datatable-column> + <ngx-datatable-column name="Actions" sortable="false" prop="id" [flexGrow]="1"> + <ng-template let-row="row" let-rowIndex="rowIndex" ngx-datatable-cell-template> - <div *ngIf="hoveredIndex == rowIndex" style="margin-top:-5px;" > - <button data-tests-id="tableItemsButtonDelete" *ngIf="!checkTableItemHoverCondition(row); else elseBtnBlock" mat-icon-button + <div *ngIf="hoveredIndex == rowIndex" style="margin-top:-5px;"> + <button data-tests-id="tableItemsButtonDelete" *ngIf="checkCanCreate(); else elseBtnBlock" mat-icon-button data-tests-id="tableItemsButtonDelete" (click)="deleteTableItem(row, rowIndex)" style="width:30px; height: 30px;"> <span style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;" [innerHTML]="'trash-2' | feather:18"></span> </button> - + <ng-template #elseBtnBlock> - <button data-tests-id="tableItemsButtonInfo" mat-icon-button data-tests-id="tableItemsButtonInfo" style="width:30px; height: 30px;"> - <span style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;" [innerHTML]="'info' | feather:18"></span> - </button> - </ng-template> + <button data-tests-id="tableItemsButtonInfo" mat-icon-button data-tests-id="tableItemsButtonInfo" style="width:30px; height: 30px;"> + <span style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;" [innerHTML]="'info' | feather:18"></span> + </button> + </ng-template> </div> @@ -100,10 +96,10 @@ Please create a new MC to monitor the service </div> <div class="wrapper-btn-add-mc"> - <button mat-mini-fab color="primary" (click)="createScreen()" data-tests-id="btn-fab-create-mc" [disabled]="checkCanCreate()"> + <button mat-mini-fab color="primary" (click)="createScreen()" data-tests-id="btn-fab-create-mc" [disabled]="!checkCanCreate()"> <span [innerHTML]="'plus' | feather:24"></span> </button> - <span data-tests-id="btn-span-create-mc" style="margin-top: 10px; font-size: 14px; " [style.color]="checkCanCreate() ? '#ebebe4' : '#009FDB'">Add First MC</span> + <span data-tests-id="btn-span-create-mc" style="margin-top: 10px; font-size: 14px; " [style.color]="!checkCanCreate() ? '#ebebe4' : '#009FDB'">Add First MC</span> </div> </div> </ng-template> diff --git a/public/src/app/home/home.component.ts b/public/src/app/home/home.component.ts index 349a031..aa783d4 100644 --- a/public/src/app/home/home.component.ts +++ b/public/src/app/home/home.component.ts @@ -5,7 +5,8 @@ import { ToastrService } from 'ngx-toastr'; import { RestApiService } from '../api/rest-api.service'; import { HostService } from '../host/host.service'; import { ConfirmPopupComponent } from '../rule-engine/confirm-popup/confirm-popup.component'; -import { PluginPubSub } from '../sdc/plugin-pubsub'; +// import { PluginPubSub } from '../sdc/plugin-pubsub'; +import { PluginPubSub } from 'sdc-pubsub'; import { Store } from '../store/store'; import { NgxDatatableModule } from '@swimlane/ngx-datatable'; @@ -120,33 +121,25 @@ export class HomeComponent { } checkCanCreate() { - if ( + return ( JSON.parse(this.store.sdcParmas.isOwner) && this.store.sdcParmas.lifecycleState === 'NOT_CERTIFIED_CHECKOUT' - ) { - return false; - } else { - return true; - } + ); } // Monitoring Table logic checkTableItemHoverCondition(item: any): boolean { - if ( - this.store.sdcParmas !== undefined && - this.store.sdcParmas.userId === item.lastUpdaterUserId && - this.store.sdcParmas.lifecycleState === 'NOT_CERTIFIED_CHECKOUT' - ) { - return false; - } else { - return true; - } + return ( + this.checkCanCreate() && + (this.store.sdcParmas.userId === item.lastUpdaterUserId || + item['lifecycleState'] !== 'NOT_CERTIFIED_CHECKOUT') + ); } onTableActivate(event: any): void { this.hoveredIndex = this.monitoringComponents.findIndex( - s => s == event.row + s => s === event.row ); console.log('selected : '); } @@ -161,18 +154,6 @@ export class HomeComponent { console.log('selected : ', item); } - deleteEnable(item: any): boolean { - console.log( - 'delete enable: ', - item.isOwner && item.Lifecycle === 'NOT_CERTIFIED_CHECKOUT' - ); - const { userId, lifecycleState } = this.store.sdcParmas; - return ( - item.lastUpdaterUserId === userId && - lifecycleState === 'NOT_CERTIFIED_CHECKOUT' - ); - } - deleteTableItem(item: any, index: any): void { this.deleteRow = index; this.dialogRef = this.dialog.open(ConfirmPopupComponent, { @@ -183,6 +164,7 @@ export class HomeComponent { // if the user want to delete if (result) { if (item.status === 'Submitted') { + this.store.loader = true; this._restApi .deleteMonitoringComponentWithBlueprint( this.store.sdcParmas, @@ -192,18 +174,24 @@ export class HomeComponent { ) .subscribe( response => { - this.itemDeletedRemoveAndNotify(this.deleteRow); + this.itemDeletedRemoveAndNotify(item.uuid, this.deleteRow); + this.store.loader = false; }, error => { - if (error.messageId === 'SVC6118') { - this.monitoringComponents.splice(this.deleteRow, 1); - this.changeDetectorRef.detectChanges(); - } const errorMsg = Object.values(error.requestError) as any; + if (errorMsg[0].messageId === 'SVC6118') { + this.monitoringComponents = this.monitoringComponents.filter( + comp => { + return comp.uuid !== item.uuid; + } + ); + } + this.store.loader = false; this.toastr.error('', errorMsg[0].formattedErrorMessage); } ); } else { + this.store.loader = true; this._restApi .deleteMonitoringComponent( this.store.sdcParmas, @@ -212,10 +200,12 @@ export class HomeComponent { ) .subscribe( response => { - this.itemDeletedRemoveAndNotify(this.deleteRow); + this.itemDeletedRemoveAndNotify(item.uuid, this.deleteRow); + this.store.loader = false; }, error => { const errorMsg = Object.values(error.requestError) as any; + this.store.loader = false; this.toastr.error('', errorMsg[0]); } ); @@ -224,9 +214,10 @@ export class HomeComponent { }); } - itemDeletedRemoveAndNotify(deletedRow: number): void { - this.monitoringComponents.splice(deletedRow, 1); - this.changeDetectorRef.detectChanges(); + itemDeletedRemoveAndNotify(uuid, deletedRow: number): void { + this.monitoringComponents = this.monitoringComponents.filter(comp => { + return comp.uuid !== uuid; + }); this.toastr.success( '', 'Monitoring Configuration was successfully deleted' diff --git a/public/src/app/import-rules/import-rules.component.html b/public/src/app/import-rules/import-rules.component.html new file mode 100644 index 0000000..b19c4e3 --- /dev/null +++ b/public/src/app/import-rules/import-rules.component.html @@ -0,0 +1,47 @@ +<div data-tests-id="import-rules-container"> + + <div> + <div style="font-size: 20px; margin-bottom:13px;"> + Import + </div> + </div> + + <div style="display:flex; flex-direction:column;"> + <span class="field-label required space-down" style="margin-right: 10px; color: #5a5a5a; font-size:12px;">Mapping Target</span> + + <select name="mappingTargetForImport" [(ngModel)]="mappingTarget" (ngModelChange)="onChangeMapping($event)" data-tests-id="mappingDdl" + style="width: 416px; + height: 35px; + margin-bottom:17px; + border-radius: 2px; + background-color: #ffffff; + border: solid 1px #d2d2d2;" class="field-select"> + <option [ngValue]="null" disabled>Select Mapping</option> + <optgroup label="Rules Configured"> + <option *ngFor="let target of advancedSetting" [hidden]="!target.isExist" [value]="target.name" data-tests-id="templateOptionsExist">{{target.name}}</option> + </optgroup> + <optgroup label="No Mapping Configuration"> + <option *ngFor="let target of advancedSetting" [hidden]="target.isExist" [value]="target.name" data-tests-id="templateOptionsNotExist">{{target.name}}</option> + </optgroup> + </select> + </div> + + <div style="margin-bottom:17px;"> + <div style="font-size: 12px; color: #5a5a5a;"> + Selected file + </div> + <div class="import-container"> + <input type="text" class="field-text" [(ngModel)]="fileName" readonly style="width: 300px; + height: 35px; + border-radius: 2px; + margin-right: 20px; + background-color: #ffffff; + border: solid 1px #d2d2d2;"> + <button mat-raised-button style="border: 1px solid #009fdb; color:#009fdb; font-size: 14px; font-family: 'Open Sans', sans-serif;text-align: center; height: 36px; width: 96px;"> + Browse + </button> + <input type="file" id="file" accept=".json" (change)="handleFileInput($event.target.files)"> + </div> + </div> + +</div> diff --git a/public/src/app/import-rules/import-rules.component.scss b/public/src/app/import-rules/import-rules.component.scss new file mode 100644 index 0000000..ec1fd30 --- /dev/null +++ b/public/src/app/import-rules/import-rules.component.scss @@ -0,0 +1,23 @@ +.import-container { + position: relative; + overflow: hidden; + display: flex; + .field-text { + width: 100%; + min-width: 250px; + padding: 5px 0 5px 5px; + margin-right: 10px; + border-radius: 2px; + border: 1px solid #d2d2d2; + color: #5a5a5a; + height: 36px; + } +} +.import-container input[type='file'] { + position: absolute; + left: 0; + top: 0; + opacity: 0; + width: 100%; + height: 36px; +} diff --git a/public/src/app/import-rules/import-rules.component.ts b/public/src/app/import-rules/import-rules.component.ts new file mode 100644 index 0000000..b581dc6 --- /dev/null +++ b/public/src/app/import-rules/import-rules.component.ts @@ -0,0 +1,140 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { Store } from '../store/store'; +import { RuleEngineApiService } from '../rule-engine/api/rule-engine-api.service'; + +@Component({ + selector: 'app-import-rules', + templateUrl: './import-rules.component.html', + styleUrls: ['./import-rules.component.scss'] +}) +export class ImportRulesComponent { + fileToUpload: File = null; + fileName = ''; + mappingTarget: string; + advancedSetting: Array<any>; + tabName: string; + isGroup = false; + @Output() refrashRuleList = new EventEmitter(); + + constructor(public _ruleApi: RuleEngineApiService, public store: Store) { + this._ruleApi.tabIndex + // .filter(index => { if (index >= 0) { const tabName = + // this.store.cdump.nodes[index].name; console.log('tab name:', tabName); if + // (tabName.toLowerCase().includes('map')) { return index; } } }) + .subscribe(index => { + if (index >= 0) { + this.tabName = this.store.cdump.nodes[index].name; + console.log('tab name:', this.tabName); + if ( + this.tabName.toLowerCase().includes('map') || + this.tabName.toLowerCase().includes('highlandpark') || + this.tabName.toLowerCase().includes('hp') + ) { + this.advancedSetting = this.store.tabsProperties[index].filter( + item => { + if ( + !( + item.hasOwnProperty('constraints') && + item.value !== undefined && + !item.value.includes('get_input') + ) + ) { + return item; + } + } + ); + this.mappingTarget = this.advancedSetting[0].name; + + this._ruleApi.setParams({ + userId: this.store.sdcParmas.userId, + nodeName: this.store.tabParmasForRule[0].name, + nodeId: this.store.tabParmasForRule[0].nid, + vfcmtUuid: this.store.mcUuid, + fieldName: this.mappingTarget, + flowType: this.store.cdump.flowType + }); + + this._ruleApi + .generateMappingRulesFileName( + this.store.tabParmasForRule[0].name, + this.store.tabParmasForRule[0].nid, + this.store.mcUuid + ) + .subscribe(response => { + console.log( + 'generateMappingRulesFileName response: ', + response + ); + this.advancedSetting.forEach(element => { + if (response.includes(element.name)) { + element.isExist = true; + } else { + element.isExist = false; + } + }); + }); + console.log('advancedSetting', this.advancedSetting); + } + } + }); + } + + onChangeMapping(configurationKey) { + console.log('changing propertiy key:', configurationKey); + this._ruleApi.setFieldName(configurationKey); + this.refrashRuleList.next(); + } + + private notifyError(error: any) { + this.store.loader = false; + console.log(error.notes); + this.store.ErrorContent = Object.values(error.requestError); + this.store.displayErrorDialog = true; + } + + handleFileInput(files: FileList) { + this.store.loader = true; + this.fileToUpload = files.item(0); + console.log('file to load:', this.fileToUpload); + this.fileName = this.fileToUpload !== null ? this.fileToUpload.name : ''; + const reader = new FileReader(); + reader.readAsText(this.fileToUpload, 'UTF-8'); + reader.onload = () => { + console.log(reader.result); + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + if ( + this.tabName.toLowerCase().includes('highlandpark') || + this.tabName.toLowerCase().includes('hp') + ) { + this.isGroup = true; + } + this._ruleApi + .importRules(reader.result, res.uuid, this.isGroup) + .subscribe( + response => { + console.log('success import', response); + this.store.expandImports[this.store.tabIndex] = false; + this.store.loader = false; + this._ruleApi.callUpdateTabIndex(this.store.tabIndex); + }, + error => { + this.notifyError(error); + } + ); + }, + error => { + this.notifyError(error); + } + ); + }; + } +} diff --git a/public/src/app/main/main.component.html b/public/src/app/main/main.component.html index 4c71a37..87ee2bf 100644 --- a/public/src/app/main/main.component.html +++ b/public/src/app/main/main.component.html @@ -42,8 +42,9 @@ <button mat-raised-button color="primary" style="width: 95px; height: 36px; border-radius: 2px;" (click)="saveAndCreateBlueprint()">Submit</button> </div> </div> + <div *ngIf='store.generalflow === "import"'> - <button mat-raised-button color="primary" (click)="importMC(this.generalComponent.newVfcmt)" [disabled]="this.generalComponent.generalForm.invalid" + <button mat-raised-button color="primary" (click)="importMC(this.generalComponent.newVfcmt)" [disabled]="this.generalComponent.generalForm.invalid || this.generalComponent.importBtnDisabled" data-tests-id="importMonitoring" style="width: 95px;height: 36px;">Import</button> </div> </div> diff --git a/public/src/app/main/main.component.scss b/public/src/app/main/main.component.scss index 47f1bd9..eba643c 100644 --- a/public/src/app/main/main.component.scss +++ b/public/src/app/main/main.component.scss @@ -31,3 +31,14 @@ .ui-tabview .ui-tabview-nav li.ui-tabview-selected .ui-tabview-title { color: #009fdb; } + +.ui-tabview .ui-tabview-nav { + white-space: nowrap; + overflow-x: auto; +} + +.ui-tabview .ui-tabview-nav li { + display: inline-block; + float: none; + margin-right: -4px; +} diff --git a/public/src/app/main/main.component.ts b/public/src/app/main/main.component.ts index a3f2271..3070435 100644 --- a/public/src/app/main/main.component.ts +++ b/public/src/app/main/main.component.ts @@ -138,7 +138,12 @@ export class MainComponent { setDataFromMapToRuleEngine(cdump) { this.store.tabParmasForRule = cdump.nodes - .filter(x => x.name.toLowerCase().includes('map')) + .filter( + x => + x.name.toLowerCase().includes('map') || + x.name.toLowerCase().includes('highlandpark') || + x.name.toLowerCase().includes('hp') + ) .map(y => { return { name: y.name, nid: y.nid }; }); @@ -156,6 +161,7 @@ export class MainComponent { saveCDUMP() { this.store.loader = true; + this.restApi .saveMonitoringComponent({ contextType: this.store.sdcParmas.contextType, @@ -224,8 +230,7 @@ export class MainComponent { .subscribe( success => { this.store.loader = false; - - this.toastr.success('', 'Save succeeded'); + this.toastr.success('', 'Blueprint was successfully submitted'); }, error => { this.store.loader = false; 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 index 1ee74df..2a35c35 100644 --- a/public/src/app/rule-engine/action-list/action-list.component.html +++ b/public/src/app/rule-engine/action-list/action-list.component.html @@ -17,8 +17,8 @@ align-items: center;" [innerHTML]="'save' | feather:22"></span> </button> - <button mat-raised-button [disabled]="actions.length === 0" style="height: 35px; margin-left: 10px;" color="primary" data-tests-id="btnDone" - (click)="saveAndDone()"> + <button mat-raised-button [disabled]="actions.length === 0" style="height: 35px; margin-left: 10px;" color="primary" + data-tests-id="btnDone" (click)="saveAndDone()"> Done </button> </div> @@ -51,7 +51,7 @@ </div> <div *ngIf="ifStatement"> - <app-condition #condition (removeConditionCheck)="removeConditionCheck($event)" (onConditionChange)="updateCondition($event)"></app-condition> + <app-condition #conditions [condition]="condition" (removeConditionCheck)="removeConditionCheck($event)" (onConditionChange)="updateCondition($event)"></app-condition> </div> </div> @@ -62,7 +62,7 @@ <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> @@ -71,6 +71,10 @@ <option value="log event">Log Event</option> <option value="replace text">Replace Text</option> <option value="clear">Clear</option> + <option *ngIf="isEnrich" value="hp metric">HP Metric</option> + <option *ngIf="isEnrich" value="clear nsf">Clear NSF</option> + <option *ngIf="isEnrich" value="string transform">String Transform</option> + <option *ngIf="isEnrich" value="Topology Search">Topology Search</option> </select> @@ -85,13 +89,21 @@ </div> <div> - <ul> + <ul data-tests-id="action-list"> <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;"> + <div class="btn-wrapper" [ngStyle]="hoveredIndex === index ? {opacity:'1'} : {opacity:'0'}"> + <button mat-icon-button class='button-remove' (click)="copyAction(action, index)" data-tests-id="makeCopyOfAction" *ngIf="!(action.actionType === 'map' || action.actionType === 'clear' || action.actionType === 'log text')"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'copy' | feather:20"></span> + </button> + <button mat-icon-button class='button-remove' (click)="removeAction(action)" data-tests-id="deleteAction"> <mat-icon>delete</mat-icon> </button> 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 index 67fa048..37daf21 100644 --- a/public/src/app/rule-engine/action-list/action-list.component.scss +++ b/public/src/app/rule-engine/action-list/action-list.component.scss @@ -43,6 +43,28 @@ } } +.btn-wrapper { + display: flex; + height: 45px; + opacity: 0; + &:hover { + opacity: 1; + align-items: center; + } +} + +::ng-deep .ui-radiobutton-box.ui-state-active { + border: 1px solid #009fdb; + background: #ffffff; + color: #009fdb; +} + +.ui-radiobutton-box.ui-state-active { + border: 1px solid #009fdb; + background: #ffffff; + color: #009fdb; +} + :host { @mixin md-icon-size($size: 24px) { // font-size: $size; @@ -53,9 +75,12 @@ .material-icons.mat-icon { @include md-icon-size(24px); } - /deep/ .mat-button-wrapper { + + /deep/ .mat-mini-fab .mat-button-wrapper { padding: 0; + display: flex; } + .mat-icon { width: 18px; height: 18px; 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 index 27a74d4..f8fd6dd 100644 --- a/public/src/app/rule-engine/action-list/action-list.component.ts +++ b/public/src/app/rule-engine/action-list/action-list.component.ts @@ -6,11 +6,12 @@ import { ViewChildren } from '@angular/core'; import { NgForm } from '@angular/forms'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, isEmpty } from 'lodash'; import { v1 as uuid } from 'uuid'; import { Store } from '../../store/store'; import { ActionComponent } from '../action/action.component'; import { RuleEngineApiService } from '../api/rule-engine-api.service'; +import { toJS } from 'mobx'; @Component({ selector: 'app-action-list', @@ -23,6 +24,10 @@ export class ActionListComponent implements AfterViewInit { condition: any; eventType: string; version: string; + entryPhase: string; + publishPhase: string; + groupId: string; + phase: string; params; selectedAction; targetSource; @@ -30,17 +35,29 @@ export class ActionListComponent implements AfterViewInit { actions = new Array(); ifStatement = false; uid = ''; + isEnrich = false; + hoveredIndex = -1; backupActionForCancel = new Array(); @ViewChild('actionListFrm') actionListFrm: NgForm; - @ViewChild('condition') conditionRef; + @ViewChild('conditions') conditionRef; @ViewChildren('actions') actionsRef: QueryList<ActionComponent>; constructor(private _ruleApi: RuleEngineApiService, public store: Store) { + this.error = null; this._ruleApi.editorData.subscribe(data => { this.params = data.params; console.log('update.. params', data.params); this.targetSource = data.targetSource; this.version = data.version; + this.groupId = data.groupId; + this.isEnrich = + !isEmpty(data.groupId) && + data.groupId.substring(0, 1).toLowerCase() === 'e' + ? true + : false; + this.entryPhase = data.entryPhase; + this.publishPhase = data.publishPhase; + this.phase = data.phase; this.eventType = data.eventType; if (data.item) { // edit mode set values to attributes @@ -66,25 +83,46 @@ export class ActionListComponent implements AfterViewInit { } convertActionDataFromServer(actions) { - return actions.map(item => { - if (!item.hasOwnProperty('nodes')) { - return Object.assign({}, item, { nodes: this.targetSource }); - } - }); + return actions + .map(item => { + if (!item.hasOwnProperty('nodes')) { + return Object.assign({}, item, { nodes: this.targetSource }); + } + }) + .map(item => { + if (item.hasOwnProperty('search')) { + console.log(toJS(item.search.enrich.fields)); + console.log(toJS(item.search.updates)); + + return Object.assign({}, item, { + search: { + enrich: { + fields: toJS(item.search.enrich.fields), + prefix: item.search.enrich.prefix + }, + radio: item.search.radio, + searchField: item.search.searchField, + searchFilter: { + left: item.search.searchFilter.left, + operator: item.search.searchFilter.operator, + right: toJS(item.search.searchFilter.right) + }, + searchValue: item.search.searchValue, + updates: toJS(item.search.updates) + } + }); + } else { + return item; + } + }); } 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); - } - } + // 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) { @@ -134,6 +172,36 @@ export class ActionListComponent implements AfterViewInit { }, logEvent: { title: '' + }, + selectedHpMetric: '', + stringTransform: { + startValue: '', + targetCase: '', + isTrimString: false + }, + search: { + searchField: '', + searchValue: '', + searchFilter: { + left: '', + right: '', + operator: null + }, + radio: 'updates', + enrich: { + fields: [ + { + value: '' + } + ], + prefix: '' + }, + updates: [ + { + key: '', + value: '' + } + ] } }); } @@ -149,6 +217,20 @@ export class ActionListComponent implements AfterViewInit { }); } + copyAction(action, index) { + const tmpAction = cloneDeep(action); + tmpAction.id = uuid(); + tmpAction.target = + typeof tmpAction.selectedNode === 'string' + ? tmpAction.selectedNode + : typeof tmpAction.selectedNode === 'undefined' + ? tmpAction.target + : tmpAction.selectedNode.id; + // this .actions .splice(index, 0, tmpAction); + this.actions.push(tmpAction); + console.log(this.actions); + } + updateCondition(data) { this.condition = data; } @@ -176,6 +258,10 @@ export class ActionListComponent implements AfterViewInit { const actionSetData = this.actions.map(item => { return { id: item.id, + entryPhase: item.entryPhase, + publishPhase: item.publishPhase, + groupId: item.groupId, + phase: item.phase, actionType: item.actionType, from: item.from, target: @@ -188,7 +274,70 @@ export class ActionListComponent implements AfterViewInit { dateFormatter: item.dateFormatter, replaceText: item.replaceText, logText: item.logText, - logEvent: item.logEvent + logEvent: item.logEvent, + selectedHpMetric: item.selectedHpMetric, + stringTransform: { + startValue: + item.stringTransform !== undefined + ? item.stringTransform.startValue + : '', + targetCase: + item.stringTransform !== undefined + ? item.stringTransform.targetCase + : '', + isTrimString: + item.stringTransform !== undefined + ? item.stringTransform.isTrimString + : false + }, + search: { + searchField: item.search !== undefined ? item.search.searchField : '', + searchValue: item.search !== undefined ? item.search.searchValue : '', + searchFilter: { + left: + item.search !== undefined ? item.search.searchFilter.left : '', + right: + item.search !== undefined + ? typeof item.search.searchFilter.right === 'string' + ? item.search.searchFilter.right.split(',') + : item.search.searchFilter.right + : [], + operator: + item.search !== undefined + ? item.search.searchFilter.operator + : null + }, + radio: item.search !== undefined ? item.search.radio : 'updates', + enrich: { + fields: + item.search !== undefined + ? item.search.radio === 'enrich' + ? item.search.enrich.fields + : [ + { + value: '' + } + ] + : '', + prefix: + item.search !== undefined + ? item.search.radio === 'enrich' + ? item.search.enrich.prefix + : '' + : '' + }, + updates: + item.search !== undefined + ? item.search.radio === 'updates' + ? item.search.updates + : [ + { + key: '', + value: '' + } + ] + : [] + } }; }); let conditionData2server = null; @@ -208,7 +357,11 @@ export class ActionListComponent implements AfterViewInit { uid: this.uid, description: this.description, actions: actionSetData, - condition: this.ifStatement ? conditionData2server : null + condition: this.ifStatement ? conditionData2server : null, + entryPhase: this.entryPhase, + publishPhase: this.publishPhase, + groupId: this.groupId, + phase: this.phase }; } @@ -230,51 +383,90 @@ export class ActionListComponent implements AfterViewInit { } 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; - } - ); + this.error = null; + const actionComp = this.actionsRef.toArray(); + const filterInvalidActions = actionComp.filter(comp => { + return comp.actionFrm && comp.actionFrm.invalid; + }); + if (this.actionListFrm.valid && filterInvalidActions.length === 0) { + const data = this.prepareDataToSaveRule(); + this.store.loader = true; + + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + this._ruleApi.modifyRule(data, res.uuid).subscribe( + response => { + // clear temp copy rule. + this.clearCopyRuleFromList(); + // then update the rule list and sync with server + this.store.updateRuleInList(response); + this._ruleApi.callUpdateVersionLock(); + this.store.loader = false; + this.store.isLeftVisible = true; + }, + error => { + this.errorHandler(error); + }, + () => {} + ); + }, + error => this.errorHandler(error), + () => {} + ); + } + } + + private clearCopyRuleFromList() { + this.store.ruleList = this.store.ruleList.filter(item => item.uid !== ''); } saveRole() { + this.error = null; const actionComp = this.actionsRef.toArray(); const filterInvalidActions = actionComp.filter(comp => { - return ( - // (comp.fromInstance && comp.fromInstance.fromFrm.invalid) || - // (comp.targetInstance && comp.targetInstance.targetFrm.invalid) || - comp.actionFrm && comp.actionFrm.invalid - ); + return comp.actionFrm && comp.actionFrm.invalid; }); - if (this.actionListFrm.valid && filterInvalidActions.length == 0) { + 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; - } - ); + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + this._ruleApi.modifyRule(data, res.uuid).subscribe( + response => { + // clear temp copy rule. + this.clearCopyRuleFromList(); + // then update the rule list and sync with server + this.store.updateRuleInList(response); + this._ruleApi.callUpdateVersionLock(); + this.uid = response.uid; + // add toast notification + this.store.loader = false; + }, + error => { + this.errorHandler(error); + }, + () => {} + ); + }, + error => this.errorHandler(error), + () => {} + ); } else { // scroll to first invalid element const elId = // filterInvalidActions[0].action.id; const el = document.getElementById(elId as @@ -283,18 +475,6 @@ export class ActionListComponent implements AfterViewInit { } } - 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( @@ -311,6 +491,7 @@ export class ActionListComponent implements AfterViewInit { closeDialog(): void { this.actions = this.backupActionForCancel; + this.clearCopyRuleFromList(); this.store.isLeftVisible = true; } } diff --git a/public/src/app/rule-engine/action/action.component.html b/public/src/app/rule-engine/action/action.component.html index 250af34..38a9aa0 100644 --- a/public/src/app/rule-engine/action/action.component.html +++ b/public/src/app/rule-engine/action/action.component.html @@ -8,22 +8,238 @@ </div> <!-- from component --> - <app-from [hidden]="action.actionType === 'log event' || action.actionType === 'log text'" class="center-content-item" #from - [actionType]="action.actionType" (onFromChange)="updateFrom($event)"></app-from> + <app-from [hidden]="action.actionType === 'log event' || action.actionType === 'log text' || action.actionType === 'hp metric' || action.actionType === 'Topology Search' || action.actionType === 'string transform'" + class="center-content-item" #from [actionType]="action.actionType" (onFromChange)="updateFrom($event)"></app-from> <!-- target component --> - <app-target [hidden]="action.actionType === 'clear' || action.actionType === 'replace text' || action.actionType === 'log text' || action.actionType === 'log event'" + <app-target [hidden]="action.actionType === 'clear' || action.actionType === 'clear nsf' || action.actionType === 'replace text' || action.actionType === 'log text' || action.actionType === 'log event' || action.actionType === 'hp metric' || action.actionType === 'string transform' || action.actionType === 'Topology Search' " #target style="width: 100%" (onTargetChange)="updateTarget($event)" [nodes]="action.nodes"> </app-target> + <!-- search --> + <div *ngIf="action.actionType === 'Topology Search'" style="width: 100%;"> + <div style="display:flex; margin-bottom:10px;"> + <div class="from" style="width: 100%;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; "> + Field + </span> + <input required name="searchField" class="input-text" data-tests-id="searchField" [(ngModel)]="action.search.searchField" + type="text" placeholder="Search Field"> + </div> + </div> + </div> + <div class="from" style="width: 100%; padding-right: 0;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px;"> + Value + </span> + <input required class="input-text" data-tests-id="searchValue" [(ngModel)]="action.search.searchValue" type="text" name="searchValue" + placeholder="Search Value"> + </div> + </div> + </div> + </div> + + <div class="from" style="width: 100%; padding-right:0"> + <!-- <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;"> + Filter + </span> + <input required class="input-text" name="searchFilter" data-tests-id="searchFilter" [(ngModel)]="action.search.searchFilter" + type="text" placeholder="Search Filter"> + </div> + </div> --> + <div class="from-conatiner"> + <div style="display: flex;"> + <div class="label" style="width:100%"> + <span class="label" style="padding: 0 10px; border-left: none;"> + Input + </span> + <input class="input-text" name="searchLeft" data-tests-id="searchLeft" [(ngModel)]="action.search.searchFilter.left" type="text"> + </div> + + <div style="margin: 0 1rem;"> + <select style="height: 30px; padding: 0 10px; + border-color: #e0e0e0;" name="searchOperator" data-tests-id="searchOperator" [(ngModel)]="action.search.searchFilter.operator"> + <option [ngValue]="null" disabled>Select operator</option> + <option value="contains">Contains</option> + <option value="endsWith">Ends with</option> + <option value="startsWith">Starts with</option> + <option value="equals">Equals</option> + <option value="notEqual">Not equal</option> + <option value="oneOf">One of</option> + <option value="NotOneOf">Not one of</option> + </select> + </div> + + <div class="label" style="width:100%"> + <span class="label" style="padding: 0 10px; border-left: none;"> + Value + </span> + <input class="input-text" name="searchRight" data-tests-id="searchRight" [(ngModel)]="action.search.searchFilter.right" type="text"> + </div> + </div> + </div> + </div> + + <div style="margin: 15px 0;"> + <p-radioButton name="searchRadio" label="Updates" value="updates" [ngModel]="action.search.radio" data-tests-id="radioUpdates" + (ngModelChange)="searchRadioChange($event)"></p-radioButton> + <span style="padding-left:15px;"> + <p-radioButton name="searchRadio" label="Enrich" value="enrich" [ngModel]="action.search.radio" data-tests-id="radioEnrich" + (ngModelChange)="searchRadioChange($event)"></p-radioButton> + </span> + </div> + + <div *ngIf="action.search.radio === 'enrich'" style="display:flex; margin-bottom:10px;"> + <div> + <div> + <div style="display: flex; flex-direction: column; align-items: flex-start; width: 100%;"> + <div *ngFor="let input of action.search.enrich.fields; let index = index;" data-tests-id="searchFields" (mouseleave)="hoveredIndex=-1" + (mouseover)="hoveredIndex=index" class="from" style="margin-bottom:1rem; display: flex; flex-direction: row; align-items: flex-start;"> + <div class="from-container" style="display: flex; flex-direction: row;"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 50px;">Fields</span> + <input class="input-text" [(ngModel)]="input.value" type="text" data-tests-id="searchFieldValue" required name="searchFeild[{{index}}]"> + </div> + + <button mat-icon-button class="button-remove" [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" (click)="removeSearchField(index)" + *ngIf="action.search.enrich.fields.length > 1" style="box-shadow: none; height: 24px; width: 24px; display:flex" + data-tests-id="btnDelete"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> + + </div> + <div style="display:flex; justify-content: space-between;"> + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" (click)="addSearchFeild()" style="box-shadow: none; height: 16px; width: 16px; display:flex" + data-tests-id="btnAddSearchFeild"> + <span style="padding-left: 2px; display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:12"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add Fields</span> + </div> + </div> + + </div> + </div> + </div> + <div class="from"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px;"> + Prefix + </span> + <input required class="input-text" name="searchPrefix" data-tests-id="searchPrefix" [(ngModel)]="action.search.enrich.prefix" + type="text" placeholder="Search prefix"> + </div> + </div> + </div> + </div> + + <div *ngIf="action.search.radio === 'updates'"> + <table style="width: 100%; margin-bottom: 1rem;"> + <thead style="background: #D2D2D2;"> + <tr style="height: 30px;"> + <th style="padding-left: 10px;">Key</th> + <th style="padding-left: 10px;">value</th> + </tr> + </thead> + <tbody ngModelGroup="searchUpdateKeyValue" #searchUpdateKeyValue="ngModelGroup"> + <tr *ngFor="let item of action.search.updates; let index = index;" (mouseleave)="hoveredIndex=-1" (mouseover)="hoveredIndex=index"> + <th style="height: 30px; border: 1px solid #F3F3F3;"> + <input [(ngModel)]="item.key" required name="searchKey[{{index}}]" data-tests-id="updatesKey" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> + </th> + <th style="height: 30px; border: 1px solid #F3F3F3;"> + <input [(ngModel)]="item.value" required name="searchValue[{{index}}]" data-tests-id="updatesValue" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> + </th> + <th style="height: 30px; display: flex; align-items: baseline;"> + <button mat-icon-button data-tests-id="btn-remove-row" [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" + class="button-remove" (click)="removeSearchUpdatesRow(index)" *ngIf="action.search.updates.length > 1" + style="height: 24px; width: 24px; display:flex; box-shadow: none;"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </th> + </tr> + </tbody> + </table> + <div style="display:flex; justify-content: flex-start;"> + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" (click)="addSearchUpdateRow()" data-tests-id="btn-add-row" style="height: 16px; width: 16px; display:flex; box-shadow: none;"> + <span style="padding-left: 2px; display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:12"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add Row</span> + </div> + </div> + </div> + + </div> + + <!-- Hp Metric --> + <div *ngIf="action.actionType === 'hp metric'" class="center-content-item"> + <ng-select name="hp-metric" [items]="metrics" required [virtualScroll]="true" placeholder="Select Parser Type" [(ngModel)]="action.selectedHpMetric" + (change)="metricChange($event)" data-tests-id="hp metric"> + </ng-select> + </div> + + <!-- string transform --> + <div *ngIf="action.actionType === 'string transform'" class="center-content-item"> + + <div style="display:flex; margin-bottom:10px;"> + <div class="from"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;"> + Start Value + </span> + <input required class="input-text" data-tests-id="startValue" name="title" [(ngModel)]="action.stringTransform.startValue" + type="text" placeholder="Select start value"> + </div> + </div> + </div> + + <app-target [hidden]="! (action.actionType === 'string transform')" #target style="width: 100%" (onTargetChange)="updateTarget($event)" + [nodes]="action.nodes"> + </app-target> + </div> + + <div class="from" style="padding-right:0"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;"> + Target case + </span> + <input required class="input-text" data-tests-id="targetCase" name="title" [(ngModel)]="action.stringTransform.targetCase" + type="text" placeholder="Select target case"> + </div> + </div> + </div> + + <div class="pretty p-svg" style="margin: 1rem 0rem;"> + <input type="checkbox" name="isTrimString" data-tests-id="isTrimString" [checked]="action.stringTransform.isTrimString" (change)="action.stringTransform.isTrimString = !action.stringTransform.isTrimString" + /> + <div class="state"> + <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>Trim String</label> + </div> + </div> + </div> + <!-- log Event --> <div *ngIf="action.actionType === 'log event'" class="center-content-item"> <div class="from"> <div class="from-container"> <div style="display: flex; align-items: center; width: 100%;" class="label"> <span class="label" style="padding: 0 5px; width: 100px;">Title</span> - <input required class="input-text" data-tests-id="InputLogTitle" ngModel name="title" [(ngModel)]="action.logEvent.title" - type="text" placeholder="The title for the log entry"> + <input required class="input-text" data-tests-id="InputLogTitle" name="title" [(ngModel)]="action.logEvent.title" type="text" + placeholder="The title for the log entry"> </div> </div> </div> @@ -35,8 +251,8 @@ <div class="from-container"> <div style="display: flex; align-items: center; width: 100%;" class="label"> <span class="label" style="padding: 0 5px; width: 100px;">Log Text</span> - <input required class="input-text" data-tests-id="InputLogText" ngModel name="logText" [(ngModel)]="action.logText.text" - type="text" placeholder="The title for the log entry"> + <input required class="input-text" data-tests-id="InputLogText" name="logText" [(ngModel)]="action.logText.text" type="text" + placeholder="Text to log"> </div> </div> </div> @@ -44,49 +260,53 @@ </div> - <!-- dateFormatter --> - <div *ngIf="action.actionType === 'date formatter'" style="flex-direction: column; margin-left: 156px; align-items: flex-end;"> - <div style="display: flex; margin: 0.5em 0; padding-left: 6px;"> - <div class="from" style="width:50%;"> - <div class="from-container"> - <div style="display: flex; align-items: center; width: 100%;" class="label"> - <span class="label" style="padding: 0 5px; width: 100px;">From Format</span> - <input data-tests-id="InputFromFormat" class="input-text" ngModel required name="fromFormat" [(ngModel)]="action.dateFormatter.fromFormat" type="text"> - </div> - </div> + <!-- dateFormatter --> + <div *ngIf="action.actionType === 'date formatter'" style="flex-direction: column; margin-left: 163px; align-items: flex-end;"> + <div style="display: flex; margin: 0.5em 0; padding-left: 6px;"> + <div class="from" style="width:50%;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;">From Format</span> + <input data-tests-id="InputFromFormat" class="input-text" required name="fromFormat" [(ngModel)]="action.dateFormatter.fromFormat" + type="text"> </div> - <div class="from" style="width:50%; padding: 0;"> - <div class="from-container"> - <div style="display: flex; align-items: center; width: 100%;" class="label"> - <span class="label" style="padding: 0 5px; width: 100px;">To Format</span> - <input data-tests-id="InputToFormat" class="input-text" ngModel required name="toFormat" [(ngModel)]="action.dateFormatter.toFormat" type="text"> - </div> - </div> + </div> + </div> + <div class="from" style="width:50%; padding: 0;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;">To Format</span> + <input data-tests-id="InputToFormat" class="input-text" required name="toFormat" [(ngModel)]="action.dateFormatter.toFormat" + type="text"> </div> </div> - - <div style="display: flex; margin: 0.5em 0; padding-left: 6px;"> - <div class="from" style="width:50%;"> - <div class="from-container"> - <div style="display: flex; align-items: center; width: 100%;" class="label"> - <span class="label" style="padding: 0 5px; width: 132px;">From Time-zone</span> - <input class="input-text" data-tests-id="InputFromTimezone" ngModel required name="fromTimezone" [(ngModel)]="action.dateFormatter.fromTimezone" type="text"> - </div> - </div> + </div> + </div> + + <div style="display: flex; margin: 0.5em 0; padding-left: 6px;"> + <div class="from" style="width:50%;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 132px;">From Time-zone</span> + <input class="input-text" data-tests-id="InputFromTimezone" required name="fromTimezone" [(ngModel)]="action.dateFormatter.fromTimezone" + type="text"> </div> - <div class="from" style="width:50%; padding: 0;"> - <div class="from-container"> - <div style="display: flex; align-items: center; width: 100%;" class="label"> - <span class="label" style="padding: 0 5px; width: 100px;">To Time-zone</span> - <input class="input-text" data-tests-id="InputToTimezone" ngModel required name="toTimezone" [(ngModel)]="action.dateFormatter.toTimezone" type="text"> - </div> - </div> + </div> + </div> + <div class="from" style="width:50%; padding: 0;"> + <div class="from-container"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 100px;">To Time-zone</span> + <input class="input-text" data-tests-id="InputToTimezone" required name="toTimezone" [(ngModel)]="action.dateFormatter.toTimezone" + type="text"> </div> </div> </div> + </div> + </div> <!-- replace text --> - <div *ngIf="action.actionType === 'replace text'" class="action-container" style="flex-direction: row; margin-left: 152px; padding: 0 0.8em;"> + <div *ngIf="action.actionType === 'replace text'" class="action-container" style="flex-direction: row; margin-left: 160px; padding: 0 0.8em;"> <div class="action-item"> <div class="from" style="width:100%;"> @@ -94,8 +314,8 @@ <div class="label" style="width: 100%;"> <span class="label" style="padding: 0 5px; width: 100px;">Find what</span> - <input data-tests-id="InputFindWhat" class="input-text" ngModel required name="findWhat" [(ngModel)]="action.replaceText.find" - type="text" placeholder="Find text"> + <input data-tests-id="InputFindWhat" class="input-text" required name="findWhat" [(ngModel)]="action.replaceText.find" type="text" + placeholder="Find text"> </div> </div> @@ -109,7 +329,7 @@ <div class="label" style="width: 100%;"> <span class="label" style="padding: 0 5px; width: 100px;">Replace with</span> - <input data-tests-id="InputReplaceWith" class="input-text" ngModel required name="replaceWith" [(ngModel)]="action.replaceText.replace" + <input data-tests-id="InputReplaceWith" class="input-text" required name="replaceWith" [(ngModel)]="action.replaceText.replace" type="text" placeholder="Replace with text"> </div> @@ -120,15 +340,14 @@ </div> <!-- log text --> - <div *ngIf="action.actionType === 'log text'" class="action-container" style="flex-direction: row; margin-left: 152px; padding: 0 0.8em;"> + <div *ngIf="action.actionType === 'log text'" class="action-container" style="flex-direction: row; margin-left: 160px; padding: 0 0.8em;"> <div class="action-item"> <div class="from" style="width: 100%;"> <div class="from-container" display="padding:0;"> <div class="label" style="width: 100%;"> <span class="label" style="padding: 0 5px; width: 100px;">Log Name</span> - <input class="input-text" data-tests-id="InputLogName" ngModel name="logName" [(ngModel)]="action.logText.name" - type="text" placeholder="Enter log name"> + <input class="input-text" data-tests-id="InputLogName" name="logName" [(ngModel)]="action.logText.name" type="text" placeholder="Enter log name"> </div> </div> </div> @@ -139,8 +358,8 @@ <div class="from-container"> <div class="label" style="width: 100%;"> <span class="label" style="padding: 0 5px; width: 100px;">Log Level</span> - <input class="input-text" data-tests-id="InputLogLevel" ngModel required name="logLevel" [(ngModel)]="action.logText.level" - type="text" placeholder="Text to log"> + <input class="input-text" data-tests-id="InputLogLevel" required name="logLevel" [(ngModel)]="action.logText.level" type="text" + placeholder="The title for the log entry"> </div> </div> </div> @@ -165,7 +384,7 @@ </div> </div> <div *ngIf="action.map.haveDefault" class="input-wrapper"> - <input type="text" ngModel required name="defaultInput" data-tests-id="defaultInput" [(ngModel)]="action.map.default" class="input"> + <input type="text" required name="defaultInput" data-tests-id="defaultInput" [(ngModel)]="action.map.default" class="input"> </div> </div> @@ -179,10 +398,10 @@ <tbody ngModelGroup="mapKeyValue" #mapKeyValue="ngModelGroup"> <tr *ngFor="let item of action.map.values; let index = index;" (mouseleave)="hoveredIndex=-1" (mouseover)="hoveredIndex=index"> <th style="height: 30px; border: 1px solid #F3F3F3;"> - <input [(ngModel)]="item.key" ngModel required name="mapValue[{{index}}]" data-tests-id="key" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> + <input [(ngModel)]="item.key" required name="mapKey[{{index}}]" data-tests-id="key" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> </th> <th style="height: 30px; border: 1px solid #F3F3F3;"> - <input [(ngModel)]="item.value" ngModel required name="mapValue[{{index}}]" data-tests-id="value" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> + <input [(ngModel)]="item.value" required name="mapValue[{{index}}]" data-tests-id="value" type="text" style="width:97%; height: 100%;border: none; padding:0 5px;"> </th> <th style="height: 30px; display: flex; align-items: baseline;"> <button mat-icon-button data-tests-id="btn-remove-row" [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" @@ -195,7 +414,7 @@ </table> - <div style="display:flex; justify-content: space-between;"> + <div style="display:flex; justify-content: flex-start;"> <div style="display: flex; align-items: center;"> <button mat-mini-fab color="primary" (click)="addMapRow()" data-tests-id="btn-add-row" style="height: 16px; width: 16px; display:flex; box-shadow: none;"> <span style="padding-left: 2px; display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:12"></span> @@ -203,6 +422,17 @@ </button> <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add Row</span> </div> + <div class="btn-wrapper"> + <div style="width: 36px; height: 36px; cursor: pointer;"> + <span style="width: 100%; + color:#5a5a5a; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'download' | feather:20"></span> + </div> + <input type="file" id="file" accept=".csv" (change)="handleFileInput($event.target.files)"> + </div> </div> </div> diff --git a/public/src/app/rule-engine/action/action.component.scss b/public/src/app/rule-engine/action/action.component.scss index fc36380..e25f0fd 100644 --- a/public/src/app/rule-engine/action/action.component.scss +++ b/public/src/app/rule-engine/action/action.component.scss @@ -11,6 +11,13 @@ .highlight { color: #009fdb; } + .input-text { + border: none; + flex: 1; + // width: 250px; + padding: 5px 0 5px 5px; + margin: 0; + } .center-content { display: flex; width: 100%; @@ -23,7 +30,7 @@ display: flex; align-items: center; justify-content: center; - min-width: 142px; + min-width: 150px; } .center-content-item { width: 100%; @@ -84,7 +91,8 @@ .from { display: flex; flex-direction: column; - padding: 0 10px; + // padding: 0 10px; + padding-right: 10px; .from-container { display: flex; flex-direction: column; @@ -131,3 +139,16 @@ padding: 0 5px; width: 110px; } + +.btn-wrapper { + position: relative; +} +.btn-wrapper input[type='file'] { + position: absolute; + left: 0; + top: 0; + opacity: 0; + width: 36px; + height: 36px; + cursor: pointer; +} diff --git a/public/src/app/rule-engine/action/action.component.ts b/public/src/app/rule-engine/action/action.component.ts index 1a62e1a..6658d52 100644 --- a/public/src/app/rule-engine/action/action.component.ts +++ b/public/src/app/rule-engine/action/action.component.ts @@ -1,34 +1,51 @@ -import { Component, Inject, Input, OnInit, ViewChild } from '@angular/core'; +import { + Component, + Inject, + Input, + OnInit, + ViewChild, + AfterViewInit +} from '@angular/core'; // import { Copy } from "../model"; import { Http, Response, Headers, RequestOptions } from '@angular/http'; -import { Observable } from 'rxjs/Rx'; +// import {Observable} from 'rxjs/Rx'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import { Subject } from 'rxjs/Subject'; import { NgForm } from '@angular/forms'; +import * as Papa from 'papaparse'; +import { metricData } from './metric.data'; +import { Store } from '../../store/store'; +import { ToastrService } from 'ngx-toastr'; @Component({ selector: 'app-action', templateUrl: './action.component.html', styleUrls: ['./action.component.scss'] }) -export class ActionComponent implements OnInit { +export class ActionComponent implements OnInit, AfterViewInit { @Input() action; @ViewChild('from') fromInstance; @ViewChild('target') targetInstance; @ViewChild('actionFrm') actionFrm: NgForm; highlight = 'black'; hoveredIndex; + fileToUpload: File = null; + fileName = ''; + metrics = metricData; changeStyle($event) { this.highlight = $event.type === 'mouseover' ? 'highlight' : 'black'; } - ngOnInit(): void { + ngOnInit(): void {} + constructor(public store: Store, private toastr: ToastrService) {} + + ngAfterViewInit(): void { console.log(this.action.id); - if (this.action.from !== '') { + if (this.action.from !== undefined && this.action.from !== '') { console.log('Action %o', this.action); this.fromInstance.updateMode(this.action.from); } - if (this.action.target !== '') { + if (this.action.target !== undefined && this.action.target !== '') { this.targetInstance.updateMode(this.action); } } @@ -47,8 +64,63 @@ export class ActionComponent implements OnInit { this.action.map.values.splice(index, 1); } + removeSearchField(index) { + this.action.search.enrich.fields.splice(index, 1); + } + + addSearchFeild() { + this.action.search.enrich.fields.push(''); + } + + searchRadioChange(radioType) { + console.log(radioType); + this.action.search.radio = radioType; + console.log(this.action.search); + } + + metricChange(metric) { + console.log('metric change:', metric); + } + changeCheckbox() { console.log(this.action.id); return (this.action.map.haveDefault = !this.action.map.haveDefault); } + addSearchUpdateRow() { + this.action.search.updates.push({ key: '', value: '' }); + } + removeSearchUpdatesRow(index) { + this.action.search.updates.splice(index, 1); + } + + handleFileInput(files: FileList) { + this.fileToUpload = files.item(0); + console.log('file to load:', this.fileToUpload); + this.fileName = this.fileToUpload !== null ? this.fileToUpload.name : ''; + + this.store.loader = true; + Papa.parse(this.fileToUpload, { + complete: result => { + if (result.data) { + const mapConvert = result.data + .slice(0, 300) + .filter(item => item[0] !== undefined && item[1] !== undefined) + .map(item => { + console.log(`item 0: ${item[0]} item 1: ${item[1]}`); + return { + key: item[0].trim(), + value: item[1].trim() + }; + }); + this.store.loader = false; + this.action.map.values = mapConvert; + } + }, + error: (err, file) => { + this.store.loader = false; + console.log(`error: ${err}, in file ${file}`); + this.toastr.error('', err); + } + }); + } } diff --git a/public/src/app/rule-engine/action/metric.data.ts b/public/src/app/rule-engine/action/metric.data.ts new file mode 100644 index 0000000..9f2e3e7 --- /dev/null +++ b/public/src/app/rule-engine/action/metric.data.ts @@ -0,0 +1,43 @@ +export const metricData = [ + 'snmp_vHTTPPROXY', + 'JerichoStatusPoller', + 'Jericho_SYSLOG', + 'snmp_vJSALOGS', + 'StatusPoller', + 'SYSLOG', + 'snmp_vECA', + 'snmp_vEPDG_MME', + 'snmp_vEPDG', + 'snmp_vEricsson_HB', + 'snmp_vEricsson_ALR', + 'snmp_vEricsson_SBG', + 'snmp_vEricsson_MME', + 'snmp_vFAMP_MME', + 'snmp_vHSS', + 'snmp_vLSTM', + 'snmp_vMDNS', + 'snmp_Jericho', + 'snmp_vMMSC_CMAUI', + 'snmp_vNEMS', + 'snmp_vNokiaCTS', + 'snmp_vOTA', + 'snmp_vPCRF_MOG', + 'snmp_vPMS', + 'snmp_vSAE_GW', + 'snmp_vSAMnagios', + 'snmp_vSCP_Amdocs', + 'snmp_vSCP_ulticom', + 'snmp_vSRX', + 'snmp_vSeGW', + 'snmp_vVig', + 'SYSLOG_VCO', + 'VES_${event.commonEventHeader.domain}', + 'snmp_vF5', + 'CDAP_Enriched_Event', + 'CDAP_Enriched_Syslog_Event', + 'OaaSContrail', + 'GuestOs', + 'AIC_Infra_Nagios', + 'PMOSS_DCAE-KPI', + 'Sec_Syslog_Event' +]; diff --git a/public/src/app/rule-engine/action/papa.spec.ts b/public/src/app/rule-engine/action/papa.spec.ts new file mode 100644 index 0000000..864d581 --- /dev/null +++ b/public/src/app/rule-engine/action/papa.spec.ts @@ -0,0 +1,84 @@ +import * as Papa from 'papaparse'; + +describe('parse CSV to JSON', () => { + it('should have only 2 attribute key and value', () => { + const stringAsCSV = 'liav,GL'; + Papa.parse(stringAsCSV, { + complete: result => { + if (result.data) { + const parser = result.data + .slice(0, 300) + .filter(item => item[0] !== undefined && item[1] !== undefined) + .map(item => { + return { + key: item[0].trim(), + value: item[1].trim() + }; + }); + expect(parser).toEqual([ + { + key: 'liav', + value: 'GL' + } + ]); + } + } + }); + }); + + it('should have 2 attribute ignore 1', () => { + const stringAsCSV = 'liav,GL,DCAE'; + Papa.parse(stringAsCSV, { + complete: result => { + if (result.data) { + const parser = result.data + .slice(0, 300) + .filter(item => item[0] !== undefined && item[1] !== undefined) + .map(item => { + return { + key: item[0].trim(), + value: item[1].trim() + }; + }); + expect(parser).toEqual([ + { + key: 'liav', + value: 'GL' + } + ]); + } + } + }); + }); + + it('should have 4 attribute', () => { + const stringAsCSV = `liav,GL + Vosk,Dev`; + + Papa.parse(stringAsCSV, { + complete: result => { + if (result.data) { + const parser = result.data + .slice(0, 300) + .filter(item => item[0] !== undefined && item[1] !== undefined) + .map(item => { + return { + key: item[0].trim(), + value: item[1].trim() + }; + }); + expect(parser).toEqual([ + { + key: 'liav', + value: 'GL' + }, + { + key: 'Vosk', + value: 'Dev' + } + ]); + } + } + }); + }); +}); diff --git a/public/src/app/rule-engine/api/rule-engine-api.service.ts b/public/src/app/rule-engine/api/rule-engine-api.service.ts index 7bf5e18..f19fd15 100644 --- a/public/src/app/rule-engine/api/rule-engine-api.service.ts +++ b/public/src/app/rule-engine/api/rule-engine-api.service.ts @@ -9,7 +9,7 @@ import { import 'rxjs/add/operator/catch'; // Import RxJs required methods import 'rxjs/add/operator/map'; -import { Observable, Subject } from 'rxjs/Rx'; +import { Observable, Subject, BehaviorSubject } from 'rxjs/Rx'; import { v4 as uuid } from 'uuid'; import { environment } from '../../../environments/environment'; @@ -25,7 +25,7 @@ export class RuleEngineApiService { flowType: string; editorData: Subject<any> = new Subject(); updateVersionLock: Subject<any> = new Subject(); - tabIndex: Subject<any> = new Subject(); + tabIndex: Subject<any> = new BehaviorSubject(-1); constructor(private http: Http) { this.baseUrl = `${environment.apiBaseUrl}/rule-editor`; @@ -70,6 +70,20 @@ export class RuleEngineApiService { ); } + getLatestMcUuid(params) { + const { contextType, serviceUuid, vfiName, vfcmtUuid } = params; + const url = `${ + environment.apiBaseUrl + }/${contextType}/${serviceUuid}/${vfiName}/${vfcmtUuid}/getLatestMcUuid`; + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .get(url, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + getListOfRules(): Observable<any> { const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${ this.nid @@ -83,8 +97,99 @@ export class RuleEngineApiService { }); } - modifyRule(newRole) { - const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${ + getInitialPhases(flowType): Observable<any> { + const url = `${environment.apiBaseUrl}/conf/getPhases/${flowType}`; + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .get(url, this.options) + .map(response => response.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + + exportRules() { + const url = `${this.baseUrl}/export/${this.vfcmtUuid}/${ + this.dcaeCompName + }/${this.nid}/${this.configParam}`; + console.log(url); + const link = document.createElement('a'); + link.download = 'a'; + link.href = url; + document.body.appendChild(link); + link.click(); + } + + importRules(rule, vfcmtUuid, isGroup) { + const url = `${this.baseUrl}/import/${vfcmtUuid}/${this.dcaeCompName}/${ + this.nid + }/${this.configParam}/${isGroup}`; + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .post(url, rule, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json() || 'Server error'); + }); + } + + importPhase(ruleList) { + const url = `${this.baseUrl}/importPhase`; + + Object.assign(ruleList, { + vfcmtUuid: this.vfcmtUuid, + dcaeCompLabel: this.dcaeCompName, + nid: this.nid, + configParam: this.configParam + }); + + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .post(url, ruleList, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + + applyFilter(newFilter) { + const url = `${this.baseUrl}/applyFilter`; + + Object.assign(newFilter, { + vfcmtUuid: this.vfcmtUuid, + dcaeCompLabel: this.dcaeCompName, + nid: this.nid, + configParam: this.configParam + }); + + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .post(url, newFilter, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + + deleteFilter() { + const deleteFilter = { + vfcmtUuid: this.vfcmtUuid, + dcaeCompLabel: this.dcaeCompName, + nid: this.nid, + configParam: this.configParam + }; + const url = `${this.baseUrl}/deleteFilter`; + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .post(url, deleteFilter, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + + modifyRule(newRole, vfcmtUuid) { + const url = `${this.baseUrl}/rule/${vfcmtUuid}/${this.dcaeCompName}/${ this.nid }/${this.configParam}`; this.options.headers.set('X-ECOMP-RequestID', uuid()); @@ -96,8 +201,8 @@ export class RuleEngineApiService { }); } - deleteRule(uid) { - const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${ + deleteRule(uid, vfcmtUuid) { + const url = `${this.baseUrl}/rule/${vfcmtUuid}/${this.dcaeCompName}/${ this.nid }/${this.configParam}/${uid}`; this.options.headers.set('X-ECOMP-RequestID', uuid()); @@ -109,15 +214,28 @@ export class RuleEngineApiService { }); } - translate(nofityId) { + deleteGroup(groupId, vfcmtUuid) { + const url = `${this.baseUrl}/group/${vfcmtUuid}/${this.dcaeCompName}/${ + this.nid + }/${this.configParam}/${groupId}`; + this.options.headers.set('X-ECOMP-RequestID', uuid()); + return this.http + .delete(url, this.options) + .map((res: Response) => res.json()) + .catch((error: any) => { + return Observable.throw(error.json().requestError || 'Server error'); + }); + } + + translate(entryPhase, publishPhase, vfcmtUuid) { const url = `${this.baseUrl}/rule/translate`; const params = { - vfcmtUuid: this.vfcmtUuid, + vfcmtUuid: vfcmtUuid, dcaeCompLabel: this.dcaeCompName, nid: this.nid, configParam: this.configParam, - flowType: this.flowType, - notifyId: nofityId + entryPhase: entryPhase, + publishPhase: publishPhase }; this.options.headers.set('X-ECOMP-RequestID', uuid()); // const params = new URLSearchParams(); params.append('flowType', diff --git a/public/src/app/rule-engine/condition/condition.component.html b/public/src/app/rule-engine/condition/condition.component.html index 0ff244b..7ba21e2 100644 --- a/public/src/app/rule-engine/condition/condition.component.html +++ b/public/src/app/rule-engine/condition/condition.component.html @@ -5,17 +5,58 @@ <div *ngIf="node.data.name === 'operator'" style="background: #F2F2F2;"> <div style="display: flex; margin-left: 5px; align-items: center; min-height: 35px;"> <div style="display: flex; align-items: center;" *ngIf="showType"> - <select style="padding: 5px;" [(ngModel)]="node.data.type"> + <select style="padding: 5px;margin-left: 10px;" [(ngModel)]="node.data.type"> <option value="ANY">ANY</option> <option value="ALL">ALL</option> </select> - <div style="display: flex; align-items: center; margin-left: 10px;"> + <div style="display: flex; align-items: center; margin-left: 10px; width: 150px;"> of the following are true: </div> </div> - <div style="display: flex; margin-left: auto;"> + <div *ngIf="isFilter" style="display: flex; width: 100%; justify-content: space-between; cursor: default;"> + <div style="display: flex;"> + <div style="display: flex; align-items: center; padding: 0 15px;"> + <button mat-mini-fab color="primary" data-tests-id="addCondition" (click)="addConditional(tree, node)" style="height: 16px; width: 16px; display:flex; box-shadow: none;align-items: center;justify-content: center;"> + <span style="width: 100%; + color:white; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'plus' | feather:12"></span> + </button> + <span class="btn-label">Add Filter + </span> + </div> + + <div style="display: flex; align-items: center; padding: 0 25px;"> + <button mat-mini-fab color="primary" data-tests-id="addConditionGroup" [disabled]="node.data.level === 2" (click)="addConditionalGroup(tree, node)" + style="height: 16px; width: 16px; display:flex; box-shadow: none;align-items: center;justify-content: center;"> + <span style="width: 100%; + color:white; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'plus' | feather:12"></span> + </button> + <span [style.color]="node.data.level === 2 ? '#a7a7a7' : '#009fdb' " class="btn-label">Add Filter Group + </span> + </div> + </div> + <!-- background: #FFFFFF; --> + <div style="display: flex; align-items: center; padding: 0 5px; "> + <button data-tests-id="removeConditionNode" mat-icon-button (click)="removeConditional(tree, node)" class="button-remove"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'trash-2' | feather:20"></span> + </button> + </div> + </div> + + <div *ngIf="!isFilter" style="display: flex; margin-left: auto;"> <div style="display: flex; align-items: center; padding: 0 25px;"> <button mat-mini-fab color="primary" data-tests-id="addCondition" (click)="addConditional(tree, node)" style="height: 24px; width: 24px; display:flex; box-shadow: none;"> @@ -35,9 +76,13 @@ </span> </div> - <div style="display: flex; align-items: center; padding: 0 5px; background: #FFFFFF;"> + <div style="display: flex; align-items: center; padding: 0 5px;"> <button data-tests-id="removeConditionNode" mat-icon-button (click)="removeConditional(tree, node)" class="button-remove"> - <mat-icon class="md-24">delete</mat-icon> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'trash-2' | feather:20"></span> </button> </div> @@ -64,21 +109,41 @@ <option value="startsWith">Starts with</option> <option value="equals">Equals</option> <option value="notEqual">Not equal</option> + <option value="assigned">Assigned</option> + <option value="unassigned">Unassigned</option> + <option value="oneOf">One of</option> + <option value="NotOneOf">Not one of</option> </select> </div> - <div class="label" style="width:100%"> + <div class="label" style="width:100%" *ngIf="node.data.operator !== 'assigned' && node.data.operator !== 'unassigned'"> <span class="label" style="padding: 0 10px; border-left: none;"> Value </span> <input class="input-text" data-tests-id="right" (ngModelChange)="modelChange($event)" [(ngModel)]="node.data.right" ngDefaultControl type="text"> </div> + + <div *ngIf="node.data.operator === 'assigned' || node.data.operator === 'unassigned'" class="pretty p-svg" style="margin: 4px 0 1em 0em; margin-left:10px;"> + <input type="checkbox" name="emptyIsAssigned" data-tests-id="emptyIsAssigned" [checked]="node.data.emptyIsAssigned" (change)="node.data.emptyIsAssigned = !node.data.emptyIsAssigned" + /> + <div class="state"> + <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>Empty Is Assigned</label> + </div> + </div> </div> <!-- remove button --> <div class="show-delete"> <button mat-icon-button data-tests-id="RemoveCondition" (click)="removeConditional(tree, node)" class="button-remove"> - <mat-icon class="md-24">delete</mat-icon> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'trash-2' | feather:20"></span> </button> </div> diff --git a/public/src/app/rule-engine/condition/condition.component.scss b/public/src/app/rule-engine/condition/condition.component.scss index 8c0e9e0..228d8c6 100644 --- a/public/src/app/rule-engine/condition/condition.component.scss +++ b/public/src/app/rule-engine/condition/condition.component.scss @@ -9,7 +9,7 @@ padding: 0; } .angular-tree-component { - padding-left: 1em; + // padding-left: 1em; overflow-y: hidden; } .tree-node-leaf.container { @@ -26,11 +26,45 @@ } .node-wrapper { background: white; + padding-top: 10px; } .tree-children { border-left: 2px solid #f2f2f2; + border-right: 2px solid #f2f2f2; + border-bottom: 2px solid #f2f2f2; // border-top: 1px solid #f2f2f2; border-bottom: 1px solid #f2f2f2; + // border-bottom: 1px solid #f2f2f2; + } + .tree-node-level-2 + > tree-node-wrapper + > .node-wrapper + > .node-content-wrapper { + width: 90%; + } + .tree-node-level-2 .tree-children { + width: 90%; + } + .tree-node-level-3 + > tree-node-wrapper + > .node-wrapper + > .node-content-wrapper { + width: 85%; + } + .tree-node-level-3 .tree-children { + width: 85%; + } + .tree-node-level-4 + > tree-node-wrapper + > .node-wrapper + > .node-content-wrapper { + width: 80%; + } + .tree-node-level-4 .tree-children { + width: 80%; + } + div .node-content-wrapper { + padding: 0; } tree-node-expander { display: none; diff --git a/public/src/app/rule-engine/condition/condition.component.ts b/public/src/app/rule-engine/condition/condition.component.ts index f44fbf4..200b42d 100644 --- a/public/src/app/rule-engine/condition/condition.component.ts +++ b/public/src/app/rule-engine/condition/condition.component.ts @@ -3,10 +3,13 @@ import { ViewEncapsulation, ViewChild, Output, - EventEmitter + EventEmitter, + Input, + OnInit } from '@angular/core'; import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component'; -import { some } from 'lodash'; +import { some, cloneDeep } from 'lodash'; +import { toJS } from 'mobx'; @Component({ selector: 'app-condition', @@ -14,7 +17,9 @@ import { some } from 'lodash'; styleUrls: ['./condition.component.scss'], encapsulation: ViewEncapsulation.None }) -export class ConditionComponent { +export class ConditionComponent implements OnInit { + @Input() condition; + @Input() isFilter = false; conditionTree = []; showType = false; @ViewChild(TreeComponent) private tree: TreeComponent; @@ -27,7 +32,9 @@ export class ConditionComponent { animateAcceleration: 1.2 }; - constructor() { + ngOnInit(): void { + console.log('condition', this.condition); + this.conditionTree.push({ name: 'operator', level: 0, @@ -39,10 +46,23 @@ export class ConditionComponent { left: '', right: '', operator: null, - level: 1 + level: 1, + emptyIsAssigned: false }); + + if (this.condition) { + const condition = toJS(this.condition); + if (condition.name === 'condition') { + this.updateMode(true, condition); + } else { + const convertedCondition = this.convertConditionFromServer(condition); + this.updateMode(false, convertedCondition); + } + } } + constructor() {} + onInitialized(tree) { tree.treeModel.expandAll(); } @@ -58,7 +78,8 @@ export class ConditionComponent { left: data.left, right: data.right, operator: data.operator, - level: 1 + level: 1, + emptyIsAssigned: false }); this.showType = false; } else { @@ -82,7 +103,8 @@ export class ConditionComponent { left: '', right: '', operator: null, - level: tempLevel + level: tempLevel, + emptyIsAssigned: false }; selectedNode.data.children.push(conditionTemplate); tree.treeModel.update(); @@ -108,7 +130,8 @@ export class ConditionComponent { left: '', right: '', operator: null, - level: selectedNode.data.level + 2 + level: selectedNode.data.level + 2, + emptyIsAssigned: false }); } tree.treeModel.update(); @@ -149,6 +172,35 @@ export class ConditionComponent { } } + public 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; + } + + 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; + } + private deleteNodeAndUpdateTreeView(selectedNode: any, tree: any) { selectedNode.parent.data.children.splice(selectedNode.index, 1); tree.treeModel.update(); diff --git a/public/src/app/rule-engine/from/from.component.html b/public/src/app/rule-engine/from/from.component.html index df2c110..011f609 100644 --- a/public/src/app/rule-engine/from/from.component.html +++ b/public/src/app/rule-engine/from/from.component.html @@ -38,6 +38,35 @@ </div> </div> + <!-- clear NSF template --> + <div class="from" *ngIf="actionType === 'clear nsf'" ngModelGroup="clear-nsf" #clearFrom="ngModelGroup" style="padding-right: 0;"> + <div *ngFor="let input of from.values; let index = index;" data-tests-id="clearInputArrayFrom" (mouseleave)="hoveredIndex=-1" + (mouseover)="hoveredIndex=index" class="from-conatiner" style="margin-bottom:1rem; display: flex; flex-direction: column; align-items: flex-start;" + data-tests-id="fromComponent"> + <div style="display: flex; align-items: center; width: 100%;"> + <div style="display: flex; align-items: center; width: 100%;" class="label"> + <span class="label" style="padding: 0 5px; width: 50px;">From</span> + <input class="input-text" (ngModelChange)="modelChange(from)" [(ngModel)]="input.value" type="text" data-tests-id="valueInput" + ngModel required name="clear-nfs[{{index}}]"> + </div> + + <button mat-icon-button class="button-remove" [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" (click)="removeFromInput(index)" + *ngIf="from.values.length > 1" style="box-shadow: none; height: 24px; width: 24px; display:flex" data-tests-id="btnDelete"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> + + </div> + <div style="display:flex; justify-content: space-between;"> + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" (click)="addFromInput()" style="box-shadow: none; height: 16px; width: 16px; display:flex" + data-tests-id="btnAddInput"> + <span style="padding-left: 2px; display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:12"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add input</span> + </div> + </div> + </div> <!-- clear template --> <div class="from" *ngIf="actionType === 'clear'" ngModelGroup="clear" #clearFrom="ngModelGroup"> <div *ngFor="let input of from.values; let index = index;" data-tests-id="clearInputArrayFrom" (mouseleave)="hoveredIndex=-1" @@ -62,8 +91,6 @@ <button mat-mini-fab color="primary" (click)="addFromInput()" style="box-shadow: none; height: 16px; width: 16px; display:flex" data-tests-id="btnAddInput"> <span style="padding-left: 2px; display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:12"></span> -<!-- - <mat-icon>add</mat-icon> --> </button> <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add input</span> </div> diff --git a/public/src/app/rule-engine/from/from.component.scss b/public/src/app/rule-engine/from/from.component.scss index 852984d..f5ec4cc 100644 --- a/public/src/app/rule-engine/from/from.component.scss +++ b/public/src/app/rule-engine/from/from.component.scss @@ -1,7 +1,8 @@ .from { display: flex; flex-direction: column; - padding: 0 10px; + // padding: 0 10px; + padding-right: 10px; .label { border: 1px solid #d2d2d2; @@ -55,6 +56,7 @@ } /deep/ .mat-button-wrapper { padding: 0; + display: flex; } .mat-icon { width: 18px; diff --git a/public/src/app/rule-engine/from/from.component.ts b/public/src/app/rule-engine/from/from.component.ts index bc1dedb..c526103 100644 --- a/public/src/app/rule-engine/from/from.component.ts +++ b/public/src/app/rule-engine/from/from.component.ts @@ -3,7 +3,9 @@ import { Input, Output, EventEmitter, - ViewChild + ViewChild, + AfterViewInit, + ChangeDetectorRef } from '@angular/core'; // import { From } from "../model"; import { Subject } from 'rxjs/Subject'; @@ -23,40 +25,27 @@ import { NgForm } from '@angular/forms'; styleUrls: ['./from.component.scss'], animations: [ trigger('state', [ - state( - 'open', - style({ - opacity: 1, - height: 'auto' - }) - ), + state('open', style({ opacity: 1, height: 'auto' })), transition('* => open', [ - animate( - 200, - keyframes([ - style({ - opacity: 1, - height: 'auto' - }) - ]) - ) + animate(200, keyframes([style({ opacity: 1, height: 'auto' })])) ]), - state( - 'closed', - style({ - opacity: 0, - height: 0 - }) - ) + state('closed', style({ opacity: 0, height: 0 })) ]) ] }) -export class FromComponent { +export class FromComponent implements AfterViewInit { from: any = { value: '', regex: '', state: 'closed', - values: [{ value: '' }, { value: '' }] + values: [ + { + value: '' + }, + { + value: '' + } + ] }; @Input() actionType; @Output() onFromChange = new EventEmitter(); @@ -64,10 +53,20 @@ export class FromComponent { hoveredIndex; // public keyUp = new BehaviorSubject<string>(null); - ngOnInit(): void { - if (this.actionType === 'clear') { - this.from.values = [{ value: '' }]; + constructor(private changeDetector: ChangeDetectorRef) {} + + ngAfterViewInit(): void { + if ( + (this.actionType === 'clear' || this.actionType === 'clear nsf') && + this.from.values[0].value === '' + ) { + this.from.values = [ + { + value: '' + } + ]; } + this.changeDetector.detectChanges(); } showRegex(item) { @@ -79,12 +78,22 @@ export class FromComponent { updateMode(fromData) { console.log(fromData); if (fromData) { - this.from = fromData; + if ( + (this.actionType === 'clear' || this.actionType === 'clear nsf') && + fromData.values[0].value === '' + ) { + this.from.values = [ + { + value: '' + } + ]; + } else { + this.from = fromData; + } } + this.changeDetector.detectChanges(); } - constructor() {} - modelChange(event) { this.onFromChange.emit(event); } diff --git a/public/src/app/rule-engine/rule-list/rule-list.component.html b/public/src/app/rule-engine/rule-list/rule-list.component.html index 4ce6efb..9a9997e 100644 --- a/public/src/app/rule-engine/rule-list/rule-list.component.html +++ b/public/src/app/rule-engine/rule-list/rule-list.component.html @@ -2,8 +2,8 @@ <div class="header"> <span style="font-size: 18px; margin-left:20px;">Rule Engine</span> <div style="display:flex"> - <button mat-raised-button (click)="translateRules()" color="primary" [disabled]="store.ruleList.length === 0" style="margin-right: 10px; width: 113px;height: 36px;" - data-tests-id="btnTranslate"> + <button mat-raised-button (click)="translateRules()" color="primary" [disabled]="store.ruleList.length === 0 || entryPhase === '' || publishPhase === '' " + style="margin-right: 10px; width: 113px;height: 36px;" data-tests-id="btnTranslate"> Translate </button> <app-bar-icons [tabName]="this.store.tabParmasForRule[0].name"></app-bar-icons> @@ -13,87 +13,238 @@ <div style="margin: 0rem 1rem; flex-grow: 1; overflow-y: auto;"> <!-- error container --> - <div *ngIf="error" style="color: white; background: red; padding: 1rem; border-radius: 5px; font-weight: bold;"> + <div *ngIf="error" style="color: white; background: red; padding: 1rem; border-radius: 5px; font-weight: bold; margin-bottom: 15px;"> {{ error }} </div> - <app-version-type-select #versionEventType [versions]="versions" [metaData]="metaData" (nodesUpdated)="handleUpdateNode($event)" - (refrashRuleList)="handlePropertyChange()"></app-version-type-select> + <div style="display: flex"> + <app-version-type-select #versionEventType [versions]="versions" [metaData]="metaData" (nodesUpdated)="handleUpdateNode($event)" + (refrashRuleList)="handlePropertyChange()"></app-version-type-select> - <!-- <div class="container-phase-notify"> - <div> - <span class="field-label required" style="margin-right: 10px;"> - phase - </span> - <select name="phase" [(ngModel)]="phase" data-tests-id="phase" style="height: 27px; padding: 0.3rem; margin-right: 18px;" - class="field-select"> - <option [ngValue]="null" disabled>Select phase</option> - </select> + <div style="display:flex;"> + <div class="field" style="display: flex; align-items: baseline;flex-direction: column; margin:0; margin-right: 10px;"> + <div class="field-label required" style="padding-right: 10px;"> + Entry Phase + </div> + <input type="text" name="entryPhase" required [(ngModel)]="entryPhase" class="field-text" style="width:250px;" + data-tests-id="entryPhase" /> + </div> + + <div class="field" style="display: flex; align-items: baseline;flex-direction: column; margin:0; margin-right: 10px;"> + <div class="field-label required" style="padding-right: 10px;"> + Publish Phase + </div> + <input type="text" name="publishPhase" [(ngModel)]="publishPhase" class="field-text" style="width:250px;" + data-tests-id="publishPhase" /> + </div> </div> + </div> - <div class="default" style="display: flex; align-items: center"> - <div class="pretty p-svg"> - <input type="checkbox" name="notifyCheckbox" data-tests-id="notifyCheckbox" /> + <div style="margin-bottom: 24px;"> + <div style="display: flex; justify-content: space-between; align-items: baseline;"> + <div class="pretty p-svg" style="margin-top: 24px; margin-bottom: 10px;"> + <input type="checkbox" name="isCondition" data-tests-id="isFilter" [checked]="ifStatement" (change)="filterCheckbox()" /> <div class="state"> <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>notify OID</label> + <label>Filtering</label> </div> </div> - <div class="input-wrapper"> - <input type="text" ngModel required name="notify-oid" data-tests-id="notify-oid" class="input"> + <div *ngIf="ifStatement"> + <button mat-raised-button (click)="applyFilter()" color="primary" style="width: 113px;height: 36px;" + data-tests-id="applyFilter"> + Apply Filter + </button> </div> </div> - </div> --> - - <div *ngIf="targetSource && store.ruleList.length === 0" style="margin: 30px 0; display: flex; align-items: center; justify-content: center; flex-direction: column;"> + <div *ngIf="ifStatement"> + <app-condition #conditions [isFilter]="true" [condition]="condition" (removeConditionCheck)="removeConditionCheck($event)" + (onConditionChange)="updateCondition($event)"></app-condition> + </div> + </div> - <div style="margin: 3em 0 2em 0;"> - <div style="font-size: 1.5em;"> - Rules were not yet created - </div> - <div style="padding: 0.5em; padding-top: 1em;"> - Please create a new normalization rule + <div *ngIf="targetSource && (tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp'))"> + <div style="display: flex; align-items: baseline; width: 170px; position:relative;" (mouseenter)="showBtnList = true" + (mouseleave)="showBtnList = false" data-tests-id="addGroup"> + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" id="addGroup" style="height: 16px; width: 16px; display:flex; justify-content: center; background-color: white; border: 1.5px solid #009fdb; color: #009fdb; cursor: default;"> + <span style="width: 100%; + color:#009fdb; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'plus' | feather:12"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Phase</span> </div> + + <ul *ngIf="showBtnList" class="button-list"> + <li> + <button class="button-options" [disabled]="disabledMapBtn('map')" (click)="addGroup('map')" data-tests-id="btnMap"> + map + </button> + </li> + <li> + <button class="button-options" [disabled]="disabledMapBtn('enrich')" (click)="addGroup('enrich')" + data-tests-id="btnEnrich"> + enrich + </button> + </li> + </ul> </div> - <button mat-fab (click)="openAction()" style="background-color:#009FDB" data-tests-id="btnAddFirstRule"> - <span [innerHTML]="'plus' | feather:24"></span> - </button> - <span style="margin-top: 1rem; font-size: 14px; color: #009FDB;"> - Add First Rule - </span> - </div> + <div *ngIf="store.groupList.length > 0"> + <div *ngFor="let item of store.groupList; let index = index" style="border: solid 1px #dedede; margin: 15px 0;"> + <div class="field" style="display: flex; align-items: flex-end; margin:0px;margin-left:1em;"> + <div style="margin-right:10px;"> + <img class="icon-img" *ngIf="item.groupId.substring(0, 1).toLowerCase() !== 'm'" src="{{imgBase}}/group_enrich.svg" + alt="group_enrich"> + <img class="icon-img" *ngIf="item.groupId.substring(0, 1).toLowerCase() === 'm'" src="{{imgBase}}/group_map.svg" + alt="group_map"> + </div> + <div class="field-label" style="padding-right: 10px; color: #959595;"> + PHASE NAME + </div> + <input type="text" name="phase" [(ngModel)]="item.phase" class="field-text" data-tests-id="phase" /> - <div *ngIf="store.ruleList.length > 0"> - <div style="display: flex; align-items: center;"> - <button mat-mini-fab color="primary" id="addMoreRule" data-tests-id="addMoreRule" style="height: 24px; width: 24px; display:flex" - (click)="openAction()"> - <mat-icon class="material-icons md-18">add</mat-icon> - </button> - <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Rule</span> + <!-- <div class="btn-wrapper" *ngIf="item.groupId.substring(0, 1).toLowerCase() === 'm'"> + <button mat-icon-button class="gray button-options" data-tests-id="importCDAP"> + <span style="width: 100%; + color:#5a5a5a; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" [innerHTML]="'upload' | feather:20"></span> + </button> + <input type="file" id="file" accept=".json" (change)="handleImportCDAP($event.target.files, item.groupId, item.phase)"> + </div> --> + + <button mat-icon-button (click)="deleteGroup(item.groupId)" [disabled]="disableDeleteGroup(item.groupId)" + class="gray" data-tests-id="deleteGroup"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'trash-2' | feather:20"></span> + </button> + </div> + + <div style="margin: 1rem;"> + <div *ngFor="let rule of item.list; let index = index" data-tests-id="ruleElement" (mouseleave)="hoveredIndex=-1" + (mouseover)="hoveredIndex=index" class="item listOfRule"> + <span style="width:100%; display: flex; align-items: center;"> + {{rule.description}} - [{{rule.uid}}] + </span> + <div style="display: flex; justify-content: flex-end;" class="ruleList-btn"> + <button (click)="openAction(rule, {groupId: item.groupId, phase: item.phase})" data-tests-id="editRule" + class="btn-list" mat-icon-button> + <mat-icon class="md-24">mode_edit</mat-icon> + </button> + <button mat-icon-button class='btn-list' (click)="copyRule(rule, index, {groupId: item.groupId, phase: item.phase})" + data-tests-id="copyRule"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'copy' | feather:20"></span> + </button> + <button (click)="removeItem(rule.uid, item.groupId)" data-tests-id="deleteRule" class="btn-list" + mat-icon-button [disabled]="disableDeleteGroup(item.groupId) && item.list.length === 1"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> + </div> + </div> + + <div style="display: flex; align-items: center; margin:1em;"> + <button mat-mini-fab color="primary" id="addMoreRule" data-tests-id="addMoreRule" style="height: 16px; width: 16px; display:flex; justify-content: center;" + (click)="openAction(null,item)"> + <span style="width: 100%; + color:white; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'plus' | feather:12"></span> + </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Rule</span> + </div> + + </div> </div> </div> - <div style="margin: 30px 0 10px 0;"> + <div *ngIf="targetSource && !(tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp'))"> + + <div *ngIf="targetSource && store.ruleList.length === 0 && !(tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp'))" + style="margin: 30px 0; display: flex; align-items: center; justify-content: center; flex-direction: column;"> - <div *ngFor="let item of store.ruleList; let index = index" data-tests-id="ruleElement" (mouseleave)="hoveredIndex=-1" (mouseover)="hoveredIndex=index" - class="item" style="display: flex;" [ngStyle]="hoveredIndex === index ? {'background-color': '#E6F6FB', 'color': '#009FDB'} : {'background-color': '#FFFFFF', 'color':'gray'}"> - <span style="width:100%; display: flex; align-items: center;"> - {{item.description}} - [{{item.uid}}] + <div style="margin: 3em 0 2em 0;"> + <div style="font-size: 1.5em;"> + Rules were not yet created + </div> + <div style="padding: 0.5em; padding-top: 1em;"> + Please create a new normalization rule + </div> + </div> + + <button mat-fab (click)="openAction()" style="background-color:#009FDB" data-tests-id="btnAddFirstRule"> + <span [innerHTML]="'plus' | feather:24"></span> + </button> + <span style="margin-top: 1rem; font-size: 14px; color: #009FDB;"> + Add First Rule </span> - <div style="display: flex; justify-content: flex-end;" *ngIf="index==hoveredIndex"> - <button (click)="openAction(item)" data-tests-id="editRule" class="btn-list" mat-icon-button> - <mat-icon class="md-24">mode_edit</mat-icon> - </button> - <button (click)="removeItem(item.uid)" data-tests-id="deleteRule" class="btn-list" mat-icon-button> - <mat-icon class="md-24">delete</mat-icon> + </div> + + <div *ngIf="store.ruleList.length > 0 && !(tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp'))"> + <div style="display: flex; align-items: center;"> + <button mat-mini-fab color="primary" id="addMoreRule" data-tests-id="addMoreRule" style="height: 16px; width: 16px; display:flex; justify-content: center;" + (click)="openAction()"> + <span style="width: 100%; + color:white; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'plus' | feather:15"></span> </button> + <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Rule</span> + </div> + </div> + + <div style="margin: 15px 0 10px 0;"> + + <div *ngFor="let item of store.ruleList; let index = index" data-tests-id="ruleElement" (mouseleave)="hoveredIndex=-1" + (mouseover)="hoveredIndex=index" class="item" style="display: flex;" [ngStyle]="hoveredIndex === index ? {'background-color': '#E6F6FB', 'color': '#009FDB'} : {'background-color': '#FFFFFF', 'color':'gray'}"> + <span style="width:100%; display: flex; align-items: center;"> + {{item.description}} - [{{item.uid}}] + </span> + <div style="display: flex; justify-content: flex-end;" *ngIf="index==hoveredIndex"> + <button (click)="openAction(item)" data-tests-id="editRule" class="btn-list" mat-icon-button> + <mat-icon class="md-24">mode_edit</mat-icon> + </button> + <button mat-icon-button class='btn-list' (click)="copyRule(item, index)" data-tests-id="copyRule"> + <span style="width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center;" + [innerHTML]="'copy' | feather:20"></span> + </button> + <button (click)="removeItem(item.uid, null)" data-tests-id="deleteRule" class="btn-list" mat-icon-button> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> </div> </div> + </div> + </div> </div> diff --git a/public/src/app/rule-engine/rule-list/rule-list.component.scss b/public/src/app/rule-engine/rule-list/rule-list.component.scss index 6446fbd..822a3f4 100644 --- a/public/src/app/rule-engine/rule-list/rule-list.component.scss +++ b/public/src/app/rule-engine/rule-list/rule-list.component.scss @@ -6,6 +6,7 @@ flex-direction: column; margin: 0; padding: 0; + width: 100%; .header { position: relative; display: flex; @@ -28,6 +29,10 @@ } } +/deep/ .mat-mini-fab .mat-button-wrapper { + padding: 0; +} + .my-full-screen-dialog .mat-dialog-container { max-width: none; width: 100vw; @@ -117,3 +122,74 @@ .material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } + +.listOfRule { + display: flex; + background-color: #ffffff; + color: gray; + &:hover { + background-color: #e6f6fb; + color: #009fdb; + } + .ruleList-btn { + opacity: 0; + } + &:hover .ruleList-btn { + opacity: 1; + } +} + +.gray { + color: #696969; +} + +.icon-img { + width: 24px; + height: 24px; + padding: 2px; +} + +.button-list { + position: absolute; + left: 100px; + list-style-type: none; + width: 150px; + border-radius: 2px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.3); + background-color: #ffffff; + border: solid 1px #d2d2d2; + border-top: 2px solid #009fdb; + margin-left: 11px; + top: 10px; + // margin-top: 15px; + .button-options { + height: 29px; + padding: 7px 9px; + width: 100%; + text-align: left; + background: white; + border: 0px; + &:hover { + background-color: #e6f6fb; + cursor: pointer; + } + &:disabled, + [disabled] { + cursor: default; + } + } +} + +.btn-wrapper { + position: relative; + cursor: pointer; +} +.btn-wrapper input[type='file'] { + position: absolute; + left: 0; + top: 0; + opacity: 0; + width: 100%; + height: 36px; + cursor: pointer; +} diff --git a/public/src/app/rule-engine/rule-list/rule-list.component.ts b/public/src/app/rule-engine/rule-list/rule-list.component.ts index 2857ea2..c2878c1 100644 --- a/public/src/app/rule-engine/rule-list/rule-list.component.ts +++ b/public/src/app/rule-engine/rule-list/rule-list.component.ts @@ -6,6 +6,10 @@ import { timer } from 'rxjs/observable/timer'; import { Store } from '../../store/store'; import { RuleEngineApiService } from '../api/rule-engine-api.service'; import { ConfirmPopupComponent } from '../confirm-popup/confirm-popup.component'; +import { cloneDeep, has, countBy } from 'lodash'; +import { toJS } from 'mobx'; +import { v4 as uuidGenarator } from 'uuid'; +import { environment } from '../../../environments/environment'; const primaryColor = '#009fdb'; @@ -13,7 +17,7 @@ const primaryColor = '#009fdb'; selector: 'app-rule-list', templateUrl: './rule-list.component.html', styleUrls: ['./rule-list.component.scss'], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.Emulated }) export class RuleListComponent { @ViewChild('versionEventType') versionType; @@ -27,6 +31,18 @@ export class RuleListComponent { params; versions; metaData; + tabName; + // group data + showBtnList = false; + entryPhase; + publishPhase; + latestBtnGroup; + imgBase = environment.imagePath; + fileToUpload; + fileName; + // filter + ifStatement = false; + condition: any; private errorHandler(error: any) { this.store.loader = false; @@ -45,7 +61,206 @@ export class RuleListComponent { } } + updateCondition(data) { + this.condition = data; + } + + filterCheckbox() { + this.ifStatement = !this.ifStatement; + if (!this.ifStatement && this.condition !== undefined) { + this.deleteFilter(); + } + } + + removeConditionCheck(flag) { + this.ifStatement = flag; + if (this.condition !== undefined) { + this.deleteFilter(); + } + } + + private deleteFilter() { + this.error = null; + this.store.loader = true; + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + this._ruleApi.deleteFilter().subscribe( + response => { + console.log('success import', response); + this.store.loader = false; + }, + error => { + const errorMsg = Object.values(error) as any; + if (errorMsg[0].messageId !== 'SVC6119') { + this.errorHandler(error); + } else { + this.store.loader = false; + } + this.condition = null; + } + ); + }, + error => { + this.errorHandler(error); + } + ); + } + + disabledMapBtn(btnType) { + if (this.store.groupList.length > 0) { + if (btnType === 'map') { + if ( + this.store.groupList[this.store.groupList.length - 1].groupId + .substring(0, 1) + .toLowerCase() === 'm' || + this.store.groupList.length === 3 + ) { + return true; + } else { + return false; + } + } else { + if ( + this.store.groupList[this.store.groupList.length - 1].groupId + .substring(0, 1) + .toLowerCase() === 'e' || + this.store.groupList.length === 3 + ) { + return true; + } else { + return false; + } + } + } + } + + disableDeleteGroup(groupId) { + const countGroupType = countBy(this.store.groupList, item => { + const innerGroupType = + item.groupId.substring(0, 1).toLowerCase() === 'm' ? 'map' : 'enrich'; + return innerGroupType === 'map' ? 'map' : 'enrich'; + }); + const groupType = + groupId.substring(0, 1).toLowerCase() === 'm' ? 'map' : 'enrich'; + if (groupType === 'map') { + return countGroupType.enrich === 2 ? true : false; + } else { + return countGroupType.map === 2 ? true : false; + } + } + + handleImportCDAP(files: FileList, groupId, phaseName) { + this.error = null; + this.store.loader = true; + this.fileToUpload = files.item(0); + console.log('file to load:', this.fileToUpload); + this.fileName = this.fileToUpload !== null ? this.fileToUpload.name : ''; + const reader = new FileReader(); + reader.readAsText(this.fileToUpload, 'UTF-8'); + reader.onload = () => { + console.log(reader.result); + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + const input = { + version: this.versionType.selectedVersion, + eventType: this.versionType.selectedEvent, + groupId: groupId, + phase: phaseName, + payload: JSON.parse(reader.result) + }; + this._ruleApi.importPhase(input).subscribe( + response => { + console.log('success import', response); + this.store.loader = false; + this.store.updateRuleList(Object.values(response.rules)); + }, + error => { + this.errorHandler(error); + } + ); + }, + error => { + this.errorHandler(error); + } + ); + }; + } + + addGroup(type) { + this.latestBtnGroup = type; + const defaultPhase = + type === 'enrich' + ? `standard_${this.tabName}_enrich` + : `standard_${this.tabName}`; + const groupId = type + uuidGenarator(); + const newGroup = { + groupId: groupId, + phase: defaultPhase + }; + this.store.groupList.push(newGroup); + this.showBtnList = false; + } + + deleteGroup(groupId) { + this.store.loader = true; + this.error = null; + // check if group list have list + const selectedGroup = this.store.groupList.filter( + item => item.groupId === groupId + ); + const isExistInRuleList = this.store.ruleList.filter( + item => item.groupId === groupId + ); + if (isExistInRuleList.length < 1) { + this.store.groupList = this.store.groupList.filter( + item => item.groupId !== groupId + ); + this.store.loader = false; + } else { + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + this._ruleApi.deleteGroup(groupId, res.uuid).subscribe( + response => { + this.store.deleteFromGroup(groupId); + this.store.loader = false; + }, + error => { + this.errorHandler(error); + } + ); + }, + error => this.errorHandler(error), + () => {} + ); + } + } + private getListOfRules() { + this.error = null; this._ruleApi.getListOfRules().subscribe( response => { console.log('res: %o', response); @@ -57,20 +272,37 @@ export class RuleListComponent { ); this.store.updateRuleList(Object.values(response.rules)); this.targetSource = response.schema; - this.store.notifyIdValue = response.notifyId; - this.versionType.notifyIdCheckbox = - response.notifyId !== '' ? true : false; + this.entryPhase = response.entryPhase; + this.publishPhase = response.publishPhase; + this.condition = response.filter; + this.ifStatement = this.condition == null ? false : true; } else { - this.versionType.notifyIdCheckbox = false; this.store.resetRuleList(); + this.condition = null; + this.ifStatement = false; this.versionType.updateVersionTypeFlag(false); this.targetSource = null; + + this._ruleApi.getInitialPhases(this.store.flowType).subscribe( + data => { + (this.entryPhase = data.entryPhase), + (this.publishPhase = data.publishPhase); + }, + error => { + this.errorHandler(error); + } + ); // if the the list is empty then get version and domain events - this._ruleApi.getMetaData().subscribe(data => { - console.log(data); - this.versions = data.map(x => x.version); - this.metaData = data; - }); + this._ruleApi.getMetaData().subscribe( + data => { + console.log(data); + this.versions = data.map(x => x.version); + this.metaData = data; + }, + error => { + this.errorHandler(error); + } + ); } this.store.loader = false; }, @@ -87,28 +319,120 @@ export class RuleListComponent { public store: Store ) { this.store.loader = false; - this._ruleApi.tabIndex.subscribe(index => { - console.log('rule index in rule-list component:', index); - const tabName = this.store.cdump.nodes[index].name; - console.log('tab name:', tabName); - - if (tabName.toLowerCase().includes('map')) { - this.params = { - vfcmtUuid: this.store.mcUuid, - nodeName: this.store.tabParmasForRule[0].name, - nodeId: this.store.tabParmasForRule[0].nid, - fieldName: this.store.tabsProperties[index][0].name, - userId: this.store.sdcParmas.userId, - flowType: this.store.cdump.flowType - }; - console.log('params: %o', this.params); - this.store.loader = true; - // set api params by iframe url query - this._ruleApi.setParams(this.params); - store.ruleListExistParams = this.params; - this.getListOfRules(); + this._ruleApi.tabIndex + // .filter(index => { if (index >= 0) { const tabName = + // this.store.cdump.nodes[index].name; console.log('tab name:', tabName); if + // (tabName.toLowerCase().includes('map')) { return index; } } }) + .subscribe(index => { + this.error = null; + if (index >= 0) { + this.tabName = this.store.cdump.nodes[index].name; + console.log('tab name:', this.tabName); + if ( + this.tabName.toLowerCase().includes('map') || + this.tabName.toLowerCase().includes('highlandpark') || + this.tabName.toLowerCase().includes('hp') + ) { + const advancedSetting = this.store.tabsProperties[index].filter( + item => { + if ( + !( + item.hasOwnProperty('constraints') && + item.value !== undefined && + !item.value.includes('get_input') + ) + ) { + return item; + } + } + ); + const mappingTarget = advancedSetting[0].name; + console.log('mappingTarget', mappingTarget); + + this.params = { + vfcmtUuid: this.store.mcUuid, + nodeName: this.store.tabParmasForRule[0].name, + nodeId: this.store.tabParmasForRule[0].nid, + fieldName: mappingTarget, + userId: this.store.sdcParmas.userId, + flowType: this.store.cdump.flowType + }; + console.log('params: %o', this.params); + this.store.loader = true; + // set api params by iframe url query + this._ruleApi.setParams(this.params); + store.ruleListExistParams = this.params; + this.getListOfRules(); + } + } + }); + } + + applyFilter() { + this.store.loader = true; + this.error = null; + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + let conditionData2server = null; + conditionData2server = this.convertConditionToServer(this.condition); + const newFilter = { + version: this.versionType.selectedVersion, + eventType: this.versionType.selectedEvent, + entryPhase: this.entryPhase, + publishPhase: this.publishPhase, + filter: conditionData2server + }; + this._ruleApi.applyFilter(newFilter).subscribe( + success => { + this.store.loader = false; + }, + error => { + this.errorHandler(error); + } + ); + }, + error => this.errorHandler(error), + () => {} + ); + } + + 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]; + } + + 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; } handlePropertyChange() { @@ -119,46 +443,52 @@ export class RuleListComponent { translateRules() { this.store.loader = true; - // send translate JSON - const nofityId = this.store.notifyIdValue; - this._ruleApi.translate(nofityId).subscribe( - data => { - this.store.loader = false; - console.log(JSON.stringify(data)); - let domElementName: string; - this.store.configurationForm.forEach(property => { - console.log('mappingTarget ', this.versionType.mappingTarget); - if (property.name === this.versionType.mappingTarget) { - property.value = JSON.stringify(data); - domElementName = property.name; - console.log(property.name); - } - }); - this.toastr.success('', 'Translate succeeded'); - this.store.expandAdvancedSetting[this.store.tabIndex] = true; - const source = timer(500); - source.subscribe(val => { - const el = document.getElementById(domElementName); - const label = el.children.item(0) as HTMLElement; - label.style.color = primaryColor; - const input = el.children.item(1) as HTMLElement; - input.style.color = primaryColor; - input.style.borderColor = primaryColor; - el.scrollIntoView(); - }); - }, - error => { - this.errorHandler(error); - } - ); + this.error = null; + // send translate JSON const nofityId = this.store.notifyIdValue; + const mcUuid = this.store.mcUuid; + this._ruleApi + .translate(this.entryPhase, this.publishPhase, mcUuid) + .subscribe( + data => { + this.store.loader = false; + console.log(JSON.stringify(data)); + let domElementName: string; + this.store.configurationForm.forEach(property => { + console.log('mappingTarget ', this.versionType.mappingTarget); + if (property.name === this.versionType.mappingTarget) { + property.value = JSON.stringify(data); + domElementName = property.name; + console.log(property.name); + } + }); + this.toastr.success('', 'Successfull translation'); + this.store.expandAdvancedSetting[this.store.tabIndex] = true; + const source = timer(500); + source.subscribe(val => { + const el = document.getElementById(domElementName); + const label = el.children.item(0) as HTMLElement; + label.style.color = primaryColor; + const input = el.children.item(1) as HTMLElement; + input.style.color = primaryColor; + input.style.borderColor = primaryColor; + el.scrollIntoView(); + this.store.cdumpIsDirty = true; + }); + }, + error => { + this.errorHandler(error); + } + ); } handleUpdateNode(data) { this.targetSource = data.nodes; this.store.resetRuleList(); + this.condition = null; + this.ifStatement = false; } - removeItem(uid) { + removeItem(uid, groupId) { this.dialogRef = this.dialog.open(ConfirmPopupComponent, { panelClass: 'my-confrim-dialog', disableClose: true @@ -168,38 +498,70 @@ export class RuleListComponent { if (result) { // call be api this.store.loader = true; - this._ruleApi.deleteRule(uid).subscribe( - success => { - this.store.removeRuleFromList(uid); - // if its the last rule - if (this.store.ruleList.length === 0) { - this._ruleApi.getMetaData().subscribe(data => { - console.log(data); - this.versions = data.map(x => x.version); - this.metaData = data; - this.versionType.updateVersionTypeFlag(false); - this.targetSource = null; - }); - } - this.store.loader = false; - }, - error => { - this.store.loader = false; - this.errorHandler(error); - } - ); + this.error = null; + this._ruleApi + .getLatestMcUuid({ + contextType: this.store.sdcParmas.contextType, + serviceUuid: this.store.sdcParmas.uuid, + vfiName: this.store.vfiName, + vfcmtUuid: this.store.mcUuid + }) + .subscribe( + res => { + this.store.mcUuid = res.uuid; + this._ruleApi.deleteRule(uid, res.uuid).subscribe( + success => { + this.store.removeRuleFromList(uid, groupId); + // if its the last rule + if (this.store.ruleList.length === 0) { + this._ruleApi.getMetaData().subscribe(data => { + console.log(data); + this.versions = data.map(x => x.version); + this.metaData = data; + this.versionType.updateVersionTypeFlag(false); + this.targetSource = null; + }); + } + this.store.loader = false; + }, + error => { + this.store.loader = false; + this.errorHandler(error); + } + ); + }, + error => this.errorHandler(error), + () => {} + ); } }); } - openAction(item): void { + copyRule(rule, index, groupItem) { + const copyRule = cloneDeep(toJS(rule)); + copyRule.uid = ''; + copyRule.description = copyRule.description.concat('_Copy'); + this.store.ruleList.push(copyRule); + this.openAction(copyRule, groupItem); + this.toastr.warning( + 'The rule you are editing has unsaved changes, please make sure to save your work' + + '.', + 'The mapping rule is copied' + ); + } + + openAction(item, groupItem): void { this.crud = isEmpty(item) ? 'new' : 'edit'; this._ruleApi.passDataToEditor({ version: this.versionType.selectedVersion, eventType: this.versionType.selectedEvent, targetSource: this.targetSource, item: isEmpty(item) ? null : item, - params: this.params + params: this.params, + groupId: isEmpty(groupItem) ? null : groupItem.groupId, + phase: isEmpty(groupItem) ? null : groupItem.phase, + entryPhase: this.entryPhase, + publishPhase: this.publishPhase }); this.store.isLeftVisible = false; diff --git a/public/src/app/rule-engine/target/target.component.ts b/public/src/app/rule-engine/target/target.component.ts index c9aa75c..b200144 100644 --- a/public/src/app/rule-engine/target/target.component.ts +++ b/public/src/app/rule-engine/target/target.component.ts @@ -4,7 +4,8 @@ import { ViewChild, Input, Output, - EventEmitter + EventEmitter, + ChangeDetectorRef } from '@angular/core'; import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component'; // import {trigger, state, animate, transition, style} from @@ -36,6 +37,8 @@ export class TargetComponent { animateAcceleration: 1.2 }; + constructor(private changeDetector: ChangeDetectorRef) {} + filterFn(value, treeModel: TreeModel) { treeModel.filterNodes(node => fuzzysearch(value, node.data.name)); } @@ -49,6 +52,7 @@ export class TargetComponent { id: action.target, name: '' }; + this.changeDetector.detectChanges(); } onEvent(event) { diff --git a/public/src/app/rule-engine/version-type-select/version-type-select.component.html b/public/src/app/rule-engine/version-type-select/version-type-select.component.html index 74c55a8..df1b497 100644 --- a/public/src/app/rule-engine/version-type-select/version-type-select.component.html +++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.html @@ -2,10 +2,12 @@ <div style="flex:1; display: flex; align-items: flex-end;"> - <div style="display:flex; flex-direction:column; margin-right: 25px;"> - <span class="field-label required space-down" style="margin-right: 10px;">Mapping Target</span> + <div style="display:flex; flex-direction:column; margin-right: 10px;"> + <span class="field-label required space-down" style="margin-right: 10px;"> + Mapping Target + </span> <select name="mappingTarget" [(ngModel)]="mappingTarget" (ngModelChange)="onChangeMapping($event)" data-tests-id="mappingDdl" - style="height: 35px; padding: 0.3rem; border: 1px solid #d2d2d2" class="field-select"> + style="height: 35px; padding: 0.3rem; border: 1px solid #d2d2d2; width:250px;" class="field-select"> <option [ngValue]="null" disabled>Select Mapping</option> <optgroup label="Rules Configured"> <option *ngFor="let target of advancedSetting" [hidden]="!target.isExist" [value]="target.name" data-tests-id="templateOptionsExist">{{target.name}}</option> @@ -16,20 +18,20 @@ </select> </div> - <div style="display:flex; flex-direction:column; margin-right: 25px;"> + <div style="display:flex; flex-direction:column; margin-right: 10px;"> <span class="field-label required space-down" style="font-size: 13px; margin-right: 10px; display: flex; align-items: center;" [ngClass]="{'required' : !readOnly}"> Version </span> - <select *ngIf="!readOnly" style="height: 35px; padding: 0.3rem; border: 1px solid #d2d2d2" [(ngModel)]="selectedVersion" (ngModelChange)="onSelectVersion($event)" - data-tests-id="selectVersion"> + <select *ngIf="!readOnly" style="height: 35px; padding: 0.3rem; border: 1px solid #d2d2d2" [(ngModel)]="selectedVersion" + (ngModelChange)="onSelectVersion($event)" data-tests-id="selectVersion"> <option [ngValue]="null" disabled>Select Version</option> <option *ngFor="let version of versions" [value]="version" data-tests-id="option">{{version}}</option> </select> <span *ngIf="readOnly" style="height: 35px; padding: 0.3rem; width:100px; border: 1px solid #D2D2D2; display: flex; align-items: center; background: #F2F2F2">{{selectedVersion}}</span> </div> - <div style="display:flex; flex-direction:column; margin-right: 25px;"> + <div style="display:flex; flex-direction:column; margin-right: 10px;"> <span class="field-label required space-down" style="font-size: 13px; display: flex; align-items: center; width: 100px;" [ngClass]="{'required' : !readOnly}"> Event Domain @@ -38,16 +40,16 @@ data-tests-id="selectEventType"> <option [ngValue]="null" disabled>Select Type</option> <option *ngFor="let event of events" [value]="event" data-tests-id="option">{{event | slice:0:event.length-6}}</option> - </select> - <span *ngIf="readOnly" style="height: 35px; padding: 0.3rem; width:200px; border: 1px solid #D2D2D2; display: flex; align-items: center; background: #F2F2F2">{{selectedEvent | slice:0:selectedEvent.length-6}}</span> + </select> + <span *ngIf="readOnly" style="height: 35px; padding: 0.3rem; width:200px; border: 1px solid #D2D2D2; display: flex; align-items: center; background: #F2F2F2">{{selectedEvent + | slice:0:selectedEvent.length-6}}</span> </div> - <div class="notifyId" style="display: flex; flex-direction:column; margin-right:25px;"> + <!-- <div class="notifyId" style="display: flex; flex-direction:column; margin-right:10px;"> <div class="pretty p-svg space-down"> <input type="checkbox" name="notifyIdCheckbox" data-tests-id="notifyIdCheckbox" [checked]="notifyIdCheckbox" (change)="changeNotifyId()" /> <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> @@ -58,7 +60,8 @@ <div *ngIf="notifyIdCheckbox" class="input-wrapper"> <input type="text" ngModel required name="defaultInput" data-tests-id="defaultInput" [(ngModel)]="store.notifyIdValue" class="input"> </div> - </div> + </div> --> + </div> </div> diff --git a/public/src/app/rule-engine/version-type-select/version-type-select.component.scss b/public/src/app/rule-engine/version-type-select/version-type-select.component.scss index 1be996e..a6eca3f 100644 --- a/public/src/app/rule-engine/version-type-select/version-type-select.component.scss +++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.scss @@ -2,7 +2,7 @@ display: flex; // margin: 10px 0; // align-items: center; flex-direction: column; - margin-bottom: 30px; + // margin-bottom: 30px; } .small-padding { diff --git a/public/src/app/rule-engine/version-type-select/version-type-select.component.ts b/public/src/app/rule-engine/version-type-select/version-type-select.component.ts index ff229cd..6869260 100644 --- a/public/src/app/rule-engine/version-type-select/version-type-select.component.ts +++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.ts @@ -28,40 +28,46 @@ export class VersionTypeSelectComponent { // set ddl with the first option value. this._ruleApi.tabIndex.subscribe(index => { - console.log('rule index:', index); - - const tabName = this.store.cdump.nodes[index].name; - console.log('tab name:', tabName); + if (index >= 0) { + const tabName = this.store.cdump.nodes[index].name; + console.log('tab name:', tabName); + if ( + tabName.toLowerCase().includes('map') || + tabName.toLowerCase().includes('highlandpark') || + tabName.toLowerCase().includes('hp') + ) { + this.advancedSetting = this.store.tabsProperties[index].filter( + item => { + if ( + !( + item.hasOwnProperty('constraints') && + item.value !== undefined && + !item.value.includes('get_input') + ) + ) { + return item; + } + } + ); + this.mappingTarget = this.advancedSetting[0].name; - if (tabName.toLowerCase().includes('map')) { - this.mappingTarget = this.store.tabsProperties[index][0].name; - this.advancedSetting = this.store.tabsProperties[index].filter(item => { - if ( - !( - item.hasOwnProperty('constraints') && - !item.value.includes('get_input') + this._ruleApi + .generateMappingRulesFileName( + this.store.ruleListExistParams.nodeName, + this.store.ruleListExistParams.nodeId, + this.store.ruleListExistParams.vfcmtUuid ) - ) { - return item; - } - }); - - this._ruleApi - .generateMappingRulesFileName( - this.store.ruleListExistParams.nodeName, - this.store.ruleListExistParams.nodeId, - this.store.ruleListExistParams.vfcmtUuid - ) - .subscribe(response => { - console.log('generateMappingRulesFileName response: ', response); - this.advancedSetting.forEach(element => { - if (response.includes(element.name)) { - element.isExist = true; - } else { - element.isExist = false; - } + .subscribe(response => { + console.log('generateMappingRulesFileName response: ', response); + this.advancedSetting.forEach(element => { + if (response.includes(element.name)) { + element.isExist = true; + } else { + element.isExist = false; + } + }); }); - }); + } } }); } diff --git a/public/src/app/rule-frame/rule-frame.component.html b/public/src/app/rule-frame/rule-frame.component.html index e0afa3d..9258342 100644 --- a/public/src/app/rule-frame/rule-frame.component.html +++ b/public/src/app/rule-frame/rule-frame.component.html @@ -1,11 +1,12 @@ <div style="position: relative; display: flex; justify-content: flex-end; height: 100%;"> - <div *ngIf="!tabName.toLowerCase().includes('map')" style="margin: 1em;"> + <div *ngIf="!tabName.toLowerCase().includes('map') && !(tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp'))" + style="margin: 1em;"> <app-bar-icons [tabName]="tabName"></app-bar-icons> </div> <!-- rule engine --> - <div style="width: 100%;" *ngIf="tabName.toLowerCase().includes('map')"> + <div style="width: 100%;" *ngIf="tabName.toLowerCase().includes('map') || tabName.toLowerCase().includes('highlandpark') || tabName.toLowerCase().includes('hp')"> <app-slide-panel [activePane]="store.isLeftVisible ? 'left' : 'right'"> <div leftPane style="height: 100%; overflow: auto;"> <app-rule-list></app-rule-list> diff --git a/public/src/app/store/store.ts b/public/src/app/store/store.ts index b075699..5ae4f24 100644 --- a/public/src/app/store/store.ts +++ b/public/src/app/store/store.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { findIndex } from 'lodash'; import { action, computed, observable, toJS } from 'mobx'; +import { groupBy, prop, compose, values } from 'ramda'; @Injectable() export class Store { @@ -14,6 +15,7 @@ export class Store { @observable loader = false; @observable cdumpIsDirty = false; @observable expandAdvancedSetting = []; + @observable expandImports = []; @observable generalflow; @observable vfiName; @observable flowType; @@ -27,6 +29,7 @@ export class Store { // rule-engine @observable tabParmasForRule; @observable ruleList = new Array(); + @observable groupList = new Array(); @observable ruleListExistParams; @observable ruleEditorInitializedState; @observable isLeftVisible; @@ -50,22 +53,60 @@ export class Store { console.log('new rule'); this.ruleList.push(rule); } + // handle group list + if (rule.groupId !== undefined) { + this.groupList + .filter(item => item.groupId === rule.groupId) + .map(item2 => { + if (item2.list === undefined) { + item2.list = new Array(); + } + const ruleItemIndex = findIndex( + item2.list, + ruleFromList => ruleFromList.uid === rule.uid + ); + if (ruleItemIndex > -1) { + item2.list[ruleItemIndex] = rule; + } else { + item2.list.push(rule); + } + }); + } } @action updateRuleList(listOfRules) { this.ruleList = listOfRules; console.log(toJS(this.ruleList)); + const fn = compose(values, groupBy(prop('groupId')))(listOfRules); + const dis = fn.map(item => { + return { groupId: item[0].groupId, phase: item[0].phase, list: item }; + }); + console.log(dis); + this.groupList = dis; } @action - removeRuleFromList(uid) { + deleteFromGroup(groupId) { + this.groupList = this.groupList.filter(item => item.groupId !== groupId); + } + + @action + removeRuleFromList(uid, groupId) { this.ruleList = this.ruleList.filter(item => item.uid !== uid); + // remove from group + this.groupList.forEach(item => { + if (item.groupId === groupId) { + item.list = item.list.filter(listItem => listItem.uid !== uid); + } + return item; + }); } @action resetRuleList() { this.ruleList = new Array(); + this.groupList = new Array(); } @action @@ -85,21 +126,23 @@ export class Store { if (!x.assignment) { x.assignment = {}; x.assignment.value = ''; - } else if (typeof x.assignment.value === 'object') { - x.assignment.value = JSON.stringify(x.assignment.value); } if (x.value) { if (typeof x.value === 'object') { - x.value = JSON.stringify(x.value); + x.value = ''; } } else if (!x.value) { - x.value = x.assignment.value; + if (typeof x.assignment.value === 'object') { + x.value = ''; + } + // else { x.value = x.assignment.value; } } return x; }); }); nodes.map(() => { this.expandAdvancedSetting.push(false); + this.expandImports.push(false); }); console.log('tabsProperties: %o', this.tabsProperties.toJS()); } |