summaryrefslogtreecommitdiffstats
path: root/public
diff options
context:
space:
mode:
authorManor, Yanir (ym903w) <ym903w@intl.att.com>2018-09-20 14:18:38 +0300
committeryanir manor <ym903w@intl.att.com>2018-09-26 09:34:04 +0000
commit22eda038b6cb646d63bfaf617372fce2b5d98631 (patch)
tree0bb3ef9104e02c031305ecfb2ab0692a57b1ed73 /public
parent40bb7dc4a104dc7b387b3586e610299b85f903a3 (diff)
update code to latest
update code to latest Change-Id: I6ed427434b0da47e0d33507a0992b09fe48f9c52 Issue-ID: DCAEGEN2-821 Signed-off-by: Manor, Yanir (ym903w) <ym903w@intl.att.com>
Diffstat (limited to 'public')
-rw-r--r--public/package.json8
-rw-r--r--public/src/app/app.module.ts15
-rw-r--r--public/src/app/bar-icons/bar-icons.component.html46
-rw-r--r--public/src/app/bar-icons/bar-icons.component.scss18
-rw-r--r--public/src/app/bar-icons/bar-icons.component.ts13
-rw-r--r--public/src/app/diagram/diagram.component.html6
-rw-r--r--public/src/app/diagram/diagram.component.scss2
-rw-r--r--public/src/app/diagram/diagram.component.spec.ts2
-rw-r--r--public/src/app/general/general.component.scss2
-rw-r--r--public/src/app/general/general.component.ts2
-rw-r--r--public/src/app/home/home.component.html40
-rw-r--r--public/src/app/home/home.component.ts67
-rw-r--r--public/src/app/import-rules/import-rules.component.html47
-rw-r--r--public/src/app/import-rules/import-rules.component.scss23
-rw-r--r--public/src/app/import-rules/import-rules.component.ts140
-rw-r--r--public/src/app/main/main.component.html3
-rw-r--r--public/src/app/main/main.component.scss11
-rw-r--r--public/src/app/main/main.component.ts11
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.html24
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.scss27
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.ts319
-rw-r--r--public/src/app/rule-engine/action/action.component.html336
-rw-r--r--public/src/app/rule-engine/action/action.component.scss25
-rw-r--r--public/src/app/rule-engine/action/action.component.ts84
-rw-r--r--public/src/app/rule-engine/action/metric.data.ts43
-rw-r--r--public/src/app/rule-engine/action/papa.spec.ts84
-rw-r--r--public/src/app/rule-engine/api/rule-engine-api.service.ts138
-rw-r--r--public/src/app/rule-engine/condition/condition.component.html79
-rw-r--r--public/src/app/rule-engine/condition/condition.component.scss36
-rw-r--r--public/src/app/rule-engine/condition/condition.component.ts68
-rw-r--r--public/src/app/rule-engine/from/from.component.html31
-rw-r--r--public/src/app/rule-engine/from/from.component.scss4
-rw-r--r--public/src/app/rule-engine/from/from.component.ts73
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.html259
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.scss76
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.ts532
-rw-r--r--public/src/app/rule-engine/target/target.component.ts6
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.html27
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.scss2
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.ts68
-rw-r--r--public/src/app/rule-frame/rule-frame.component.html5
-rw-r--r--public/src/app/store/store.ts53
-rw-r--r--public/src/assets/images/group_enrich.svg1
-rw-r--r--public/src/assets/images/group_map.svg25
-rw-r--r--public/src/styles.css4
45 files changed, 2394 insertions, 491 deletions
diff --git a/public/package.json b/public/package.json
index 35c0892..f06f6d9 100644
--- a/public/package.json
+++ b/public/package.json
@@ -12,9 +12,6 @@
"test:coverage": "jest --coverage",
"lint": "ng lint",
"e2e": "ng e2e",
- "cy:run": "cypress run",
- "cy:open": "cypress open",
- "cy:report": "cypress run --reporter mochawesome",
"war-local": "npm run build && cd dist && jar -cvf rule_engine.war *",
"war-remote": "mvn clean install",
"storybook": "start-storybook -p 9001 -c .storybook",
@@ -48,7 +45,6 @@
"@angular/platform-browser": "^5.1.0",
"@angular/platform-browser-dynamic": "^5.1.0",
"@angular/router": "^5.1.0",
- "@bahmutov/add-typescript-to-cypress": "^2.0.0",
"@ng-select/ng-select": "^0.26.2",
"@swimlane/ngx-datatable": "^13.0.1",
"angular-tree-component": "^7.1.0",
@@ -60,13 +56,16 @@
"material-design-icons": "^3.0.1",
"mobx": "^4.2.0",
"mobx-angular": "^3.0.1",
+ "mobx-utils": "^5.0.0",
"ngx-datatable": "^1.0.3",
+ "ngx-papaparse": "^2.1.4",
"ngx-toastr": "^8.2.1",
"papaparse": "^4.3.6",
"pretty-checkbox": "3.0.3",
"primeng": "^5.0.2",
"ramda": "^0.25.0",
"rxjs": "^5.5.6",
+ "sdc-pubsub": "^1.0.20",
"uuid": "^3.2.1",
"zone.js": "^0.8.19"
},
@@ -88,7 +87,6 @@
"angular2-template-loader": "^0.6.2",
"babel-core": "^6.26.0",
"codelyzer": "^4.0.1",
- "cypress": "^2.1.0",
"electron": "^1.8.3",
"husky": "^0.14.3",
"istanbul-instrumenter-loader": "^3.0.1",
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());
}
diff --git a/public/src/assets/images/group_enrich.svg b/public/src/assets/images/group_enrich.svg
new file mode 100644
index 0000000..b0ac313
--- /dev/null
+++ b/public/src/assets/images/group_enrich.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 23.74 23.74"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{fill:#009fdb;}.cls-3{clip-path:url(#clip-path);}</style><clipPath id="clip-path"><polygon class="cls-1" points="14.49 16 9.24 16 9.24 6.72 14.49 6.72 14.49 8 10.76 8 10.76 10.53 14.26 10.53 14.26 11.8 10.76 11.8 10.76 14.71 14.49 14.71 14.49 16"/></clipPath></defs><title>Artboard 1 copy</title><path class="cls-2" d="M23.74,4.27V0H19.47V1.48H4.27V0H0V4.27H1.49v15.2H0v4.27H4.27V22.09h15.2v1.65h4.27V19.47H22.1V4.27Zm-3-3h1.74V3H20.73ZM1.27,3V1.27H3V3ZM3,22.47H1.27V20.73H3Zm19.47-1.74v1.74H20.73V20.73Zm-1.63-1.26H19.47v1.35H4.27V19.47H2.76V4.27H4.27V2.75h15.2V4.27h1.37Z"/><g class="cls-3"><rect class="cls-2" x="4.24" y="1.72" width="15.25" height="19.28"/></g></svg> \ No newline at end of file
diff --git a/public/src/assets/images/group_map.svg b/public/src/assets/images/group_map.svg
new file mode 100644
index 0000000..14cd038
--- /dev/null
+++ b/public/src/assets/images/group_map.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+ <title>icons/group_map</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="icons/group_map" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <polygon id="Fill-1" fill="#FFFFFF" points="2.59576986 21.9382739 21.9382739 21.9382739 21.9382739 2.59576986 2.59576986 2.59576986"></polygon>
+ <path d="M2.56278293,20.6403889 L20.6403889,20.6403889 L20.6403889,2.56278293 L2.56278293,2.56278293 L2.56278293,20.6403889 Z M1.29788493,21.9052869 L21.9052869,21.9052869 L21.9052869,1.29788493 L1.29788493,1.29788493 L1.29788493,21.9052869 Z" id="Fill-2" fill="#009FDB"></path>
+ <polygon id="Fill-3" fill="#FFFFFF" points="10.3830794 17.9724674 17.9724674 17.9724674 17.9724674 10.3830794 10.3830794 10.3830794"></polygon>
+ <path d="M10.3500925,16.6745825 L16.6745825,16.6745825 L16.6745825,10.3500925 L10.3500925,10.3500925 L10.3500925,16.6745825 Z M9.0851945,17.9394805 L17.9394805,17.9394805 L17.9394805,9.0851945 L9.0851945,9.0851945 L9.0851945,17.9394805 Z" id="Fill-4" fill="#009FDB"></path>
+ <polygon id="Fill-13" fill="#FFFFFF" points="5.19153972 14.0788126 12.7809277 14.0788126 12.7809277 6.48942465 5.19153972 6.48942465"></polygon>
+ <path d="M6.45643772,12.7809277 L12.7809277,12.7809277 L12.7809277,6.45643772 L6.45643772,6.45643772 L6.45643772,12.7809277 Z M5.19153972,14.0458257 L14.0458257,14.0458257 L14.0458257,5.19153972 L5.19153972,5.19153972 L5.19153972,14.0458257 Z" id="Fill-14" fill="#009FDB"></path>
+ <polygon id="Fill-5" fill="#FFFFFF" points="0 3.00413275 3.00413275 3.00413275 3.00413275 0 0 0"></polygon>
+ <path d="M0,4.26903075 L4.26903075,4.26903075 L4.26903075,0 L0,0 L0,4.26903075 Z M1.264898,3.00444897 L3.00444897,3.00444897 L3.00444897,1.26521422 L1.264898,1.26521422 L1.264898,3.00444897 Z" id="Fill-6" fill="#009FDB"></path>
+ <polygon id="Fill-7" fill="#FFFFFF" points="19.4682739 3.00413275 22.4724067 3.00413275 22.4724067 0 19.4682739 0"></polygon>
+ <path d="M19.4682739,4.26903075 L23.7373047,4.26903075 L23.7373047,0 L19.4682739,0 L19.4682739,4.26903075 Z M20.7331719,3.00444897 L22.4724067,3.00444897 L22.4724067,1.26521422 L20.7331719,1.26521422 L20.7331719,3.00444897 Z" id="Fill-8" fill="#009FDB"></path>
+ <polygon id="Fill-9" fill="#FFFFFF" points="0 22.4724067 3.00413275 22.4724067 3.00413275 19.4682739 0 19.4682739"></polygon>
+ <path d="M0,23.7373047 L4.26903075,23.7373047 L4.26903075,19.4682739 L0,19.4682739 L0,23.7373047 Z M1.264898,22.4727229 L3.00444897,22.4727229 L3.00444897,20.7331719 L1.264898,20.7331719 L1.264898,22.4727229 Z" id="Fill-10" fill="#009FDB"></path>
+ <polygon id="Fill-11" fill="#FFFFFF" points="19.4682739 22.4724067 22.4724067 22.4724067 22.4724067 19.4682739 19.4682739 19.4682739"></polygon>
+ <rect id="Rectangle" fill="#FFFFFF" x="5" y="5" width="13" height="13"></rect>
+ <path d="M19.4682739,23.7373047 L23.7373047,23.7373047 L23.7373047,19.4682739 L19.4682739,19.4682739 L19.4682739,23.7373047 Z M20.7331719,22.4727229 L22.4724067,22.4727229 L22.4724067,20.7331719 L20.7331719,20.7331719 L20.7331719,22.4727229 Z" id="Fill-12" fill="#009FDB"></path>
+ <path d="M11.2368164,16 L8.54541016,8.24951172 L8.49462891,8.24951172 C8.56656937,9.40055914 8.60253906,10.4796499 8.60253906,11.4868164 L8.60253906,16 L7.22509766,16 L7.22509766,6.71972656 L9.36425781,6.71972656 L11.9414062,14.1020508 L11.9794922,14.1020508 L14.6328125,6.71972656 L16.7783203,6.71972656 L16.7783203,16 L15.3183594,16 L15.3183594,11.4106445 C15.3183594,10.9493792 15.3299966,10.3484738 15.3532715,9.60791016 C15.3765463,8.86734656 15.3966471,8.41878333 15.4135742,8.26220703 L15.362793,8.26220703 L12.5761719,16 L11.2368164,16 Z" id="M" fill="#009FDB"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/public/src/styles.css b/public/src/styles.css
index a56199a..c9ff9f5 100644
--- a/public/src/styles.css
+++ b/public/src/styles.css
@@ -90,3 +90,7 @@ input.ng-touched.ng-invalid:not(form) {
.ui-dialog-titlebar {
background: white;
}
+
+::-webkit-file-upload-button {
+ cursor: pointer;
+}