summaryrefslogtreecommitdiffstats
path: root/public/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'public/src/app')
-rw-r--r--public/src/app/api/feather-pipe.ts19
-rw-r--r--public/src/app/api/rest-api.service.spec.ts26
-rw-r--r--public/src/app/api/rest-api.service.ts179
-rw-r--r--public/src/app/app-routing.module.ts27
-rw-r--r--public/src/app/app.component.html7
-rw-r--r--public/src/app/app.component.scss20
-rw-r--r--public/src/app/app.component.ts31
-rw-r--r--public/src/app/app.module.ts109
-rw-r--r--public/src/app/bar-icons/bar-icons.component.html59
-rw-r--r--public/src/app/bar-icons/bar-icons.component.scss48
-rw-r--r--public/src/app/bar-icons/bar-icons.component.ts47
-rw-r--r--public/src/app/diagram/diagram.component.html19
-rw-r--r--public/src/app/diagram/diagram.component.scss28
-rw-r--r--public/src/app/diagram/diagram.component.spec.ts26
-rw-r--r--public/src/app/diagram/diagram.component.ts12
-rw-r--r--public/src/app/error-dialog/error-dialog.component.html17
-rw-r--r--public/src/app/error-dialog/error-dialog.component.scss0
-rw-r--r--public/src/app/error-dialog/error-dialog.component.ts17
-rw-r--r--public/src/app/general/general.component.html83
-rw-r--r--public/src/app/general/general.component.scss38
-rw-r--r--public/src/app/general/general.component.spec.ts55
-rw-r--r--public/src/app/general/general.component.ts323
-rw-r--r--public/src/app/home/home.component.html106
-rw-r--r--public/src/app/home/home.component.scss110
-rw-r--r--public/src/app/home/home.component.ts188
-rw-r--r--public/src/app/host/host.service.ts44
-rw-r--r--public/src/app/loader/loader.component.html4
-rw-r--r--public/src/app/loader/loader.component.scss152
-rw-r--r--public/src/app/loader/loader.component.spec.ts26
-rw-r--r--public/src/app/loader/loader.component.ts12
-rw-r--r--public/src/app/main/main.component.html62
-rw-r--r--public/src/app/main/main.component.scss33
-rw-r--r--public/src/app/main/main.component.ts228
-rw-r--r--public/src/app/router.animations.ts66
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.html100
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.scss77
-rw-r--r--public/src/app/rule-engine/action-list/action-list.component.ts290
-rw-r--r--public/src/app/rule-engine/action/action.component.html114
-rw-r--r--public/src/app/rule-engine/action/action.component.scss116
-rw-r--r--public/src/app/rule-engine/action/action.component.ts51
-rw-r--r--public/src/app/rule-engine/api/rule-engine-api.service.spec.ts19
-rw-r--r--public/src/app/rule-engine/api/rule-engine-api.service.ts134
-rw-r--r--public/src/app/rule-engine/condition/condition.component.html89
-rw-r--r--public/src/app/rule-engine/condition/condition.component.scss114
-rw-r--r--public/src/app/rule-engine/condition/condition.component.spec.ts51
-rw-r--r--public/src/app/rule-engine/condition/condition.component.ts161
-rw-r--r--public/src/app/rule-engine/confirm-popup/confirm-popup.component.html12
-rw-r--r--public/src/app/rule-engine/confirm-popup/confirm-popup.component.scss20
-rw-r--r--public/src/app/rule-engine/confirm-popup/confirm-popup.component.ts18
-rw-r--r--public/src/app/rule-engine/from/from.component.html70
-rw-r--r--public/src/app/rule-engine/from/from.component.scss63
-rw-r--r--public/src/app/rule-engine/from/from.component.ts91
-rw-r--r--public/src/app/rule-engine/host/exit-mode.enum.ts4
-rw-r--r--public/src/app/rule-engine/host/host-params.ts8
-rw-r--r--public/src/app/rule-engine/host/host.service.spec.ts18
-rw-r--r--public/src/app/rule-engine/host/host.service.ts56
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.html73
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.scss109
-rw-r--r--public/src/app/rule-engine/rule-list/rule-list.component.ts197
-rw-r--r--public/src/app/rule-engine/slide-panel/slide-panel.component.html8
-rw-r--r--public/src/app/rule-engine/slide-panel/slide-panel.component.scss15
-rw-r--r--public/src/app/rule-engine/slide-panel/slide-panel.component.ts27
-rw-r--r--public/src/app/rule-engine/target/target.component.html28
-rw-r--r--public/src/app/rule-engine/target/target.component.scss99
-rw-r--r--public/src/app/rule-engine/target/target.component.spec.ts57
-rw-r--r--public/src/app/rule-engine/target/target.component.ts77
-rw-r--r--public/src/app/rule-engine/target/target.util.ts50
-rw-r--r--public/src/app/rule-engine/target/target.validation.spec.ts83
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.html34
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.scss46
-rw-r--r--public/src/app/rule-engine/version-type-select/version-type-select.component.ts86
-rw-r--r--public/src/app/rule-frame/rule-frame.component.html19
-rw-r--r--public/src/app/rule-frame/rule-frame.component.scss10
-rw-r--r--public/src/app/rule-frame/rule-frame.component.ts35
-rw-r--r--public/src/app/store/store.ts98
75 files changed, 5048 insertions, 0 deletions
diff --git a/public/src/app/api/feather-pipe.ts b/public/src/app/api/feather-pipe.ts
new file mode 100644
index 0000000..7a0715d
--- /dev/null
+++ b/public/src/app/api/feather-pipe.ts
@@ -0,0 +1,19 @@
+import { DomSanitizer } from '@angular/platform-browser';
+import { Pipe, PipeTransform } from '@angular/core';
+
+import * as feather from 'feather-icons/dist/feather';
+
+@Pipe({ name: 'feather' })
+export class FeatherIconsPipe implements PipeTransform {
+ constructor(private sanitizer: DomSanitizer) {}
+
+ transform(icon: string, size: number = 24, fill: string = 'none') {
+ return this.sanitizer.bypassSecurityTrustHtml(
+ feather.icons[icon].toSvg({
+ width: size,
+ height: size,
+ fill: fill
+ })
+ );
+ }
+}
diff --git a/public/src/app/api/rest-api.service.spec.ts b/public/src/app/api/rest-api.service.spec.ts
new file mode 100644
index 0000000..ce921cb
--- /dev/null
+++ b/public/src/app/api/rest-api.service.spec.ts
@@ -0,0 +1,26 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpModule } from '@angular/http';
+import { RestApiService } from './rest-api.service';
+import { v4 as genrateUuid } from 'uuid';
+
+describe('RestApiService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [RestApiService]
+ });
+ });
+
+ it(
+ 'should be created',
+ inject([RestApiService], (service: RestApiService) => {
+ expect(service).toBeTruthy();
+ })
+ );
+
+ it('should genrate deffrent uuid each time for request id', () => {
+ const firstUuid = genrateUuid();
+ const secondUuid = genrateUuid();
+ expect(firstUuid !== secondUuid).toBe(true);
+ });
+});
diff --git a/public/src/app/api/rest-api.service.ts b/public/src/app/api/rest-api.service.ts
new file mode 100644
index 0000000..ba5cc54
--- /dev/null
+++ b/public/src/app/api/rest-api.service.ts
@@ -0,0 +1,179 @@
+import { Injectable } from '@angular/core';
+import {
+ Http,
+ Response,
+ Headers,
+ RequestOptions,
+ URLSearchParams
+} from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+import 'rxjs/add/observable/throw';
+import { environment } from '../../environments/environment';
+import { v4 as uuidGenarator } from 'uuid';
+
+@Injectable()
+export class RestApiService {
+ options: RequestOptions;
+ headers: Headers;
+ baseUrl: string;
+
+ constructor(private http: Http) {
+ this.baseUrl = `${environment.apiBaseUrl}`;
+ this.headers = new Headers({
+ 'Content-Type': 'application/json',
+ USER_ID: 'ym903w'
+ });
+ this.options = new RequestOptions({ headers: this.headers });
+ }
+
+ getVfcmtsForMigration(params) {
+ const { contextType, uuid, version } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/${uuid}/${version}/getVfcmtsForMigration`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json() || 'Server error');
+ });
+ }
+
+ getVfcmtReferenceData(vfcmtUUID) {
+ const url = `${this.baseUrl}/getVfcmtReferenceData/${vfcmtUUID}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ getFlowType() {
+ const url = `${this.baseUrl}/conf/composition`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ createNewVFCMT(params) {
+ const url = `${this.baseUrl}/createMC`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .post(url, params, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json() || 'Server error');
+ });
+ }
+
+ importVFCMT(params) {
+ const url = `${this.baseUrl}/importMC`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .post(url, params, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json() || 'Server error');
+ });
+ }
+
+ getServiceInstances(serviceID) {
+ const url = `${this.baseUrl}/service/${serviceID}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json() || 'Server error');
+ });
+ }
+
+ getTemplateResources() {
+ const url = `${this.baseUrl}/getResourcesByMonitoringTemplateCategory`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ getMonitoringComponents(params) {
+ const { contextType, uuid, version } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/${uuid}/${version}/monitoringComponents`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ deleteMonitoringComponent(params, vfcmtUuid, vfiName) {
+ const { contextType, uuid } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/${uuid}/${vfiName}/${vfcmtUuid}/deleteVfcmtReference`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .delete(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ deleteMonitoringComponentWithBlueprint(
+ params,
+ monitoringComponentName,
+ vfcmtUuid,
+ vfiName
+ ) {
+ const { contextType, uuid } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/${monitoringComponentName}/${uuid}/${vfiName}/${vfcmtUuid}/deleteVfcmtReference`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .delete(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ getCompositionMonitoringComponent(vfcmtUuid) {
+ const url = `${this.baseUrl}/getMC/${vfcmtUuid}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ saveMonitoringComponent(params) {
+ const { contextType, serviceUuid, vfiName, vfcmtUuid, cdump } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/${serviceUuid}/${vfiName}/saveComposition/${vfcmtUuid}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .post(url, cdump, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+
+ submitMonitoringComponent(params) {
+ const { contextType, serviceUuid, vfiName, vfcmtUuid, flowType } = params;
+ const url = `${
+ this.baseUrl
+ }/${contextType}/createBluePrint/${vfcmtUuid}/${serviceUuid}/${vfiName}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuidGenarator());
+ return this.http
+ .post(url, {}, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => Observable.throw(error.json() || 'Server error'));
+ }
+}
diff --git a/public/src/app/app-routing.module.ts b/public/src/app/app-routing.module.ts
new file mode 100644
index 0000000..b2d1531
--- /dev/null
+++ b/public/src/app/app-routing.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { HomeComponent } from './home/home.component';
+import { MainComponent } from './main/main.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ redirectTo: '/home',
+ pathMatch: 'full'
+ },
+ {
+ path: 'home',
+ component: HomeComponent
+ },
+ {
+ path: 'main/:contextType/:uuid/:version/:mcid',
+ component: MainComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(routes, { useHash: true })],
+ exports: [RouterModule]
+})
+export class AppRoutingModule {}
diff --git a/public/src/app/app.component.html b/public/src/app/app.component.html
new file mode 100644
index 0000000..adb06f1
--- /dev/null
+++ b/public/src/app/app.component.html
@@ -0,0 +1,7 @@
+<!-- <div class="container"> -->
+<main [@slideAnimation]="getRouterOutletState(o)">
+ <app-error-dialog></app-error-dialog>
+ <app-loader [hidden]="!this.store.loader"></app-loader>
+ <router-outlet #o="outlet"></router-outlet>
+</main>
+<!-- </div> -->
diff --git a/public/src/app/app.component.scss b/public/src/app/app.component.scss
new file mode 100644
index 0000000..82b9721
--- /dev/null
+++ b/public/src/app/app.component.scss
@@ -0,0 +1,20 @@
+:host {
+ display: flex;
+ overflow: auto;
+ height: 100vh;
+
+ .container {
+ height: 100%;
+ }
+
+ main {
+ flex: 1;
+ position: relative;
+ }
+
+ /deep/ router-outlet ~ * {
+ position: absolute;
+ width: 100%;
+ // height: 100%;
+ }
+}
diff --git a/public/src/app/app.component.ts b/public/src/app/app.component.ts
new file mode 100644
index 0000000..0711538
--- /dev/null
+++ b/public/src/app/app.component.ts
@@ -0,0 +1,31 @@
+import { Component } from '@angular/core';
+import { slideAnimation } from './router.animations';
+import { ActivatedRoute } from '@angular/router';
+import { DomSanitizer } from '@angular/platform-browser';
+import { MatIconRegistry } from '@angular/material';
+import { Store } from './store/store';
+
+@Component({
+ selector: 'app-root',
+ animations: [slideAnimation],
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+ constructor(
+ private _iconRegistry: MatIconRegistry,
+ private _sanitizer: DomSanitizer,
+ private route: ActivatedRoute,
+ public store: Store
+ ) {
+ this.loadIcons(_iconRegistry, _sanitizer);
+ }
+
+ loadIcons(_iconRegistry: MatIconRegistry, _sanitizer: DomSanitizer) {
+ _iconRegistry.registerFontClassAlias('fontawesome', 'fa');
+ }
+
+ public getRouterOutletState(outlet) {
+ return outlet.isActivated ? outlet.activatedRoute : '';
+ }
+}
diff --git a/public/src/app/app.module.ts b/public/src/app/app.module.ts
new file mode 100644
index 0000000..8ed8c87
--- /dev/null
+++ b/public/src/app/app.module.ts
@@ -0,0 +1,109 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule, APP_INITIALIZER } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { HttpModule } from '@angular/http';
+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 { 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 { SdcUiComponentsModule } from 'sdc-ui/lib/angular';
+
+import { AppComponent } from './app.component';
+import { AppRoutingModule } from './app-routing.module';
+import { HomeComponent } from './home/home.component';
+import { GeneralComponent } from './general/general.component';
+import { MainComponent } from './main/main.component';
+import { RuleFrameComponent } from './rule-frame/rule-frame.component';
+
+import { HostService } from './host/host.service';
+import { RestApiService } from './api/rest-api.service';
+import { FeatherIconsPipe } from './api/feather-pipe';
+import { Store } from './store/store';
+import { LoaderComponent } from './loader/loader.component';
+import { ErrorDialogComponent } from './error-dialog/error-dialog.component';
+
+// rule engine
+import { TreeModule } from 'angular-tree-component';
+import { TargetComponent } from './rule-engine/target/target.component';
+import { VersionTypeSelectComponent } from './rule-engine/version-type-select/version-type-select.component';
+import { FromComponent } from './rule-engine/from/from.component';
+import { ActionComponent } from './rule-engine/action/action.component';
+import { ActionListComponent } from './rule-engine/action-list/action-list.component';
+import { ConditionComponent } from './rule-engine/condition/condition.component';
+import { RuleEngineApiService } from './rule-engine/api/rule-engine-api.service';
+import { ConfirmPopupComponent } from './rule-engine/confirm-popup/confirm-popup.component';
+import { SlidePanelComponent } from './rule-engine/slide-panel/slide-panel.component';
+import { RuleListComponent } from './rule-engine/rule-list/rule-list.component';
+import { BarIconsComponent } from './bar-icons/bar-icons.component';
+import { DiagramComponent } from './diagram/diagram.component';
+
+const appInitializerFn = () => {
+ return () => {
+ console.log('app initializing');
+ };
+};
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ HomeComponent,
+ GeneralComponent,
+ MainComponent,
+ RuleFrameComponent,
+ LoaderComponent,
+ FeatherIconsPipe,
+ ErrorDialogComponent,
+ TargetComponent,
+ VersionTypeSelectComponent,
+ FromComponent,
+ ActionComponent,
+ ActionListComponent,
+ ConditionComponent,
+ ConfirmPopupComponent,
+ SlidePanelComponent,
+ RuleListComponent,
+ BarIconsComponent,
+ DiagramComponent
+ ],
+ imports: [
+ BrowserModule,
+ BrowserAnimationsModule,
+ FormsModule,
+ HttpModule,
+ HttpClientModule,
+ AppRoutingModule,
+ MobxAngularModule,
+ TabViewModule,
+ DialogModule,
+ MatButtonModule,
+ MatIconModule,
+ MatDialogModule,
+ TreeModule,
+ NgSelectModule,
+ TooltipModule,
+ ToastrModule.forRoot({ enableHtml: true })
+ ],
+ entryComponents: [ConfirmPopupComponent],
+ providers: [
+ HostService,
+ RestApiService,
+ RuleEngineApiService,
+ Store,
+ {
+ provide: APP_INITIALIZER,
+ useFactory: appInitializerFn,
+ multi: true,
+ deps: []
+ }
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule {}
diff --git a/public/src/app/bar-icons/bar-icons.component.html b/public/src/app/bar-icons/bar-icons.component.html
new file mode 100644
index 0000000..03129bf
--- /dev/null
+++ b/public/src/app/bar-icons/bar-icons.component.html
@@ -0,0 +1,59 @@
+<div style="display: flex; position: relative; justify-content: flex-end;">
+ <div style="display: flex; justify-content: flex-end;" [class]="genrateBarTestId()">
+ <button mat-icon-button>
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'help-circle' | feather:18"></span>
+ </button>
+ <hr>
+
+ <div *ngIf="tabName.includes('map')" style="display: flex;">
+ <button mat-icon-button>
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'upload' | feather:18"></span>
+ </button>
+ <hr>
+
+ <button mat-icon-button>
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'download' | feather:18"></span>
+ </button>
+ <hr>
+ </div>
+
+ <button mat-icon-button (click)="enableSetting()" data-tests-id="setting-gear" [style.color]="this.store.expandAdvancedSetting[store.tabIndex] ? '#009FDB' : 'black'">
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'settings' | feather:18"></span>
+ </button>
+ </div>
+
+ <!-- advanced setting -->
+ <div class="setting" *ngIf="store.expandAdvancedSetting[store.tabIndex]">
+ <div *mobxAutorun style="width: 100%;" [class]="tabName+'-setting-list'">
+ <div style="font-size: 1.5em; padding: 0 12px;">{{tabName}} Advanced Setting</div>
+ <form #cdumpConfForm="ngForm">
+ <div *ngFor="let prop of store.configurationForm" class="field" [id]="prop.name">
+ <p class="field-label">{{prop.name}}</p>
+ <input *ngIf="!isPropertyDdl(prop)" type="text" name="{{prop.name}}" class="field-text" [(ngModel)]="prop.assignment.value"
+ (ngModelChange)="onChange($event)">
+ <select *ngIf="isPropertyDdl(prop)" class="field-text" name="{{prop.name}}" [(ngModel)]="prop.assignment.value" (ngModelChange)="onChange($event)">
+ <option *ngFor="let value of prop.constraints[0].valid_values" [value]="value">
+ {{value}}
+ </option>
+ </select>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/public/src/app/bar-icons/bar-icons.component.scss b/public/src/app/bar-icons/bar-icons.component.scss
new file mode 100644
index 0000000..893f757
--- /dev/null
+++ b/public/src/app/bar-icons/bar-icons.component.scss
@@ -0,0 +1,48 @@
+.setting {
+ position: absolute;
+ top: 47px;
+ right: 0;
+ background: white;
+ padding: 1em;
+ border: 1px solid gray;
+ display: flex;
+ min-width: 400px;
+ z-index: 2;
+ // width: 35%;
+}
+
+.target-field {
+ width: 370px;
+ display: flex;
+ align-items: center;
+ margin: 10px;
+ .field-label {
+ padding-right: 10px;
+ }
+ .required::before {
+ content: '*';
+ color: red;
+ padding-right: 5px;
+ }
+ .field-select {
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+}
+
+.field {
+ margin: 1em;
+ .field-label {
+ padding-bottom: 0.5em;
+ }
+ .field-text {
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+}
diff --git a/public/src/app/bar-icons/bar-icons.component.ts b/public/src/app/bar-icons/bar-icons.component.ts
new file mode 100644
index 0000000..adf4b88
--- /dev/null
+++ b/public/src/app/bar-icons/bar-icons.component.ts
@@ -0,0 +1,47 @@
+import { Component, Input, ViewChild } from '@angular/core';
+import { Store } from '../store/store';
+import { includes } from 'lodash';
+import { NgForm } from '@angular/forms';
+
+@Component({
+ selector: 'app-bar-icons',
+ templateUrl: './bar-icons.component.html',
+ styleUrls: ['./bar-icons.component.scss']
+})
+export class BarIconsComponent {
+ configuration;
+ @Input() tabName: string;
+ @ViewChild('cdumpConfForm') cdumpConfForm: NgForm;
+
+ constructor(public store: Store) {}
+
+ onChange(e) {
+ this.store.cdumpIsDirty = true;
+ }
+
+ isPropertyDdl(property) {
+ if (property.hasOwnProperty('constraints')) {
+ if (
+ includes(
+ property.constraints[0].valid_values,
+ property.assignment.value
+ )
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ genrateBarTestId() {
+ return `${this.tabName}-bar-icon-container`;
+ }
+
+ enableSetting() {
+ this.store.expandAdvancedSetting[this.store.tabIndex] = !this.store
+ .expandAdvancedSetting[this.store.tabIndex];
+ }
+}
diff --git a/public/src/app/diagram/diagram.component.html b/public/src/app/diagram/diagram.component.html
new file mode 100644
index 0000000..b3cb28a
--- /dev/null
+++ b/public/src/app/diagram/diagram.component.html
@@ -0,0 +1,19 @@
+<svg id="diagram" #diagram>
+ <svg viewBox="0 0 500 500" width="100%" height="500px" preserveAspectRatio="xMaxYMin meet" *ngFor="let item of list; let i = index">
+
+ <svg width="80px">
+ <text x="0" [attr.y]="45 * (i+1)">
+ {{item.source}}
+ </text>
+ </svg>
+
+ <circle cx="100" [attr.cy]="44 * (i+1)" r="5" />
+ <line x1="100" [attr.y1]="44 * (i+1)" [attr.x2]="maxWidth - 150" [attr.y2]="44 * (i+1)" stroke-width="2" stroke="black" stroke-dasharray="5, 5"
+ class="line" />
+ <circle [attr.cx]="maxWidth - 150" [attr.cy]="44 * (i+1)" r="5" />
+
+ <text [attr.x]="maxWidth - 130" [attr.y]="45 * (i+1)">
+ {{item.target}}
+ </text>
+ </svg>
+</svg>
diff --git a/public/src/app/diagram/diagram.component.scss b/public/src/app/diagram/diagram.component.scss
new file mode 100644
index 0000000..57437d8
--- /dev/null
+++ b/public/src/app/diagram/diagram.component.scss
@@ -0,0 +1,28 @@
+svg {
+ height: 400px;
+ width: 100%;
+ margin: auto;
+ display: block;
+ .line {
+ stroke-dasharray: 1400;
+ animation: draw 5s ease-in;
+ }
+}
+
+@keyframes draw {
+ from {
+ stroke-dashoffset: -1400;
+ }
+ to {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes dude {
+ 0% {
+ width: 0;
+ }
+ 100% {
+ width: 100%;
+ }
+}
diff --git a/public/src/app/diagram/diagram.component.spec.ts b/public/src/app/diagram/diagram.component.spec.ts
new file mode 100644
index 0000000..535f280
--- /dev/null
+++ b/public/src/app/diagram/diagram.component.spec.ts
@@ -0,0 +1,26 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DiagramComponent } from './diagram.component';
+
+describe('DiagramComponent', () => {
+ let component: DiagramComponent;
+ let fixture: ComponentFixture<DiagramComponent>;
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ declarations: [DiagramComponent]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DiagramComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/public/src/app/diagram/diagram.component.ts b/public/src/app/diagram/diagram.component.ts
new file mode 100644
index 0000000..a0ae3a1
--- /dev/null
+++ b/public/src/app/diagram/diagram.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'app-diagram',
+ templateUrl: './diagram.component.html',
+ styleUrls: ['./diagram.component.scss']
+})
+export class DiagramComponent {
+ @Input() list;
+ maxWidth: number = 500;
+ constructor() {}
+}
diff --git a/public/src/app/error-dialog/error-dialog.component.html b/public/src/app/error-dialog/error-dialog.component.html
new file mode 100644
index 0000000..7b72d06
--- /dev/null
+++ b/public/src/app/error-dialog/error-dialog.component.html
@@ -0,0 +1,17 @@
+<p-dialog [(visible)]="store.displayErrorDialog" modal="modal" width="500" [responsive]="true" data-tests-id="error-dialog">
+ <p-header>
+ <span style="font-size: 1.3em;">
+ Error
+ </span>
+ </p-header>
+
+ <div *ngFor="let error of store.ErrorContent">
+ {{ error.formattedErrorMessage }}
+ </div>
+
+ <p-footer>
+ <button mat-raised-button color="primary" (click)="closeDialog()" data-tests-id="error-cancel">
+ Cancel
+ </button>
+ </p-footer>
+</p-dialog>
diff --git a/public/src/app/error-dialog/error-dialog.component.scss b/public/src/app/error-dialog/error-dialog.component.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/public/src/app/error-dialog/error-dialog.component.scss
diff --git a/public/src/app/error-dialog/error-dialog.component.ts b/public/src/app/error-dialog/error-dialog.component.ts
new file mode 100644
index 0000000..3e7bfe0
--- /dev/null
+++ b/public/src/app/error-dialog/error-dialog.component.ts
@@ -0,0 +1,17 @@
+import { Component, OnInit } from '@angular/core';
+import { Store } from '../store/store';
+
+@Component({
+ selector: 'app-error-dialog',
+ templateUrl: './error-dialog.component.html',
+ styleUrls: ['./error-dialog.component.scss']
+})
+export class ErrorDialogComponent implements OnInit {
+ constructor(public store: Store) {}
+
+ ngOnInit() {}
+
+ closeDialog() {
+ this.store.displayErrorDialog = false;
+ }
+}
diff --git a/public/src/app/general/general.component.html b/public/src/app/general/general.component.html
new file mode 100644
index 0000000..dcea57a
--- /dev/null
+++ b/public/src/app/general/general.component.html
@@ -0,0 +1,83 @@
+<form #generalForm="ngForm" novalidate style="display: flex; margin: 1em;">
+ <div class="left">
+
+ <div class="import-wrapper" style="display: flex" *ngIf="store.generalflow === 'import' && !importCompleted">
+ <div class="field" style="width:70%">
+ <div class="field-label required" style="display: flex;">
+ <span>Select existing VFCMT</span>
+ </div>
+ <ng-select name="vfcmt" [items]="vfcmts" required [virtualScroll]="true" placeholder="Select VFCMT" [(ngModel)]="selectedVfcmt"
+ class="vfcmt-list" (change)="vfcmtChange($event)">
+ </ng-select>
+ </div>
+
+ <div class="field" style="width:30%">
+ <div class="field-label required" style="display: flex;">
+ <span>Select version</span>
+ </div>
+ <select name="version" required data-tests-id="vfcmtVersion" [(ngModel)]="selectedVersion" [style.background]="versions.length == 0 ? '#ebebe4' : 'white'"
+ (ngModelChange)="versionChange($event)" [disabled]="versions.length == 0" style="width: 100%; height: 30px;">
+ <option [ngValue]="null" disabled>Select version</option>
+ <option *ngFor="let item of versions" [value]="item.version">{{item.version}}</option>
+ </select>
+ </div>
+ </div>
+
+ <div class="field">
+ <div class="field-label required">Name</div>
+ <input type="text" name="name" ngModel required [(ngModel)]="newVfcmt.name" class="field-text" [disabled]="this.store.isEditMode || disableName"
+ data-tests-id="nameMc" />
+ </div>
+
+ <div class="field">
+ <div class="field-label required">Description</div>
+ <textarea required name="description" ngModel [(ngModel)]="newVfcmt.description" style="resize: none;" cols="30" rows="10"
+ class="field-text" data-tests-id="descMc" [disabled]="this.store.isEditMode || disableDescription"></textarea>
+ </div>
+
+ <div class="field" *ngIf="store.generalflow === 'new'">
+ <div class="field-label required" style="display: flex;">
+ <span>Template</span>
+ <span style="padding-left: 5px;" [innerHTML]="'help-circle' | feather:14"></span>
+ </div>
+ <select name="template" [disabled]="this.store.isEditMode" required [(ngModel)]="newVfcmt.template" (ngModelChange)="onChangeTemplate($event)"
+ data-tests-id="templateDdl" class="field-text" [style.background]="this.store.isEditMode ? '#ebebe4' : 'white'">
+ <option [ngValue]="null" disabled>Select template</option>
+ <option *ngFor="let template of templates" [value]="template.uuid" data-tests-id="templateOptions">{{template.name}}</option>
+ </select>
+ </div>
+
+ <div class="field" *ngIf="store.generalflow === 'import' || store.generalflow === 'edit'">
+ <div class="field-label required" style="display: flex;">
+ <span>Flow type</span>
+ <span style="padding-left: 5px;" [innerHTML]="'help-circle' | feather:14"></span>
+ </div>
+ <select name="flowType" [disabled]="this.store.isEditMode || disableFlowType" required [(ngModel)]="newVfcmt.flowType" data-tests-id="flowTypeDdl"
+ class="field-text" [style.background]="this.store.isEditMode || disableFlowType ? '#ebebe4' : 'white'">
+ <option [ngValue]="null" disabled>Select Flow Type</option>
+ <option *ngFor="let flowType of flowTypes" [value]="flowType" data-tests-id="flowTypeOptions">{{flowType}}</option>
+ </select>
+ </div>
+
+ <div class="field">
+ <div class="field-label required" style="display: flex;">
+ <span>Attached to</span>
+ <span style="padding-left: 5px;" [innerHTML]="'help-circle' | feather:14"></span>
+ </div>
+ <select name="serviceAttached" [disabled]="this.store.isEditMode || disableVnfiList" required [(ngModel)]="newVfcmt.vfni"
+ data-tests-id="vfniDdl" (ngModelChange)="onChangeVfni($event)" class="field-text" [style.background]="this.store.isEditMode || disableVnfiList ? '#ebebe4' : 'white'">
+ <option [ngValue]="null" disabled>Select VFNI</option>
+ <option *ngFor="let vfi of vfniList" [value]="vfi.resourceInstanceName">{{vfi.resourceInstanceName}}</option>
+ </select>
+ </div>
+ </div>
+
+ <div class="right">
+ <div style="padding: 0.7em 0.5em; padding-top: 1em; font-weight: 600;">Flow diagram</div>
+ <div>
+ <app-diagram [list]="list"></app-diagram>
+ <!-- <img style="width:100%; height:100%;" src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Flag_of_Romania.svg/1200px-Flag_of_Romania.svg.png"
+ alt="flow"> -->
+ </div>
+ </div>
+</form>
diff --git a/public/src/app/general/general.component.scss b/public/src/app/general/general.component.scss
new file mode 100644
index 0000000..d76e1ae
--- /dev/null
+++ b/public/src/app/general/general.component.scss
@@ -0,0 +1,38 @@
+.left,
+.right {
+ width: 50%;
+}
+
+.ng-select.ng-single .ng-control {
+ border-radius: 0;
+ height: 30px;
+ min-height: 30px;
+}
+
+.toast-container .toast {
+ width: 400px !important;
+ box-shadow: none;
+ border-radius: 0;
+}
+.toast-container .toast:hover {
+ box-shadow: none;
+}
+
+.field {
+ margin: 1em;
+ .field-label {
+ padding-bottom: 0.5em;
+ }
+ .required::before {
+ content: '*';
+ color: red;
+ padding-right: 5px;
+ }
+ .field-text {
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+}
diff --git a/public/src/app/general/general.component.spec.ts b/public/src/app/general/general.component.spec.ts
new file mode 100644
index 0000000..fb761db
--- /dev/null
+++ b/public/src/app/general/general.component.spec.ts
@@ -0,0 +1,55 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { GeneralComponent, groupingData } from './general.component';
+import { sortBy } from 'lodash';
+
+const data = [
+ {
+ name: 'avi',
+ version: '2.0'
+ },
+ {
+ name: 'stone',
+ version: '0.9'
+ },
+ {
+ name: 'avi',
+ version: '2.1'
+ },
+ {
+ name: 'vosk',
+ version: '0.1'
+ },
+ {
+ name: 'liav',
+ version: '0.5'
+ }
+];
+const sortedMatchVfcmtList = ['avi', 'liav', 'stone', 'vosk'];
+const sortedVersionInGroup = [
+ {
+ name: 'avi',
+ version: '2.1'
+ },
+ {
+ name: 'avi',
+ version: '2.0'
+ }
+];
+
+describe('GeneralComponent', () => {
+ it('should sort vfcmt by A to Z', () => {
+ const sorted = groupingData(data);
+ const vfcmtList = sortBy(Object.keys(sorted), name => name);
+ expect(vfcmtList).toEqual(sortedMatchVfcmtList);
+ });
+
+ it('should group vfcmt by name', () => {
+ const sorted = groupingData(data);
+ expect(Object.keys(sorted)).toEqual(['avi', 'stone', 'vosk', 'liav']);
+ });
+
+ it('should version array be sorted in group', () => {
+ const sorted = groupingData(data);
+ expect(Object.values(sorted)[0]).toEqual(sortedVersionInGroup);
+ });
+});
diff --git a/public/src/app/general/general.component.ts b/public/src/app/general/general.component.ts
new file mode 100644
index 0000000..422d834
--- /dev/null
+++ b/public/src/app/general/general.component.ts
@@ -0,0 +1,323 @@
+import {
+ Component,
+ OnInit,
+ ViewChild,
+ ViewEncapsulation,
+ Output,
+ EventEmitter
+} from '@angular/core';
+import { RestApiService } from '../api/rest-api.service';
+import { ActivatedRoute } from '@angular/router';
+import { Store } from '../store/store';
+import { NgForm } from '@angular/forms';
+import { forkJoin } from 'rxjs/observable/forkJoin';
+import {
+ pipe,
+ groupBy,
+ map,
+ sort,
+ descend,
+ ascend,
+ prop,
+ find,
+ propEq,
+ findIndex
+} from 'ramda';
+import { sortBy, forEach } from 'lodash';
+import { ToastrService } from 'ngx-toastr';
+
+export const groupingData = pipe(
+ groupBy(prop('name')),
+ map(sort(descend(prop('version'))))
+);
+
+@Component({
+ selector: 'app-general',
+ encapsulation: ViewEncapsulation.None,
+ templateUrl: './general.component.html',
+ styleUrls: ['./general.component.scss']
+})
+export class GeneralComponent implements OnInit {
+ newVfcmt = {
+ name: null,
+ description: null,
+ template: null,
+ flowType: null,
+ vfni: null,
+ isCloneVFCMT: false,
+ isUpdateFlowType: false
+ };
+ isLatestVersion = true;
+ vfniList = [];
+ templates = [];
+ serviceUUID: string;
+ vfcmts = new Array();
+ versions = new Array();
+ result = new Array();
+ flowTypes = new Array();
+ selectedVfcmt;
+ selectedVersion = null;
+ importCompleted = false;
+ disableName = false;
+ disableDescription = false;
+ disableFlowType = false;
+ disableVnfiList = false;
+ @Output() updateCdumpEv = new EventEmitter<string>();
+ @ViewChild('generalForm') generalForm;
+ // list = [
+ // { source: 'node1dsvsdsvd', target: 'node2' },
+ // { source: 'node3', target: 'node4' },
+ // { source: 'node5', target: 'nodedsvsds6' },
+ // { source: 'node7', target: 'node8' }
+ // ];
+ list = [];
+
+ constructor(
+ private restApi: RestApiService,
+ public store: Store,
+ private toastr: ToastrService,
+ private route: ActivatedRoute
+ ) {
+ console.log('route mcid: ', this.route.snapshot.params.mcid);
+ if (
+ this.route.snapshot.params.mcid === 'import' ||
+ this.route.snapshot.params.mcid === 'new'
+ ) {
+ this.store.generalflow = this.route.snapshot.params.mcid;
+ } else {
+ this.store.generalflow = 'edit';
+ this.store.mcUuid = this.route.snapshot.params.mcid;
+ }
+ this.serviceUUID = this.route.snapshot.params.uuid;
+ }
+
+ onChangeTemplate(template) {
+ console.log('flow template', template);
+ }
+ onChangeVfni(vfni) {
+ console.log('vfni', vfni);
+ }
+ vfcmtChange(vfcmtName) {
+ vfcmtName !== undefined
+ ? (this.versions = this.result[vfcmtName])
+ : ((this.versions = []), this.restForm());
+ this.store.isEditMode = true;
+ this.selectedVersion = null;
+ }
+ versionChange(version) {
+ const versionIndex = findIndex(propEq('version', version))(this.versions);
+ this.isLatestVersion = versionIndex === 0 ? true : false;
+ const selectedVfcmtByVersion = find(
+ propEq('version', version),
+ this.result[this.selectedVfcmt]
+ );
+ this.newVfcmt.template = selectedVfcmtByVersion.uuid;
+ this.restApi.getVfcmtReferenceData(selectedVfcmtByVersion.uuid).subscribe(
+ success => {
+ this.store.loader = false;
+ console.log('vfcmt ref data:', success);
+ this.store.isEditMode = false;
+ this.getServiceRef(success);
+ },
+ error => {
+ this.notifyError(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ }
+ private notifyError(error: any) {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ }
+
+ ngOnInit() {
+ if (this.store.generalflow === 'edit') {
+ this.store.loader = true;
+ this.restApi
+ .getCompositionMonitoringComponent(this.store.mcUuid)
+ .subscribe(
+ response => {
+ this.newVfcmt = response.vfcmt;
+ this.flowTypes.push(this.newVfcmt.flowType);
+ this.newVfcmt.vfni = this.store.vfiName;
+ this.vfniList.push({ resourceInstanceName: this.newVfcmt.vfni });
+ // this.store.cdump = response.cdump;
+ this.updateCdumpEv.next(response.cdump);
+ this.store.isEditMode = true;
+ this.store.loader = false;
+ },
+ error => {
+ this.notifyError(error);
+ }
+ );
+ } else if (this.store.generalflow === 'import') {
+ this.store.loader = true;
+ this.store.isEditMode = true;
+ this.restApi
+ .getVfcmtsForMigration({
+ contextType: this.route.snapshot.params.contextType,
+ uuid: this.route.snapshot.params.uuid,
+ version: this.route.snapshot.params.version
+ })
+ .subscribe(
+ success => {
+ this.store.loader = false;
+ this.result = groupingData(success);
+ this.vfcmts = sortBy(Object.keys(this.result), name => name);
+ },
+ error => {
+ this.notifyError(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ } else if (this.route.snapshot.params.mcid === 'new') {
+ // get template data for ddl
+ const template$ = this.restApi.getTemplateResources();
+ // get service vfi list for ddl '08ff91f1-9b57-4918-998b-4d2c98832815'
+ const vfniList$ = this.restApi.getServiceInstances(this.serviceUUID);
+ this.store.loader = true;
+ forkJoin(template$, vfniList$).subscribe(
+ success => {
+ console.log('all', success);
+ this.templates = success[0];
+ this.vfniList = success[1].resources;
+ },
+ error => {
+ this.notifyError(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ }
+ }
+
+ private restForm() {
+ this.newVfcmt = {
+ name: null,
+ description: null,
+ template: null,
+ flowType: null,
+ vfni: null,
+ isCloneVFCMT: false,
+ isUpdateFlowType: false
+ };
+ const controls = this.generalForm.controls;
+ forEach(controls, control => {
+ control.markAsUntouched();
+ });
+ }
+
+ private getServiceRef(data) {
+ if (data.flowType !== undefined) {
+ if (data.serviceUuid === this.serviceUUID) {
+ this.newVfcmt.name = data.name;
+ this.newVfcmt.description = data.description;
+ this.disableName = true;
+ this.disableDescription = true;
+ this.setFlowType(data); // true
+ this.setVfni(data);
+ this.newVfcmt.isCloneVFCMT = false;
+ } else {
+ this.isCloneVfcmtToast();
+ this.setFlowType(data); // true
+ this.setVfni(data);
+ this.newVfcmt.isCloneVFCMT = true;
+ }
+ } else {
+ if (data.serviceUuid === this.serviceUUID && this.isLatestVersion) {
+ this.newVfcmt.name = data.name;
+ this.newVfcmt.description = data.description;
+ this.disableName = true;
+ this.disableDescription = true;
+ this.newVfcmt.isCloneVFCMT = false;
+ this.setFlowType(data); // true
+ this.setVfni(data);
+ } else {
+ this.isCloneVfcmtToast();
+ this.setFlowType(data); // true
+ this.setVfni(data);
+ this.newVfcmt.isCloneVFCMT = true;
+ }
+ }
+ }
+
+ private isCloneVfcmtToast() {
+ this.toastr.warning(
+ `<h3>The monitoring configuration is copied.</h3>
+ <div>
+ The selected VFCMT is assigned to a different
+ </div>
+ <div>
+ service or has a newer version.
+ </div>
+ `,
+ '',
+ {
+ enableHtml: true,
+ // disableTimeOut: true
+ timeOut: 10000
+ }
+ );
+ }
+
+ private setVfni(data: any) {
+ if (data.serviceUuid !== this.serviceUUID) {
+ this.getVfniList();
+ this.disableVnfiList = false;
+ } else {
+ this.disableVnfiList = true;
+ this.vfniList.push({ resourceInstanceName: data.vfiName });
+ this.newVfcmt.vfni = data.vfiName;
+ }
+ }
+
+ private setFlowType(data: any) {
+ if (data.flowType === undefined) {
+ this.newVfcmt.isUpdateFlowType = true;
+ this.disableFlowType = false;
+ this.getFlowTypeList();
+ } else {
+ this.newVfcmt.isUpdateFlowType = false;
+ this.disableFlowType = true;
+ this.flowTypes.push(data.flowType);
+ this.newVfcmt.flowType = data.flowType;
+ }
+ }
+
+ private getFlowTypeList() {
+ this.restApi.getFlowType().subscribe(
+ success => {
+ console.log('flow types', success.flowTypes);
+ this.flowTypes = success.flowTypes;
+ },
+ error => {
+ this.notifyError(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ }
+ private getVfniList() {
+ this.restApi.getServiceInstances(this.serviceUUID).subscribe(
+ success => {
+ console.log('vfni List', success);
+ this.vfniList = success.resources;
+ },
+ error => {
+ this.notifyError(error);
+ return null;
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ }
+}
diff --git a/public/src/app/home/home.component.html b/public/src/app/home/home.component.html
new file mode 100644
index 0000000..90e82d3
--- /dev/null
+++ b/public/src/app/home/home.component.html
@@ -0,0 +1,106 @@
+<div class="container">
+ <div style="display: flex;
+ justify-content: space-between;">
+ <div style="font-size: 1.7em; display: flex; align-items: center;">Monitoring</div>
+
+ <div style="display: flex;">
+ <button mat-icon-button [disabled]="checkCanCreate()" (click)="importScreen()">
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'download' | feather:22"></span>
+ </button>
+
+ <button mat-raised-button color="primary" (click)="createScreen()" data-tests-id="btn-create-mc" [disabled]="checkCanCreate()">
+ Create New MC
+ </button>
+ </div>
+ </div>
+
+ <div *ngIf="showTable===true; then thenBlock else elseBlock"></div>
+
+ <ng-template #thenBlock>
+ <!-- Table -->
+ <div class="table-wrapper">
+ <div *ngIf="unavailableMonitoringComponents.length > 0" data-tests-id="unavailableArea" style="color: white; background: red; padding: 1rem; border-radius: 5px; font-weight: bold; margin: 1em 0;">
+ <div *ngFor="let item of unavailableMonitoringComponents">
+ {{item.uuid}}
+ </div>
+ </div>
+ <table class="mcTable">
+ <thead>
+ <tr data-tests-id="monitoringComponentTableHeaders">
+ <th>Monitoring Configuration</th>
+ <th>VNFI Name</th>
+ <th style="width:90px;">Version</th>
+ <th style="width:140px;">Status</th>
+ <th style="width:140px;">Last Updated by</th>
+ <th style="width:96px;">Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let item of monitoringComponents; let i = index" on-mouseleave="hoveredIndex=null" (click)="onSelect(i)" [class.active]="i == selectedLine"
+ data-tests-id="monitoringComponentTableItems" on-mouseover="hoveredIndex=i">
+ <td color="blue">
+ <div [hidden]="checkHoverCondition(item)" data-tests-id="tableItemsMonitoringConfiguration" class="table-Monitoring-Component" (click)="editItem(item)">
+ {{item.name}}
+ </div>
+ </td>
+ <td>
+ <span pTooltip="{{item.vfiName}}" tooltipPosition="bottom" style="padding:5px;">{{item.vfiName}}</span>
+ </td>
+ <td style="width:90px;">{{item.version}}</td>
+ <td style="width:140px;">{{item.status}}</td>
+ <td style="width:140px;">{{item.lastUpdaterUserId}}</td>
+ <td style="width:80px;">
+ <div *ngIf="i==hoveredIndex" [hidden]="checkHoverCondition(item)">
+ <button mat-icon-button data-tests-id="tableItemsButtonDelete" (click)="deleteItem(item)" 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>
+ </div>
+ <div *ngIf="i==hoveredIndex" [hidden]="!checkHoverCondition(item)">
+ <button 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>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </ng-template>
+
+ <ng-template #elseBlock>
+ <div style="display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ flex:1;">
+ <div style="font-size: 1.5em;">
+ Monitoring Configuration does not Exist
+ </div>
+ <div style="padding: 0.5em; padding-top: 1em;" data-tests-id="new-monitoring-title">
+ A Monitoring Configuration (MC) was not yet created
+ </div>
+ <div>
+ 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()">
+ <span [innerHTML]="'plus' | feather:24"></span>
+ </button>
+ <span data-tests-id="btn-span-create-mc" style="margin-top: 1rem; font-size: 1.2em; font-weight: 400;" [style.color]="checkCanCreate() ? '#ebebe4' : '#009FDB'">Add First MC</span>
+ </div>
+ </div>
+ </ng-template>
+</div>
+
diff --git a/public/src/app/home/home.component.scss b/public/src/app/home/home.component.scss
new file mode 100644
index 0000000..583705f
--- /dev/null
+++ b/public/src/app/home/home.component.scss
@@ -0,0 +1,110 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 0.5em;
+ margin-left: 15px;
+ margin-right: 15px;
+ .wrapper-btn-add-mc {
+ margin-top: 3em;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+}
+
+.table-Monitoring-Component {
+ &:hover {
+ color: #009fdb;
+ text-decoration: underline;
+ cursor: pointer;
+ }
+}
+
+.table-wrapper {
+ display: flex;
+ justify-content: center;
+ flex: 1;
+ margin-bottom: 2em;
+ flex-direction: column;
+ display: block;
+}
+
+table.mcTable {
+ display: flex;
+ flex-flow: column;
+ height: calc(100vh - 150px);
+ width: 100%;
+ background-color: #ffffff;
+ color: #5a5a5a;
+}
+table.mcTable thead {
+ /* head takes the height it requires,
+ and it's not scaled when table.mcTable is resized */
+ flex: 0 0 auto;
+ // width: calc(100% - 17px);
+ width: 100%;
+}
+table.mcTable tbody {
+ /* body takes all the remaining available space */
+ flex: 1 1 auto;
+ display: block;
+ overflow-y: scroll;
+}
+table.mcTable tbody tr {
+ width: 100%;
+}
+
+table.mcTable thead,
+table.mcTable tbody tr {
+ display: table;
+ table-layout: fixed;
+}
+
+table.mcTable {
+ border-collapse: collapse;
+ border-spacing: 0px;
+}
+
+table.mcTable tr.active td {
+ background-color: #e6f6fb !important;
+ color: #5a5a5a;
+}
+
+table.mcTable th {
+ background: #f4f4f4;
+ color: #191919;
+ text-align: left;
+}
+
+table.mcTable tr {
+ &:hover {
+ background-color: #f8f8f8;
+ color: #5a5a5a;
+ }
+}
+
+table.mcTable table,
+table.mcTable th,
+table.mcTable td {
+ padding: 5px 10px;
+ border: 0.5px solid #d2d2d2;
+ border-bottom: none;
+ height: 40px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+table.mcTable tr:last-child {
+ border-bottom: 0.5px solid #d2d2d2;
+}
+
+/deep/ .ui-tooltip .ui-tooltip-text {
+ font-size: 0.8em;
+ padding: 0.7em;
+}
+
+/deep/ .ui-tooltip {
+ max-width: 400px;
+}
diff --git a/public/src/app/home/home.component.ts b/public/src/app/home/home.component.ts
new file mode 100644
index 0000000..1c538c0
--- /dev/null
+++ b/public/src/app/home/home.component.ts
@@ -0,0 +1,188 @@
+import { Component } from '@angular/core';
+import { Store } from '../store/store';
+import { HostService } from '../host/host.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { RestApiService } from '../api/rest-api.service';
+import { NgIf } from '@angular/common';
+import { ConfirmPopupComponent } from '../rule-engine/confirm-popup/confirm-popup.component';
+import { MatDialog } from '@angular/material';
+import { ToastrService } from 'ngx-toastr';
+import { ChangeDetectorRef } from '@angular/core';
+
+@Component({
+ selector: 'app-home',
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.scss']
+})
+export class HomeComponent {
+ linkToMain: string;
+ currentUserId: string;
+ showTable = true;
+ selectedLine;
+ monitoringComponents = new Array();
+ unavailableMonitoringComponents = new Array();
+ hoveredIndex = null;
+ dialogRef;
+
+ constructor(
+ private activeRoute: ActivatedRoute,
+ private route: Router,
+ private _restApi: RestApiService,
+ private dialog: MatDialog,
+ public store: Store,
+ private toastr: ToastrService,
+ private changeDetectorRef: ChangeDetectorRef
+ ) {
+ this.store.loader = true;
+ this.activeRoute.queryParams.subscribe(params => {
+ console.log('params: %o', params);
+ this.store.sdcParmas = params;
+ this.linkToMain = `/main/${params.contextType}/${params.uuid}/${
+ params.version
+ }/`;
+ this._restApi.getMonitoringComponents(params).subscribe(
+ response => {
+ console.log('response: ', response);
+ if (response.hasOwnProperty('monitoringComponents')) {
+ this.monitoringComponents = response.monitoringComponents;
+ }
+ if (response.hasOwnProperty('unavailable')) {
+ this.unavailableMonitoringComponents = response.unavailable;
+ }
+ this.showTable = this.monitoringComponents.length > 0;
+ this.store.loader = false;
+ },
+ response => {
+ this.showTable = false;
+ this.store.loader = false;
+ console.log('ERROR: ', response);
+ }
+ );
+ HostService.disableLoader();
+ });
+ }
+
+ createScreen() {
+ this.store.isEditMode = false;
+ this.route.navigate([this.linkToMain + 'new']);
+ }
+
+ importScreen() {
+ this.store.isEditMode = false;
+ this.route.navigate([this.linkToMain + 'import']);
+ }
+
+ checkCanCreate() {
+ if (
+ JSON.parse(this.store.sdcParmas.isOwner) &&
+ this.store.sdcParmas.lifecycleState === 'NOT_CERTIFIED_CHECKOUT'
+ ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ checkHoverCondition(item: any): boolean {
+ if (
+ this.store.sdcParmas.userId === item.lastUpdaterUserId &&
+ this.store.sdcParmas.lifecycleState === 'NOT_CERTIFIED_CHECKOUT'
+ ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ editItem(item: any): void {
+ this.store.vfiName = item.vfiName;
+ this.route.navigate([this.linkToMain + '/' + item.uuid]);
+ }
+
+ onSelect(item: any): void {
+ this.selectedLine = item;
+ 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'
+ );
+ }
+
+ deleteItem(item: any): void {
+ let deleteRow = this.hoveredIndex;
+ this.dialogRef = this.dialog.open(ConfirmPopupComponent, {
+ panelClass: 'my-confrim-dialog',
+ disableClose: true
+ });
+ this.dialogRef.afterClosed().subscribe(result => {
+ // if the user want to delete
+ if (result) {
+ if (item.status == 'submitted') {
+ this._restApi
+ .deleteMonitoringComponentWithBlueprint(
+ this.store.sdcParmas,
+ item.name,
+ item.uuid,
+ item.vfiName
+ )
+ .subscribe(
+ response => {
+ this.itemDeletedRemoveAndNotify(deleteRow);
+ },
+ error => {
+ if (error.messageId === 'SVC6118') {
+ this.monitoringComponents.splice(deleteRow, 1);
+ this.changeDetectorRef.detectChanges();
+ }
+ const errorMsg = Object.values(error.requestError) as any;
+ this.toastr.error('', errorMsg[0]);
+ }
+ );
+ } else {
+ this._restApi
+ .deleteMonitoringComponent(
+ this.store.sdcParmas,
+ item.uuid,
+ item.vfiName
+ )
+ .subscribe(
+ response => {
+ this.itemDeletedRemoveAndNotify(deleteRow);
+ },
+ error => {
+ const errorMsg = Object.values(error.requestError) as any;
+ this.toastr.error('', errorMsg[0]);
+ }
+ );
+ }
+ }
+ });
+ }
+
+ itemDeletedRemoveAndNotify(deletedRow: number): void {
+ this.monitoringComponents.splice(deletedRow, 1);
+ this.changeDetectorRef.detectChanges();
+ this.toastr.success(
+ '',
+ 'Monitoring Configuration was successfully deleted'
+ );
+ }
+
+ // convertFile(fileInput: any) {
+ // // read file from input
+ // const fileReaded = fileInput.target.files[0];
+ // Papa.parse(fileReaded, {
+ // complete: function(results) {
+ // console.log('Finished:', results.data);
+ // }
+ // });
+ // }
+}
diff --git a/public/src/app/host/host.service.ts b/public/src/app/host/host.service.ts
new file mode 100644
index 0000000..31c4746
--- /dev/null
+++ b/public/src/app/host/host.service.ts
@@ -0,0 +1,44 @@
+import { Injectable } from '@angular/core';
+
+interface HostParams {
+ readonly userId: string;
+ readonly contextType: string;
+ readonly vfcmtUuid: string;
+ readonly lifecycleState: string;
+ readonly isOwner: string;
+}
+
+@Injectable()
+export class HostService {
+ /* Public Members */
+ public static getParams(): HostParams {
+ return this.getQueryParamsObj(window.location.hash) as HostParams;
+ }
+
+ public static disableLoader(): void {
+ this.postMessage('READY', null);
+ }
+
+ /* Private Methods */
+ private static postMessage(eventName: string, data: string): void {
+ window.parent.postMessage(
+ {
+ type: eventName,
+ data: data
+ },
+ '*'
+ );
+ }
+
+ private static getQueryParamsObj(query: string): object {
+ return query
+ .substring(7) // removes '?' that always appears as prefix to the query-string
+ .split('&') // splits query-string to "key=value" strings
+ .map(p => p.split('=')) // splits each "key=value" string to [key,value] array
+ .reduce((res, p) => {
+ // converts to a dictionary (object) of params
+ res[p[0]] = p[1];
+ return res;
+ }, {});
+ }
+}
diff --git a/public/src/app/loader/loader.component.html b/public/src/app/loader/loader.component.html
new file mode 100644
index 0000000..55a8f4c
--- /dev/null
+++ b/public/src/app/loader/loader.component.html
@@ -0,0 +1,4 @@
+<!-- loader -->
+<div class="tlv-loader-block">
+</div>
+<div class="tlv-loader large" style="z-index: 10002;" data-tests-id="loader"></div>
diff --git a/public/src/app/loader/loader.component.scss b/public/src/app/loader/loader.component.scss
new file mode 100644
index 0000000..621adba
--- /dev/null
+++ b/public/src/app/loader/loader.component.scss
@@ -0,0 +1,152 @@
+.tlv-loader-block {
+ background-color: #ffffff;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9999;
+ opacity: 0.5;
+}
+
+.tlv-loader {
+ height: 63px;
+ width: 63px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+}
+
+.tlv-loader.small {
+ -webkit-transform: scale(0.26);
+ transform: scale(0.26);
+ margin-top: -36.5px;
+ margin-left: -36.5px;
+}
+
+.tlv-loader.medium {
+ -webkit-transform: scale(0.5);
+ transform: scale(0.5);
+ margin-top: -26.5px;
+ margin-left: -26.5px;
+}
+
+.tlv-loader.large {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ margin-top: -10.5px;
+ margin-left: -10.5px;
+}
+
+.tlv-loader::before {
+ background-color: #eaeaea;
+ border-radius: 50%;
+ box-shadow: 21px 21px 0px 0px #eaeaea, 0px 42px 0px 0px #eaeaea,
+ -21px 21px 0px 0px #eaeaea;
+ content: '';
+ display: block;
+ height: 21px;
+ width: 21px;
+ position: absolute;
+ left: 50%;
+ margin-left: -10.5px;
+}
+
+.tlv-loader::after {
+ border-radius: 50%;
+ content: '';
+ display: block;
+ position: absolute;
+ height: 21px;
+ width: 21px;
+ -webkit-animation: dot-move 4.5s infinite ease-in;
+ animation: dot-move 4.5s infinite ease-in;
+}
+
+@keyframes dot-move {
+ 0% {
+ background-color: #3bb2df;
+ left: 21px;
+ top: 0;
+ }
+ 6.25% {
+ background-color: #3bb2df;
+ left: 42px;
+ top: 21px;
+ }
+ 12.5% {
+ background-color: #3bb2df;
+ left: 21px;
+ top: 42px;
+ }
+ 18.75% {
+ background-color: #3bb2df;
+ left: 0;
+ top: 21px;
+ }
+ 25% {
+ background-color: #ffb81c;
+ left: 21px;
+ top: 0;
+ }
+ 31.25% {
+ background-color: #ffb81c;
+ left: 42px;
+ top: 21px;
+ }
+ 37.5% {
+ background-color: #ffb81c;
+ left: 21px;
+ top: 42px;
+ }
+ 43.75% {
+ background-color: #ffb81c;
+ left: 0;
+ top: 21px;
+ }
+ 50% {
+ background-color: #caa2dd;
+ left: 21px;
+ top: 0;
+ }
+ 56.25% {
+ background-color: #caa2dd;
+ left: 42px;
+ top: 21px;
+ }
+ 62.5% {
+ background-color: #caa2dd;
+ left: 21px;
+ top: 42px;
+ }
+ 68.75% {
+ background-color: #caa2dd;
+ left: 0;
+ top: 21px;
+ }
+ 75% {
+ background-color: #d9e51c;
+ left: 21px;
+ top: 0;
+ }
+ 81.25% {
+ background-color: #d9e51c;
+ left: 42px;
+ top: 21px;
+ }
+ 87.5% {
+ background-color: #d9e51c;
+ left: 21px;
+ top: 42px;
+ }
+ 93.75% {
+ background-color: #d9e51c;
+ left: 0;
+ top: 21px;
+ }
+ 100% {
+ background-color: #3bb2df;
+ left: 21px;
+ top: 0;
+ }
+}
diff --git a/public/src/app/loader/loader.component.spec.ts b/public/src/app/loader/loader.component.spec.ts
new file mode 100644
index 0000000..7c82913
--- /dev/null
+++ b/public/src/app/loader/loader.component.spec.ts
@@ -0,0 +1,26 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoaderComponent } from './loader.component';
+
+describe('LoaderComponent', () => {
+ let component: LoaderComponent;
+ let fixture: ComponentFixture<LoaderComponent>;
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ declarations: [LoaderComponent]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoaderComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/public/src/app/loader/loader.component.ts b/public/src/app/loader/loader.component.ts
new file mode 100644
index 0000000..89403b2
--- /dev/null
+++ b/public/src/app/loader/loader.component.ts
@@ -0,0 +1,12 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-loader',
+ templateUrl: './loader.component.html',
+ styleUrls: ['./loader.component.scss']
+})
+export class LoaderComponent implements OnInit {
+ constructor() {}
+
+ ngOnInit() {}
+}
diff --git a/public/src/app/main/main.component.html b/public/src/app/main/main.component.html
new file mode 100644
index 0000000..d54b27b
--- /dev/null
+++ b/public/src/app/main/main.component.html
@@ -0,0 +1,62 @@
+<div class="container">
+
+ <div style="padding: .25em; display: flex;
+ justify-content: space-between;
+ align-items: flex-end;">
+ <div>
+ <a (click)="goBack()" data-tests-id="btn-back-home" style="display: flex; cursor: pointer;text-decoration: none; color: #009fdb;">
+ <mat-icon fontSet="fontawesome" fontIcon="fa-angle-left" style="height: 17px; width: 12px; font-size: 17px;"></mat-icon>
+ <span style="display: flex; align-items: center;">Back to Monitoring</span>
+ </a>
+ <div style="margin:10px 0;" data-tests-id="new-monitorying-titie">
+ <span style="font-size: 2em;" *ngIf='store.generalflow === "new"'>
+ New
+ </span>
+ <span style="font-size: 2em;" *ngIf='store.generalflow === "import"'>
+ Import
+ </span>
+ <span style="font-size: 2em;">
+ Monitoring Configuration
+ </span>
+ </div>
+ </div>
+
+ <div>
+ <div *ngIf='store.generalflow === "new" || store.generalflow === "edit"'>
+ <button *ngIf="!this.store.isEditMode" mat-raised-button color="primary" [disabled]="this.generalComponent.generalForm.invalid"
+ data-tests-id="createMonitoring" (click)="createMC(this.generalComponent.generalForm.value)">Create</button>
+
+ <div *ngIf="this.store.isEditMode" style="display: flex;">
+ <button mat-icon-button (click)="saveCDUMP()" [disabled]="!store.cdumpIsDirty">
+ <span style="width: 100%;
+ height: 100%;
+ padding-right: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'save' | feather:22"></span>
+ </button>
+ <button mat-raised-button color="primary" (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"
+ data-tests-id="importMonitoring">Import</button>
+ </div>
+ </div>
+ </div>
+
+ <div style="position: relative; flex:1;">
+
+ <p-tabView (onChange)="handleChange($event)" data-tests-id="tabs">
+ <p-tabPanel header="General">
+ <div>
+ <app-general (updateCdumpEv)="updateCdump($event)"></app-general>
+ </div>
+ </p-tabPanel>
+ <p-tabPanel *ngFor="let item of nodes" [header]="item.name">
+ <app-rule-frame [tabName]="item.name"></app-rule-frame>
+ </p-tabPanel>
+ </p-tabView>
+
+ </div>
+</div>
diff --git a/public/src/app/main/main.component.scss b/public/src/app/main/main.component.scss
new file mode 100644
index 0000000..402a56a
--- /dev/null
+++ b/public/src/app/main/main.component.scss
@@ -0,0 +1,33 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ // height: 100%;
+ margin: 1em;
+}
+
+// overhide
+.ui-tabview .ui-tabview-panel {
+ border: 1px solid #d9d9d9;
+ padding: 0;
+ margin-left: 0.2em;
+ height: calc(100vh - 150px);
+ overflow: auto;
+}
+
+.ui-tabview .ui-tabview-nav li {
+ margin: 0;
+}
+
+.ui-tabview .ui-tabview-nav li.ui-tabview-selected {
+ color: #009fdb;
+ border-top: 4px solid #009fdb;
+ border-bottom: none;
+}
+
+.ui-tabview-title {
+ font-size: 14px;
+}
+
+.ui-tabview .ui-tabview-nav li.ui-tabview-selected .ui-tabview-title {
+ color: #009fdb;
+}
diff --git a/public/src/app/main/main.component.ts b/public/src/app/main/main.component.ts
new file mode 100644
index 0000000..fdbb077
--- /dev/null
+++ b/public/src/app/main/main.component.ts
@@ -0,0 +1,228 @@
+import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+import { RestApiService } from '../api/rest-api.service';
+import { Store } from '../store/store';
+import { RuleFrameComponent } from '../rule-frame/rule-frame.component';
+import { GeneralComponent } from '../general/general.component';
+import { ToastrService } from 'ngx-toastr';
+import { forkJoin } from 'rxjs/observable/forkJoin';
+
+@Component({
+ selector: 'app-main',
+ encapsulation: ViewEncapsulation.None,
+ templateUrl: './main.component.html',
+ styleUrls: ['./main.component.scss']
+})
+export class MainComponent {
+ cdump;
+ nodes = [];
+ @ViewChild(GeneralComponent) generalComponent: GeneralComponent;
+ // @ViewChildren(RuleFrameComponent) ruleFrameRef: QueryList<RuleFrameComponent>;
+
+ constructor(
+ private route: ActivatedRoute,
+ private restApi: RestApiService,
+ private toastr: ToastrService,
+ public store: Store,
+ private location: Location
+ ) {
+ this.route.snapshot.params.mcid === 'import'
+ ? (this.store.generalflow = 'import')
+ : (this.store.generalflow = 'new');
+ }
+
+ goBack() {
+ this.location.back();
+ }
+
+ createMC(params) {
+ console.log('newVfcmt: %o', params);
+ this.store.loader = true;
+ this.restApi
+ .createNewVFCMT({
+ name: params.name,
+ description: params.description,
+ templateUuid: params.template,
+ vfiName: params.serviceAttached,
+ serviceUuid: this.route.snapshot.params.uuid,
+ contextType: this.route.snapshot.params.contextType,
+ flowType: 'default'
+ })
+ .subscribe(
+ success => {
+ console.log(success);
+ this.store.mcUuid = success.vfcmt.uuid;
+ console.log(this.cleanProperty(success));
+ this.store.cdump = success.cdump;
+ this.nodes = this.store.cdump.nodes;
+ this.store.setTabsProperties(this.nodes);
+ this.setDataFromMapToRuleEngine(success.cdump);
+ this.store.loader = false;
+ this.store.isEditMode = true;
+ },
+ error => {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ }
+ );
+ }
+
+ updateCdump(cdump) {
+ this.store.cdump = cdump;
+ this.nodes = this.store.cdump.nodes;
+ this.store.setTabsProperties(this.nodes);
+ this.setDataFromMapToRuleEngine(cdump);
+ }
+
+ importMC(params) {
+ console.log('importVfcmt: %o', params);
+ this.generalComponent.importCompleted = true;
+ this.store.loader = true;
+ this.restApi
+ .importVFCMT({
+ name: params.name,
+ description: params.description,
+ templateUuid: params.template,
+ vfiName: params.vfni,
+ serviceUuid: this.route.snapshot.params.uuid,
+ contextType: this.route.snapshot.params.contextType,
+ flowType: params.flowType,
+ cloneVFCMT: params.isCloneVFCMT,
+ updateFlowType: params.isUpdateFlowType
+ })
+ .subscribe(
+ success => {
+ console.log(success);
+ this.location.path();
+ // this.location.go();
+ this.store.mcUuid = success.vfcmt.uuid;
+ console.log(this.cleanProperty(success));
+ this.store.cdump = success.cdump;
+ this.nodes = this.store.cdump.nodes;
+ this.store.setTabsProperties(this.nodes);
+ this.setDataFromMapToRuleEngine(success.cdump);
+ this.store.generalflow = 'edit';
+ this.store.loader = false;
+ this.store.isEditMode = true;
+ },
+ error => {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ }
+ );
+ }
+
+ setDataFromMapToRuleEngine(cdump) {
+ this.store.tabParmasForRule = cdump.nodes
+ .filter(x => x.name.includes('map'))
+ .map(y => {
+ return {
+ name: y.name,
+ nid: y.nid
+ };
+ });
+ }
+
+ cleanProperty(response) {
+ return response.cdump.nodes.map(node => {
+ const t = node.properties.filter(item =>
+ item.hasOwnProperty('assignment')
+ );
+ node.properties = t;
+ return node;
+ });
+ }
+
+ saveCDUMP() {
+ debugger;
+ this.store.loader = true;
+ this.restApi
+ .saveMonitoringComponent({
+ contextType: this.store.sdcParmas.contextType,
+ serviceUuid: this.store.sdcParmas.uuid,
+ vfiName: this.generalComponent.newVfcmt.vfni,
+ vfcmtUuid: this.store.mcUuid,
+ flowType: this.generalComponent.newVfcmt.flowType,
+ cdump: this.store.cdump
+ })
+ .subscribe(
+ success => {
+ this.store.loader = false;
+ this.store.mcUuid = success.uuid;
+ this.toastr.success('', 'Save succeeded');
+ },
+ error => {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ },
+ () => {}
+ );
+ }
+
+ saveAndCreateBlueprint() {
+ debugger;
+ this.store.loader = true;
+ if (this.store.cdumpIsDirty) {
+ this.restApi
+ .saveMonitoringComponent({
+ contextType: this.store.sdcParmas.contextType,
+ serviceUuid: this.store.sdcParmas.uuid,
+ vfiName: this.generalComponent.newVfcmt.vfni,
+ vfcmtUuid: this.store.mcUuid,
+ cdump: this.store.cdump
+ })
+ .subscribe(
+ success => {
+ this.store.loader = false;
+ this.store.mcUuid = success.uuid;
+ this.submitBlueprint();
+ },
+ error => {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ },
+ () => {}
+ );
+ } else {
+ this.submitBlueprint();
+ }
+ }
+
+ submitBlueprint() {
+ this.store.loader = true;
+ this.restApi
+ .submitMonitoringComponent({
+ contextType: this.store.sdcParmas.contextType,
+ serviceUuid: this.store.sdcParmas.uuid,
+ vfiName: this.generalComponent.newVfcmt.vfni,
+ vfcmtUuid: this.store.mcUuid,
+ flowType: this.store.cdump.flowType
+ })
+ .subscribe(
+ success => {
+ this.store.loader = false;
+ this.toastr.success('', 'Save succeeded');
+ },
+ error => {
+ this.store.loader = false;
+ console.log(error.notes);
+ this.store.ErrorContent = Object.values(error.requestError);
+ this.store.displayErrorDialog = true;
+ },
+ () => {}
+ );
+ }
+
+ handleChange(e) {
+ this.store.setTabIndex(e.index - 1);
+ }
+}
diff --git a/public/src/app/router.animations.ts b/public/src/app/router.animations.ts
new file mode 100644
index 0000000..072c031
--- /dev/null
+++ b/public/src/app/router.animations.ts
@@ -0,0 +1,66 @@
+import {
+ trigger,
+ state,
+ animate,
+ transition,
+ style,
+ query
+} from '@angular/animations';
+
+export const fadeAnimation = trigger('fadeAnimation', [
+ transition('* => *', [
+ query(':enter', [style({ opacity: 0 })], { optional: true }),
+
+ query(
+ ':leave',
+ [style({ opacity: 1 }), animate('0.5s', style({ opacity: 0 }))],
+ { optional: true }
+ ),
+
+ query(
+ ':enter',
+ [style({ opacity: 0 }), animate('0.5s', style({ opacity: 1 }))],
+ { optional: true }
+ )
+ ])
+]);
+
+export const slideAnimation = trigger('slideAnimation', [
+ transition('* <=> *', [
+ // Initial state of new route
+ query(
+ ':enter',
+ style({
+ position: 'fixed',
+ width: '100%',
+ transform: 'translateX(-100%)'
+ }),
+ { optional: true }
+ ),
+ // move page off screen right on leave
+ // query(
+ // ':leave',
+ // animate(
+ // '500ms ease',
+ // style({
+ // position: 'fixed',
+ // width: '100%',
+ // transform: 'translateX(-100%)'
+ // })
+ // ),
+ // { optional: true }
+ // ),
+ // move page in screen from left to right
+ query(
+ ':enter',
+ animate(
+ '500ms ease',
+ style({
+ opacity: 1,
+ transform: 'translateX(0%)'
+ })
+ ),
+ { optional: true }
+ )
+ ])
+]);
diff --git a/public/src/app/rule-engine/action-list/action-list.component.html b/public/src/app/rule-engine/action-list/action-list.component.html
new file mode 100644
index 0000000..e7879b7
--- /dev/null
+++ b/public/src/app/rule-engine/action-list/action-list.component.html
@@ -0,0 +1,100 @@
+<form #actionListFrm="ngForm" class="wrapper" data-tests-id="popupRuleEditor">
+ <div class="header">
+ <div style="display: flex; justify-content: flex-end; align-items: center;">
+ <a (click)="closeDialog()" data-tests-id="btnBackRule" style="cursor: pointer;text-decoration: none; color: #009fdb;">
+ <mat-icon fontSet="fontawesome" fontIcon="fa-angle-left" style="height: 22px; width: 22px; font-size: 22px; padding-right: 20px;"></mat-icon>
+ </a>
+ <span style="font-size: 18px;">New Rule Editor</span>
+ </div>
+
+ <div style="display: flex; justify-content: flex-end; align-items: center; padding: 10px;">
+
+ <button mat-icon-button [disabled]="actions.length === 0" (click)="saveRole()" data-tests-id="btnSave">
+ <span style="width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;" [innerHTML]="'save' | feather:22"></span>
+ </button>
+
+ <button mat-raised-button [disabled]="actions.length === 0" style="height: 35px; margin-left: 20px;" color="primary" data-tests-id="btnDone"
+ (click)="saveAndDone()">
+ Done
+ </button>
+ </div>
+ </div>
+ <!-- error container -->
+ <div *ngIf="error" data-tests-id="errorList" class="error-list">
+ <div *ngFor="let item of error">
+ {{ item }}
+ </div>
+ </div>
+
+ <div class="main-content">
+ <div>
+ <div class="required" style="padding-right: 1rem; width: 100%; padding-bottom: 0.5rem;">Description</div>
+ <input type="text" [(ngModel)]="description" ngModel required name="descInput" style="padding: 5px; width: 100%;" data-tests-id="inputDescription">
+ </div>
+
+ <div style="margin: 1.5rem 0;">
+ <div class="pretty p-svg" style="margin: 1rem 0rem;">
+ <input type="checkbox" name="isCondition" data-tests-id="isCondition" [checked]="ifStatement" (change)="ifStatement = !ifStatement"
+ />
+ <div class="state">
+ <!-- svg path -->
+ <svg class="svg svg-icon" viewBox="0 0 20 20">
+ <path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z"
+ style="stroke: #009fdb; fill:#009fdb;"></path>
+ </svg>
+ <label>Conditional Action</label>
+ </div>
+ </div>
+
+ <div *ngIf="ifStatement">
+ <app-condition #condition (removeConditionCheck)="removeConditionCheck($event)" (onConditionChange)="updateCondition($event)"></app-condition>
+ </div>
+ </div>
+
+ <div>
+ <div class="required" style="padding-bottom: 0.5rem">
+ Action
+ </div>
+ <div style="display: flex;">
+ <select [(ngModel)]="selectedAction" name="selectedAction" style="height: 2rem; width: 150px; margin-right: 1rem;" data-tests-id="selectAction">
+ <option [ngValue]="null" disabled>Select Action</option>
+ <option value="copy">Copy</option>
+ <option value="concat">Concat</option>
+ <option value="map">Map</option>
+ <option value="date formatter">Date Formatter</option>
+ </select>
+
+ <div style="display: flex; align-items: center;">
+ <button mat-mini-fab color="primary" style="height: 24px; width: 24px; display:flex; justify-content: center;" (click)="addAction2list(selectedAction)"
+ data-tests-id="btnAddAction">
+ <span style="display: flex; justify-content: center; align-items: center" [innerHTML]="'plus' | feather:16"></span>
+ </button>
+ <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 10px">Add Action</span>
+ </div>
+
+ </div>
+
+ <div>
+ <ul>
+ <li *ngFor="let action of actions; let index = index" style="list-style: none; margin: 1rem 0;" (mouseleave)="hoveredIndex=-1"
+ (mouseover)="hoveredIndex=index" data-tests-id="action">
+ <div style="display:flex;">
+ <app-action #actions style="width: 100%;" [action]="action"></app-action>
+
+ <div style="height: 45px; display: flex; align-items: center;">
+ <button mat-icon-button class='button-remove' (click)="removeAction(action)" data-tests-id="deleteAction">
+ <mat-icon>delete</mat-icon>
+ </button>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+
+ </div>
+ </div>
+</form>
diff --git a/public/src/app/rule-engine/action-list/action-list.component.scss b/public/src/app/rule-engine/action-list/action-list.component.scss
new file mode 100644
index 0000000..39b9dce
--- /dev/null
+++ b/public/src/app/rule-engine/action-list/action-list.component.scss
@@ -0,0 +1,77 @@
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: #191919;
+ border-bottom: 2px solid #d2d2d2;
+ // padding: 0.4rem 1rem;
+ }
+
+ .main-content {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ flex-grow: 1;
+ // overflow-y: auto;
+ padding: 24px 10px;
+ width: 100%;
+ // height: calc(100vh - 54px);
+ }
+}
+
+.mat-fab,
+.mat-mini-fab,
+.mat-raised-button {
+ box-shadow: none;
+}
+
+.button-remove {
+ display: flex;
+ justify-content: center;
+ padding-top: 5px;
+ color: #a7a7a7;
+ &:hover {
+ color: #009fdb;
+ }
+}
+
+:host {
+ @mixin md-icon-size($size: 24px) {
+ // font-size: $size;
+ height: $size;
+ width: $size;
+ }
+
+ .material-icons.mat-icon {
+ @include md-icon-size(24px);
+ }
+ /deep/ .mat-button-wrapper {
+ padding: 0;
+ }
+ .mat-icon {
+ width: 18px;
+ height: 18px;
+ }
+}
+
+.black {
+ color: black;
+}
+.highlight {
+ color: #009fdb;
+}
+
+.error-list {
+ margin: 10px;
+ color: white;
+ background: red;
+ padding: 1rem;
+ border-radius: 5px;
+ font-weight: bold;
+}
diff --git a/public/src/app/rule-engine/action-list/action-list.component.ts b/public/src/app/rule-engine/action-list/action-list.component.ts
new file mode 100644
index 0000000..40ff46d
--- /dev/null
+++ b/public/src/app/rule-engine/action-list/action-list.component.ts
@@ -0,0 +1,290 @@
+import {
+ Component,
+ Inject,
+ ViewChildren,
+ QueryList,
+ AfterViewInit,
+ ViewChild,
+ Input
+} from '@angular/core';
+import { RuleEngineApiService } from '../api/rule-engine-api.service';
+import { Subject } from 'rxjs/Subject';
+import { v1 as uuid } from 'uuid';
+import { environment } from '../../../environments/environment';
+import { ActionComponent } from '../action/action.component';
+import { cloneDeep } from 'lodash';
+import { Store } from '../../store/store';
+import { NgForm } from '@angular/forms';
+
+@Component({
+ selector: 'app-action-list',
+ templateUrl: './action-list.component.html',
+ styleUrls: ['./action-list.component.scss']
+})
+export class ActionListComponent implements AfterViewInit {
+ error: Array<string>;
+ condition: any;
+ eventType: string;
+ version: string;
+ params;
+ selectedAction;
+ targetSource;
+ description = '';
+ actions = new Array();
+ ifStatement = false;
+ uid = '';
+ backupActionForCancel = new Array();
+ @ViewChild('actionListFrm') actionListFrm: NgForm;
+ @ViewChild('condition') conditionRef;
+ @ViewChildren('actions') actionsRef: QueryList<ActionComponent>;
+
+ constructor(private _ruleApi: RuleEngineApiService, public store: Store) {
+ this._ruleApi.editorData.subscribe(data => {
+ this.params = data.params;
+ console.log('update.. params', data.params);
+ this.targetSource = data.targetSource;
+ this.version = data.version;
+ this.eventType = data.eventType;
+ if (data.item) {
+ // edit mode set values to attributes
+ console.log('actions %o', data.item.actions);
+ this.actions = this.convertActionDataFromServer(data.item.actions);
+ this.backupActionForCancel = cloneDeep(this.actions);
+ this.condition = data.item.condition;
+ this.uid = data.item.uid;
+ this.description = data.item.description;
+ this.ifStatement = this.condition == null ? false : true;
+ } else {
+ this.actions = new Array();
+ this.backupActionForCancel = new Array();
+ this.condition = null;
+ this.uid = '';
+ this.description = '';
+ this.ifStatement = false;
+ }
+ this.selectedAction = null;
+ });
+ }
+
+ convertActionDataFromServer(actions) {
+ return actions.map(item => {
+ if (!item.hasOwnProperty('nodes')) {
+ return Object.assign({}, item, { nodes: this.targetSource });
+ }
+ });
+ }
+
+ ngAfterViewInit() {
+ // console.log(this.actionsRef.toArray());
+ if (this.condition) {
+ if (this.condition.name === 'condition') {
+ this.conditionRef.updateMode(true, this.condition);
+ } else {
+ const convertedCondition = this.convertConditionFromServer(
+ this.condition
+ );
+ this.conditionRef.updateMode(false, convertedCondition);
+ }
+ }
+ }
+
+ addAction2list(selectedAction) {
+ if (selectedAction !== null) {
+ this.actions.push({
+ id: uuid(),
+ nodes: this.targetSource,
+ from: {
+ value: '',
+ regex: '',
+ state: 'closed',
+ values: [{ value: '' }, { value: '' }]
+ },
+ actionType: this.selectedAction,
+ target: '',
+ map: {
+ values: [{ key: '', value: '' }],
+ haveDefault: false,
+ default: ''
+ },
+ dateFormatter: {
+ fromFormat: '',
+ toFormat: '',
+ fromTimezone: '',
+ toTimezone: ''
+ }
+ });
+ }
+ }
+
+ removeConditionCheck(flag) {
+ this.ifStatement = flag;
+ }
+
+ removeAction(action) {
+ this.actions = this.actions.filter(item => {
+ return item.id !== action.id;
+ });
+ }
+
+ updateCondition(data) {
+ this.condition = data;
+ }
+
+ changeRightToArrayOrString(data, toArray) {
+ data.forEach(element => {
+ if (element.name === 'operator') {
+ this.changeRightToArrayOrString(element.children, toArray);
+ }
+ if (element.name === 'condition') {
+ if (toArray) {
+ element.right = element.right.split(',');
+ } else {
+ element.right = element.right.join(',');
+ }
+ }
+ });
+ console.log(data);
+ return data;
+ }
+
+ prepareDataToSaveRule() {
+ // action array
+ console.log(this.actions);
+ const actionSetData = this.actions.map(item => {
+ return {
+ id: item.id,
+ actionType: item.actionType,
+ from: item.from,
+ target:
+ typeof item.selectedNode === 'string'
+ ? item.selectedNode
+ : typeof item.selectedNode === 'undefined'
+ ? item.target
+ : item.selectedNode.id,
+ map: item.map,
+ dateFormatter: item.dateFormatter
+ };
+ });
+ let conditionData2server = null;
+ if (this.ifStatement) {
+ if (this.conditionRef.conditionTree) {
+ // change condition right to array
+ conditionData2server = this.convertConditionToServer(
+ this.conditionRef.conditionTree
+ );
+ }
+ }
+ // data structure
+ return {
+ version: this.version,
+ eventType: this.eventType,
+ uid: this.uid,
+ description: this.description,
+ actions: actionSetData,
+ condition: this.ifStatement ? conditionData2server : null
+ };
+ }
+
+ errorHandler(error) {
+ this.store.loader = false;
+ console.log(error);
+ this.error = [];
+ if (typeof error === 'string') {
+ this.error.push(error);
+ } else {
+ console.log(error.notes);
+ const errorFromServer = Object.values(error)[0] as any;
+ if (Object.keys(error)[0] === 'serviceExceptions') {
+ this.error = errorFromServer.map(x => x.formattedErrorMessage);
+ } else {
+ this.error.push(errorFromServer.formattedErrorMessage);
+ }
+ }
+ }
+
+ saveAndDone() {
+ const data = this.prepareDataToSaveRule();
+ this.store.loader = true;
+ this._ruleApi.modifyRule(data).subscribe(
+ response => {
+ this.store.loader = false;
+ this.store.updateRuleInList(response);
+ this._ruleApi.callUpdateVersionLock();
+ this.store.isLeftVisible = true;
+ },
+ error => {
+ this.errorHandler(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ }
+
+ saveRole() {
+ const actionComp = this.actionsRef.toArray();
+ const filterInvalidActions = actionComp.filter(comp => {
+ return (
+ comp.fromInstance.fromFrm.invalid ||
+ comp.targetInstance.targetFrm.invalid ||
+ comp.actionFrm.invalid
+ );
+ });
+ if (this.actionListFrm.valid && filterInvalidActions.length === 0) {
+ const data = this.prepareDataToSaveRule();
+ this.store.loader = true;
+ this._ruleApi.modifyRule(data).subscribe(
+ response => {
+ this.store.loader = false;
+ this.store.updateRuleInList(response);
+ this._ruleApi.callUpdateVersionLock();
+ this.uid = response.uid;
+ // add toast notification
+ },
+ error => {
+ this.errorHandler(error);
+ },
+ () => {
+ this.store.loader = false;
+ }
+ );
+ } else {
+ // scroll to first invalid element
+ const elId = filterInvalidActions[0].action.id;
+ const el = document.getElementById(elId as string);
+ const label = el.children.item(0) as HTMLElement;
+ el.scrollIntoView();
+ }
+ }
+
+ public convertConditionFromServer(condition) {
+ const temp = new Array();
+ temp.push(condition);
+ const cloneCondition = cloneDeep(temp);
+ const conditionSetData = this.changeRightToArrayOrString(
+ cloneCondition,
+ false
+ );
+ console.log('condition to server:', conditionSetData);
+ return conditionSetData;
+ }
+
+ public convertConditionToServer(tree) {
+ const cloneCondition = cloneDeep(tree);
+ const conditionSetData = this.changeRightToArrayOrString(
+ cloneCondition,
+ true
+ );
+ let simpleCondition = null;
+ if (conditionSetData[0].children.length === 1) {
+ simpleCondition = conditionSetData[0].children;
+ }
+ console.log('condition to server:', conditionSetData);
+ return simpleCondition !== null ? simpleCondition[0] : conditionSetData[0];
+ }
+
+ closeDialog(): void {
+ this.actions = this.backupActionForCancel;
+ this.store.isLeftVisible = true;
+ }
+}
diff --git a/public/src/app/rule-engine/action/action.component.html b/public/src/app/rule-engine/action/action.component.html
new file mode 100644
index 0000000..b41ab82
--- /dev/null
+++ b/public/src/app/rule-engine/action/action.component.html
@@ -0,0 +1,114 @@
+<form #actionFrm="ngForm" class="conatiner" id="{{action.id}}" (mouseover)="changeStyle($event)" (mouseout)="changeStyle($event)">
+ <div>
+ <div class="center-content">
+ <!-- type info -->
+ <div class="action-info" [ngClass]="highlight">
+ {{action.actionType | uppercase}}
+ </div>
+ <!-- from component -->
+ <app-from #from style="width: 100%" [actionType]="action.actionType" (onFromChange)="updateFrom($event)"></app-from>
+ <!-- target component -->
+ <app-target #target style="width: 100%" (onTargetChange)="updateTarget($event)" [nodes]="action.nodes">
+ </app-target>
+ </div>
+
+ <!-- dateFormatter -->
+ <div *ngIf="action.actionType === 'date formatter'" style="display: flex; flex-direction: column; margin: 1em; align-items: flex-end;">
+ <div style="display: flex; margin: 0.5em 0;">
+ <div class="from">
+ <div class="from-conatiner">
+ <div style="display: flex; align-items: center;" class="label">
+ <span class="label" style="padding: 0 5px; width: 100px;">From Format</span>
+ <input class="input-text" ngModel required name="fromFormat" [(ngModel)]="action.dateFormatter.fromFormat" type="text">
+ </div>
+ </div>
+ </div>
+ <div class="from">
+ <div class="from-conatiner">
+ <div style="display: flex; align-items: center;" class="label">
+ <span class="label" style="padding: 0 5px; width: 100px;">To Format</span>
+ <input class="input-text" ngModel required name="toFormat" [(ngModel)]="action.dateFormatter.toFormat" type="text">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div style="display: flex; margin: 0.5em 0;">
+ <div class="from">
+ <div class="from-conatiner">
+ <div style="display: flex; align-items: center;" class="label">
+ <span class="label" style="padding: 0 5px; width: 100px;">From Time-zone</span>
+ <input class="input-text" ngModel required name="fromTimezone" [(ngModel)]="action.dateFormatter.fromTimezone" type="text">
+ </div>
+ </div>
+ </div>
+ <div class="from">
+ <div class="from-conatiner">
+ <div style="display: flex; align-items: center;" class="label">
+ <span class="label" style="padding: 0 5px; width: 100px;">To Time-zone</span>
+ <input class="input-text" ngModel required name="toTimezone" [(ngModel)]="action.dateFormatter.toTimezone" type="text">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Map -->
+ <div *ngIf="action.actionType === 'map'" class="map-container">
+ <!-- Default checkbox and input -->
+ <div class="default" style="display: flex; align-items: center">
+ <div class="pretty p-svg">
+ <input type="checkbox" name="defaultCheckbox" data-tests-id="defaultCheckbox" [checked]="action.map.haveDefault" (change)="changeCheckbox()"
+ />
+ <div class="state">
+ <!-- svg path -->
+ <svg class="svg svg-icon" viewBox="0 0 20 20">
+ <path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z"
+ style="stroke: #009fdb; fill:#009fdb;"></path>
+ </svg>
+ <label>Default</label>
+ </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">
+ </div>
+ </div>
+
+ <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="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;">
+ </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;">
+ </th>
+ <th style="height: 30px; display: flex; align-items: baseline;">
+ <button mat-icon-button [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" class="button-remove" (click)="removeMapRow(index)"
+ *ngIf="action.map.values.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: space-between;">
+ <div style="display: flex; align-items: center;">
+ <button mat-mini-fab color="primary" (click)="addMapRow()" style="height: 24px; width: 24px; display:flex; box-shadow: none;">
+ <mat-icon>add</mat-icon>
+ </button>
+ <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add Row</span>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</form>
diff --git a/public/src/app/rule-engine/action/action.component.scss b/public/src/app/rule-engine/action/action.component.scss
new file mode 100644
index 0000000..f903db4
--- /dev/null
+++ b/public/src/app/rule-engine/action/action.component.scss
@@ -0,0 +1,116 @@
+.conatiner {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ justify-content: space-between;
+ margin: 10px 0;
+ .black {
+ color: black;
+ }
+ .highlight {
+ color: #009fdb;
+ }
+ .center-content {
+ display: flex;
+ width: 100%;
+ .action-info {
+ background: #f2f2f2;
+ padding: 6px 12px;
+ border-radius: 5px;
+ height: 32px;
+ margin: 0 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 142px;
+ }
+ }
+ .map-container {
+ padding-left: 115px;
+ .default {
+ display: flex;
+ width: 100%;
+ margin: 1rem 0;
+ min-height: 35px;
+ .input-wrapper {
+ width: 100%;
+ display: flex;
+ .input {
+ height: 20px;
+ padding: 5px;
+ margin-left: 10px;
+ width: 100%;
+ border: 1px solid #d2d2d2;
+ }
+ }
+ }
+ .grid-container {
+ padding-bottom: 10px;
+ .layout {
+ display: grid;
+ grid-template-columns: 1fr 1fr 30px;
+ grid-gap: 1px;
+ .title {
+ background-color: #f3f3f3;
+ height: 30px;
+ padding-left: 10px;
+ display: flex;
+ align-items: center;
+ }
+ .text-wrapper {
+ height: 30px;
+ border: 1px solid #f3f3f3;
+ .input {
+ width: 97%;
+ height: 100%;
+ border: none;
+ padding: 0 5px;
+ }
+ }
+ .btn-container {
+ height: 30px;
+ display: flex;
+ align-items: baseline;
+ }
+ }
+ }
+ }
+}
+
+.from {
+ display: flex;
+ flex-direction: column;
+ padding: 0 10px;
+ .from-conatiner {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+ min-width: 350px;
+ .input-text {
+ border: none;
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+ }
+ .label {
+ border: 1px solid #d2d2d2;
+ height: 30px;
+ justify-content: flex-start;
+ align-items: center;
+ display: flex;
+ }
+}
+
+.button-remove {
+ display: flex;
+ justify-content: center;
+ color: #a7a7a7;
+ &:hover {
+ color: #009fdb;
+ }
+}
diff --git a/public/src/app/rule-engine/action/action.component.ts b/public/src/app/rule-engine/action/action.component.ts
new file mode 100644
index 0000000..9c7023f
--- /dev/null
+++ b/public/src/app/rule-engine/action/action.component.ts
@@ -0,0 +1,51 @@
+import { Component, Inject, Input, OnInit, ViewChild } from '@angular/core';
+// import { Copy } from "../model";
+import { Http, Response, Headers, RequestOptions } from '@angular/http';
+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';
+
+@Component({
+ selector: 'app-action',
+ templateUrl: './action.component.html',
+ styleUrls: ['./action.component.scss']
+})
+export class ActionComponent implements OnInit {
+ @Input() action;
+ @ViewChild('from') fromInstance;
+ @ViewChild('target') targetInstance;
+ @ViewChild('actionFrm') actionFrm: NgForm;
+ highlight = 'black';
+ hoveredIndex;
+ changeStyle($event) {
+ this.highlight = $event.type === 'mouseover' ? 'highlight' : 'black';
+ }
+ ngOnInit(): void {
+ console.log(this.action.id);
+ if (this.action.from !== '') {
+ console.log('Action %o', this.action);
+ this.fromInstance.updateMode(this.action.from);
+ this.targetInstance.updateMode(this.action);
+ }
+ }
+ updateFrom(data) {
+ this.action.from = data;
+ }
+ updateTarget(data) {
+ this.action.selectedNode = data;
+ }
+ /* map functionality */
+ addMapRow() {
+ this.action.map.values.push({ key: '', value: '' });
+ }
+ removeMapRow(index) {
+ this.action.map.values.splice(index, 1);
+ }
+
+ changeCheckbox() {
+ console.log(this.action.id);
+ return (this.action.map.haveDefault = !this.action.map.haveDefault);
+ }
+}
diff --git a/public/src/app/rule-engine/api/rule-engine-api.service.spec.ts b/public/src/app/rule-engine/api/rule-engine-api.service.spec.ts
new file mode 100644
index 0000000..e15535b
--- /dev/null
+++ b/public/src/app/rule-engine/api/rule-engine-api.service.spec.ts
@@ -0,0 +1,19 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpModule } from '@angular/http';
+import { RuleEngineApiService } from './rule-engine-api.service';
+
+describe('RuleEngineApiService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [RuleEngineApiService]
+ });
+ });
+
+ it(
+ 'should be created',
+ inject([RuleEngineApiService], (service: RuleEngineApiService) => {
+ expect(service).toBeTruthy();
+ })
+ );
+});
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
new file mode 100644
index 0000000..0d7ab5e
--- /dev/null
+++ b/public/src/app/rule-engine/api/rule-engine-api.service.ts
@@ -0,0 +1,134 @@
+import { Injectable, EventEmitter } from '@angular/core';
+import {
+ Http,
+ Response,
+ Headers,
+ RequestOptions,
+ URLSearchParams
+} from '@angular/http';
+import { Observable, Subject } from 'rxjs/Rx';
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+import { environment } from '../../../environments/environment';
+import { v4 as uuid } from 'uuid';
+
+@Injectable()
+export class RuleEngineApiService {
+ options: RequestOptions;
+ headers: Headers;
+ baseUrl: string;
+ vfcmtUuid: string;
+ dcaeCompName: string;
+ nid: string;
+ configParam: string;
+ flowType: string;
+ editorData: Subject<any> = new Subject();
+ updateVersionLock: Subject<any> = new Subject();
+
+ constructor(private http: Http) {
+ this.baseUrl = `${environment.apiBaseUrl}/rule-editor`;
+ }
+
+ setParams(params) {
+ this.headers = new Headers({
+ 'Content-Type': 'application/json',
+ USER_ID: params.userId
+ });
+ this.options = new RequestOptions({ headers: this.headers });
+ this.vfcmtUuid = params.vfcmtUuid;
+ this.dcaeCompName = params.nodeName;
+ this.nid = params.nodeId;
+ this.configParam = params.fieldName;
+ this.flowType = params.flowType;
+ }
+
+ setFieldName(name) {
+ this.configParam = name;
+ }
+
+ getMetaData() {
+ const url = `${this.baseUrl}/list-events-by-versions`;
+ this.options.headers.set('X-ECOMP-RequestID', uuid());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) =>
+ Observable.throw(error.json().requestError || 'Server error')
+ );
+ }
+
+ getSchema(version, eventType) {
+ const url = `${this.baseUrl}/definition/${version}/${eventType}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuid());
+ return this.http
+ .get(url, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) =>
+ Observable.throw(error.json().requestError || 'Server error')
+ );
+ }
+
+ getListOfRules(): Observable<any> {
+ const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${
+ this.nid
+ }/${this.configParam}`;
+ 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');
+ });
+ }
+
+ modifyRule(newRole) {
+ const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${
+ this.nid
+ }/${this.configParam}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuid());
+ return this.http
+ .post(url, newRole, this.options)
+ .map((res: Response) => res.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json().requestError || 'Server error');
+ });
+ }
+
+ deleteRule(uid) {
+ const url = `${this.baseUrl}/rule/${this.vfcmtUuid}/${this.dcaeCompName}/${
+ this.nid
+ }/${this.configParam}/${uid}`;
+ 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() {
+ const url = `${this.baseUrl}/rule/translate/${this.vfcmtUuid}/${
+ this.dcaeCompName
+ }/${this.nid}/${this.configParam}`;
+ this.options.headers.set('X-ECOMP-RequestID', uuid());
+ const params = new URLSearchParams();
+ params.append('flowType', this.flowType);
+ const options = { ...this.options, params: params };
+ return this.http
+ .get(url, options)
+ .map(response => response.json())
+ .catch((error: any) => {
+ return Observable.throw(error.json().requestError || 'Server error');
+ });
+ }
+
+ passDataToEditor(data) {
+ this.editorData.next(data);
+ }
+
+ callUpdateVersionLock() {
+ this.updateVersionLock.next();
+ }
+}
diff --git a/public/src/app/rule-engine/condition/condition.component.html b/public/src/app/rule-engine/condition/condition.component.html
new file mode 100644
index 0000000..a441f55
--- /dev/null
+++ b/public/src/app/rule-engine/condition/condition.component.html
@@ -0,0 +1,89 @@
+<tree-root #tree class="condition-tree" (initialized)="onInitialized(tree)" [nodes]="conditionTree" [options]="customTemplateStringOptions">
+ <ng-template #treeNodeTemplate let-node let-index="index">
+
+ <div>
+ <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">
+ <option value="ANY">ANY</option>
+ <option value="ALL">ALL</option>
+ </select>
+
+ <div style="display: flex; align-items: center; margin-left: 10px;">
+ of the following are true:
+ </div>
+ </div>
+
+ <div style="display: flex; margin-left: auto;">
+
+ <div style="display: flex; align-items: center; padding: 0 25px;">
+ <button mat-mini-fab color="primary" (click)="addConditional(tree, node)" style="height: 24px; width: 24px; display:flex; box-shadow: none;">
+ <mat-icon class="material-icons md-18">add</mat-icon>
+ </button>
+ <span class="btn-label">Add Condition
+ </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: 24px; width: 24px; display:flex; box-shadow: none;">
+ <mat-icon class="material-icons md-18">add</mat-icon>
+ </button>
+ <span [style.color]="node.data.level === 2 ? '#a7a7a7' : '#009fdb' " [style.cursor]="node.data.level === 2 ? 'default' : 'pointer' "
+ class="btn-label">Add Condition Group
+ </span>
+ </div>
+
+ <div style="display: flex; align-items: center; padding: 0 5px; background: #FFFFFF;">
+ <button mat-icon-button (click)="removeConditional(tree, node)" class="button-remove">
+ <mat-icon class="md-24">delete</mat-icon>
+ </button>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ <div *ngIf="node.data.name === 'condition'">
+ <div class="from-conatiner" style="height:35px; ">
+ <div style="display: flex; width:90%;">
+ <div class="label" style="width:100%">
+ <span class="label" style="padding: 0 10px; border-left: none;">
+ Input
+ </span>
+ <input class="input-text" data-tests-id="left" [(ngModel)]="node.data.left" (ngModelChange)="modelChange($event)" ngDefaultControl
+ type="text">
+ </div>
+
+ <div style="margin: 0 1rem;">
+ <select style="height: 30px;" data-tests-id="selectOperator" [(ngModel)]="node.data.operator" (ngModelChange)="modelChange($event)"
+ ngDefaultControl>
+ <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>
+ </select>
+ </div>
+
+ <div class="label" style="width:100%">
+ <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>
+ <!-- remove button -->
+ <div class="show-delete">
+ <button mat-icon-button (click)="removeConditional(tree, node)" class="button-remove">
+ <mat-icon class="md-24">delete</mat-icon>
+ </button>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ </ng-template>
+</tree-root>
diff --git a/public/src/app/rule-engine/condition/condition.component.scss b/public/src/app/rule-engine/condition/condition.component.scss
new file mode 100644
index 0000000..8c0e9e0
--- /dev/null
+++ b/public/src/app/rule-engine/condition/condition.component.scss
@@ -0,0 +1,114 @@
+.condition-tree {
+ tree-viewport {
+ overflow-x: hidden;
+ overflow-y: hidden;
+ }
+ .angular-tree-component,
+ .tree-node-leaf {
+ margin: 0;
+ padding: 0;
+ }
+ .angular-tree-component {
+ padding-left: 1em;
+ overflow-y: hidden;
+ }
+ .tree-node-leaf.container {
+ border-bottom: 0px;
+ }
+ .tree-node-leaf.empty {
+ font-style: italic;
+ color: #fafafa;
+ border-color: #fafafa;
+ }
+ .tree-node-leaf div {
+ margin: 0;
+ top: 0.5em;
+ }
+ .node-wrapper {
+ background: white;
+ }
+ .tree-children {
+ border-left: 2px solid #f2f2f2;
+ // border-top: 1px solid #f2f2f2;
+ border-bottom: 1px solid #f2f2f2;
+ }
+ tree-node-expander {
+ display: none;
+ }
+ .node-content-wrapper {
+ padding-left: 0;
+ width: 100%;
+ .show-delete {
+ opacity: 0;
+ }
+ }
+ .tree-node-content {
+ width: 100%;
+ }
+ .node-content-wrapper-active,
+ .node-content-wrapper.node-content-wrapper-active:hover,
+ .node-content-wrapper-active.node-content-wrapper-focused {
+ background: white;
+ }
+ *:focus {
+ outline: none;
+ }
+
+ .node-content-wrapper-active,
+ .node-content-wrapper-focused,
+ .node-content-wrapper:hover {
+ box-shadow: none;
+ .show-delete {
+ opacity: 1;
+ display: flex;
+ align-items: center;
+ padding: 0 5px;
+ }
+ }
+}
+
+.from-conatiner {
+ display: flex;
+ align-items: center;
+ .input-text {
+ border: none;
+ flex: 1;
+ // width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+ .label {
+ border: 1px solid #d2d2d2;
+ height: 30px;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ cursor: default;
+ }
+}
+
+.btn-label {
+ display: flex;
+ justify-content: center;
+ padding-left: 5px;
+ color: #009fdb;
+}
+
+.button-label {
+ color: #a7a7a7;
+ display: flex;
+ justify-content: center;
+ padding-left: 5px;
+ &:hover {
+ color: #009fdb;
+ }
+}
+
+.button-remove {
+ display: flex;
+ justify-content: center;
+ color: #a7a7a7;
+ &:hover {
+ color: #009fdb;
+ }
+}
diff --git a/public/src/app/rule-engine/condition/condition.component.spec.ts b/public/src/app/rule-engine/condition/condition.component.spec.ts
new file mode 100644
index 0000000..bb0d38a
--- /dev/null
+++ b/public/src/app/rule-engine/condition/condition.component.spec.ts
@@ -0,0 +1,51 @@
+import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { HttpModule } from '@angular/http';
+import {
+ MatDialogModule,
+ MatButtonModule,
+ MatIconModule,
+ MatDialogRef,
+ MAT_DIALOG_DATA
+} from '@angular/material';
+
+import { ConditionComponent } from './condition.component';
+
+describe('Condition Component', () => {
+ let component: ConditionComponent;
+ let fixture: ComponentFixture<ConditionComponent>;
+ let de: DebugElement;
+ let el: HTMLElement;
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ FormsModule,
+ HttpModule,
+ MatDialogModule,
+ MatButtonModule,
+ MatIconModule
+ ],
+ providers: [],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ declarations: [ConditionComponent]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ // create component and test fixture
+ fixture = TestBed.createComponent(ConditionComponent);
+ // get test component from the fixture
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should be created', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/public/src/app/rule-engine/condition/condition.component.ts b/public/src/app/rule-engine/condition/condition.component.ts
new file mode 100644
index 0000000..f44fbf4
--- /dev/null
+++ b/public/src/app/rule-engine/condition/condition.component.ts
@@ -0,0 +1,161 @@
+import {
+ Component,
+ ViewEncapsulation,
+ ViewChild,
+ Output,
+ EventEmitter
+} from '@angular/core';
+import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component';
+import { some } from 'lodash';
+
+@Component({
+ selector: 'app-condition',
+ templateUrl: './condition.component.html',
+ styleUrls: ['./condition.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class ConditionComponent {
+ conditionTree = [];
+ showType = false;
+ @ViewChild(TreeComponent) private tree: TreeComponent;
+ @Output() onConditionChange = new EventEmitter();
+ @Output() removeConditionCheck = new EventEmitter();
+ customTemplateStringOptions: ITreeOptions = {
+ isExpandedField: 'expanded',
+ animateExpand: true,
+ animateSpeed: 30,
+ animateAcceleration: 1.2
+ };
+
+ constructor() {
+ this.conditionTree.push({
+ name: 'operator',
+ level: 0,
+ type: 'ALL',
+ children: []
+ });
+ this.conditionTree[0].children.push({
+ name: 'condition',
+ left: '',
+ right: '',
+ operator: null,
+ level: 1
+ });
+ }
+
+ onInitialized(tree) {
+ tree.treeModel.expandAll();
+ }
+
+ updateMode(isSingle, data) {
+ if (isSingle) {
+ this.conditionTree[0].children.pop();
+ if (typeof data.right !== 'string') {
+ data.right = data.right.join(',');
+ }
+ this.conditionTree[0].children.push({
+ name: 'condition',
+ left: data.left,
+ right: data.right,
+ operator: data.operator,
+ level: 1
+ });
+ this.showType = false;
+ } else {
+ this.conditionTree = data;
+ setTimeout(() => (this.showType = true), 500);
+ }
+ this.tree.treeModel.update();
+ }
+
+ addConditional(tree, selectedNode) {
+ if (this.conditionTree[0].children.length > 0) {
+ this.showType = true;
+ }
+ const tempLevel =
+ selectedNode.data.name === 'condition'
+ ? selectedNode.data.level
+ : selectedNode.data.children[0].level;
+
+ const conditionTemplate = {
+ name: 'condition',
+ left: '',
+ right: '',
+ operator: null,
+ level: tempLevel
+ };
+ selectedNode.data.children.push(conditionTemplate);
+ tree.treeModel.update();
+ }
+
+ addConditionalGroup(tree, selectedNode) {
+ if (selectedNode.level < 3) {
+ if (this.conditionTree[0].children.length > 0) {
+ this.showType = true;
+ }
+ selectedNode.data.children.push({
+ name: 'operator',
+ level: selectedNode.data.level + 1,
+ type: 'ALL',
+ children: []
+ });
+
+ for (let i = 0; i < 2; i++) {
+ selectedNode.data.children[
+ selectedNode.data.children.length - 1
+ ].children.push({
+ name: 'condition',
+ left: '',
+ right: '',
+ operator: null,
+ level: selectedNode.data.level + 2
+ });
+ }
+ tree.treeModel.update();
+ tree.treeModel.expandAll();
+ }
+ }
+
+ removeConditional(tree, selectedNode) {
+ if (
+ (selectedNode.level === 1 && selectedNode.index === 0) ||
+ (selectedNode.parent.data.name === 'operator' &&
+ selectedNode.parent.level === 1 &&
+ selectedNode.parent.data.children.length === 1)
+ ) {
+ this.removeConditionCheck.emit(false);
+ } else if (
+ selectedNode.parent.level === 1 &&
+ selectedNode.parent.data.children.length === 2 &&
+ selectedNode.data.name === 'condition' &&
+ some(selectedNode.parent.data.children, { name: 'operator' })
+ ) {
+ return;
+ } else {
+ if (
+ selectedNode.parent.data.name === 'operator' &&
+ selectedNode.parent.level > 1
+ ) {
+ // Nested Group can delete when more then 2
+ if (selectedNode.parent.data.children.length > 2) {
+ this.deleteNodeAndUpdateTreeView(selectedNode, tree);
+ }
+ } else {
+ this.deleteNodeAndUpdateTreeView(selectedNode, tree);
+ if (this.conditionTree[0].children.length === 1) {
+ this.showType = false;
+ }
+ }
+ }
+ }
+
+ private deleteNodeAndUpdateTreeView(selectedNode: any, tree: any) {
+ selectedNode.parent.data.children.splice(selectedNode.index, 1);
+ tree.treeModel.update();
+ this.onConditionChange.emit(this.conditionTree);
+ }
+
+ modelChange(event) {
+ this.onConditionChange.emit(this.conditionTree);
+ }
+}
diff --git a/public/src/app/rule-engine/confirm-popup/confirm-popup.component.html b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.html
new file mode 100644
index 0000000..49c800a
--- /dev/null
+++ b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.html
@@ -0,0 +1,12 @@
+<div class="container" data-tests-id="delete-popup">
+ <div class="header">
+ Delete
+ </div>
+ <div class="content">
+ Are you sure you want to delete?
+ </div>
+ <div class="buttons">
+ <button mat-raised-button (click)="close(true)" data-tests-id="btnDelete" style="margin-right: 1rem;" color="primary">Delete</button>
+ <button mat-raised-button (click)="close(false)" data-tests-id="btnCancel" style="border: 1px solid #009FDB; color: #009FDB; background: #ffffff;">Cancel</button>
+ </div>
+</div>
diff --git a/public/src/app/rule-engine/confirm-popup/confirm-popup.component.scss b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.scss
new file mode 100644
index 0000000..2a826ff
--- /dev/null
+++ b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.scss
@@ -0,0 +1,20 @@
+.container {
+ display: flex;
+ justify-content: space-between;
+ margin: 0 !important;
+ border-top: solid 6px #ffb81c;
+ .header {
+ border-bottom: none;
+ }
+ .content {
+ margin: 1rem;
+ flex: 1;
+ font-weight: 400;
+ }
+ .buttons {
+ display: flex;
+ justify-content: flex-end;
+ border-top: solid 1px #eaeaea;
+ padding: 1rem;
+ }
+}
diff --git a/public/src/app/rule-engine/confirm-popup/confirm-popup.component.ts b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.ts
new file mode 100644
index 0000000..23b6cee
--- /dev/null
+++ b/public/src/app/rule-engine/confirm-popup/confirm-popup.component.ts
@@ -0,0 +1,18 @@
+import { Component, Inject } from '@angular/core';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
+
+@Component({
+ selector: 'app-confirm-popup',
+ templateUrl: './confirm-popup.component.html',
+ styleUrls: ['./confirm-popup.component.scss']
+})
+export class ConfirmPopupComponent {
+ constructor(
+ public dialogRef: MatDialogRef<ConfirmPopupComponent>,
+ @Inject(MAT_DIALOG_DATA) public data: any
+ ) {}
+
+ close(flag) {
+ this.dialogRef.close(flag);
+ }
+}
diff --git a/public/src/app/rule-engine/from/from.component.html b/public/src/app/rule-engine/from/from.component.html
new file mode 100644
index 0000000..7af653d
--- /dev/null
+++ b/public/src/app/rule-engine/from/from.component.html
@@ -0,0 +1,70 @@
+<form #fromFrm="ngForm" novalidate>
+ <!-- Copy template -->
+ <div class="from" *ngIf="actionType === 'copy'" data-tests-id="fromComponent">
+ <div class="from-conatiner">
+ <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" name="copyFrom" required style="min-width: 190px;" (ngModelChange)="modelChange(from)" #copyFrom="ngModel"
+ [(ngModel)]="from.value" type="text" data-tests-id="valueInput">
+ <span class="label" (click)="showRegex(from)" [ngStyle]="from.state === 'open' ? { 'color': '#009FDB'} : {'color':'gray'}"
+ style="padding: 0 5px; width: 50px; cursor: pointer; border: none" data-tests-id="btnFromRegex">Re/g</span>
+ </div>
+ <div [@state]="from.state" *ngIf="from.state === 'open'" style="display: flex; align-items: center; width: 80%;" class="label">
+ <span class="label" style="padding: 0 3px; width: 54px; border-top: none; border-bottom: none;">regex</span>
+ <input class="input-text" style="min-width: 192px;" (ngModelChange)="modelChange(from)" [(ngModel)]="from.regex" type="text"
+ ngModel required name="RegexInput" data-tests-id="inputFromRegex">
+ </div>
+ </div>
+ </div>
+ <!-- Map template -->
+ <div class="from" *ngIf="actionType === 'map'" data-tests-id="fromComponent">
+ <div class="from-conatiner">
+ <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" ngModel required name="mapFromInput" (ngModelChange)="modelChange(from)" [(ngModel)]="from.value"
+ type="text" data-tests-id="valueInput">
+ </div>
+ </div>
+ </div>
+
+ <!-- dateFormatter template -->
+ <div class="from" *ngIf="actionType === 'date formatter'" data-tests-id="fromComponent">
+ <div class="from-conatiner">
+ <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" ngModel required name="dateFormatterFromInput" (ngModelChange)="modelChange(from)" [(ngModel)]="from.value"
+ type="text" data-tests-id="valueInput">
+ </div>
+ </div>
+ </div>
+
+ <!-- Concat template -->
+ <div class="from" *ngIf="actionType === 'concat'" ngModelGroup="concat" #concatFrom="ngModelGroup">
+ <div *ngFor="let input of from.values; let index = index;" data-tests-id="concatInputArrayFrom" (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="concat[{{index}}]">
+ </div>
+
+ <button mat-icon-button class="button-remove" [ngStyle]="hoveredIndex === index ? {'opacity':'1'} : {'opacity':'0'}" (click)="removeFromInput(index)"
+ *ngIf="from.values.length > 2" 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: 24px; width: 24px; display:flex"
+ data-tests-id="btnAddInput">
+ <mat-icon>add</mat-icon>
+ </button>
+ <span style="color: #009FDB; display: flex; justify-content: center; padding-left: 6px">Add input</span>
+ </div>
+ </div>
+ </div>
+</form>
diff --git a/public/src/app/rule-engine/from/from.component.scss b/public/src/app/rule-engine/from/from.component.scss
new file mode 100644
index 0000000..852984d
--- /dev/null
+++ b/public/src/app/rule-engine/from/from.component.scss
@@ -0,0 +1,63 @@
+.from {
+ display: flex;
+ flex-direction: column;
+ padding: 0 10px;
+
+ .label {
+ border: 1px solid #d2d2d2;
+ height: 30px;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ }
+}
+
+.from-select {
+ width: 250px;
+ border: none;
+}
+
+.from-conatiner {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+ min-width: 350px;
+
+ .input-text {
+ border: none;
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+}
+
+.button-remove {
+ display: flex;
+ justify-content: center;
+ color: #a7a7a7;
+ &:hover {
+ color: #009fdb;
+ }
+}
+
+:host {
+ @mixin md-icon-size($size: 24px) {
+ font-size: $size;
+ height: $size;
+ width: $size;
+ }
+
+ .material-icons.mat-icon {
+ @include md-icon-size(24px);
+ }
+ /deep/ .mat-button-wrapper {
+ padding: 0;
+ }
+ .mat-icon {
+ width: 18px;
+ height: 18px;
+ }
+}
diff --git a/public/src/app/rule-engine/from/from.component.ts b/public/src/app/rule-engine/from/from.component.ts
new file mode 100644
index 0000000..e7c276b
--- /dev/null
+++ b/public/src/app/rule-engine/from/from.component.ts
@@ -0,0 +1,91 @@
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ ViewChild
+} from '@angular/core';
+// import { From } from "../model";
+import { Subject } from 'rxjs/Subject';
+import {
+ trigger,
+ state,
+ animate,
+ transition,
+ style,
+ keyframes
+} from '@angular/animations';
+import { NgForm } from '@angular/forms';
+
+@Component({
+ selector: 'app-from',
+ templateUrl: './from.component.html',
+ styleUrls: ['./from.component.scss'],
+ animations: [
+ trigger('state', [
+ state(
+ 'open',
+ style({
+ opacity: 1,
+ height: 'auto'
+ })
+ ),
+ transition('* => open', [
+ animate(
+ 200,
+ keyframes([
+ style({
+ opacity: 1,
+ height: 'auto'
+ })
+ ])
+ )
+ ]),
+ state(
+ 'closed',
+ style({
+ opacity: 0,
+ height: 0
+ })
+ )
+ ])
+ ]
+})
+export class FromComponent {
+ from: any = {
+ value: '',
+ regex: '',
+ state: 'closed',
+ values: [{ value: '' }, { value: '' }]
+ };
+ @Input() actionType;
+ @Output() onFromChange = new EventEmitter();
+ @ViewChild('fromFrm') fromFrm: NgForm;
+ hoveredIndex;
+ // public keyUp = new BehaviorSubject<string>(null);
+
+ showRegex(item) {
+ item.state = item.state === 'closed' ? 'open' : 'closed';
+ if (item.state === 'closed') {
+ item.regex = '';
+ }
+ }
+ updateMode(fromData) {
+ console.log(fromData);
+ if (fromData) {
+ this.from = fromData;
+ }
+ }
+
+ constructor() {}
+
+ modelChange(event) {
+ this.onFromChange.emit(event);
+ }
+ addFromInput() {
+ this.from.values.push({ value: '' });
+ }
+ removeFromInput(index) {
+ this.from.values.splice(index, 1);
+ }
+}
diff --git a/public/src/app/rule-engine/host/exit-mode.enum.ts b/public/src/app/rule-engine/host/exit-mode.enum.ts
new file mode 100644
index 0000000..784ba3b
--- /dev/null
+++ b/public/src/app/rule-engine/host/exit-mode.enum.ts
@@ -0,0 +1,4 @@
+export enum ExitMode {
+ Done,
+ Cancel
+}
diff --git a/public/src/app/rule-engine/host/host-params.ts b/public/src/app/rule-engine/host/host-params.ts
new file mode 100644
index 0000000..f204101
--- /dev/null
+++ b/public/src/app/rule-engine/host/host-params.ts
@@ -0,0 +1,8 @@
+export interface HostParams {
+ readonly vfcmtUuid: string;
+ readonly nodeName: string;
+ readonly nodeId: string;
+ readonly fieldName: string;
+ readonly userId: string;
+ readonly flowType: string;
+}
diff --git a/public/src/app/rule-engine/host/host.service.spec.ts b/public/src/app/rule-engine/host/host.service.spec.ts
new file mode 100644
index 0000000..048be80
--- /dev/null
+++ b/public/src/app/rule-engine/host/host.service.spec.ts
@@ -0,0 +1,18 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { HostService } from './host.service';
+
+describe('HostService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [HostService]
+ });
+ });
+
+ it(
+ 'should be created',
+ inject([HostService], (service: HostService) => {
+ expect(service).toBeTruthy();
+ })
+ );
+});
diff --git a/public/src/app/rule-engine/host/host.service.ts b/public/src/app/rule-engine/host/host.service.ts
new file mode 100644
index 0000000..7918d30
--- /dev/null
+++ b/public/src/app/rule-engine/host/host.service.ts
@@ -0,0 +1,56 @@
+import { Injectable } from '@angular/core';
+import { HostParams } from './host-params';
+import { ExitMode } from './exit-mode.enum';
+
+@Injectable()
+export class HostService {
+ /* Public Members */
+
+ public static getParams(): HostParams {
+ return this.getQueryParamsObj(window.location.search) as HostParams;
+ }
+
+ public static enterModifyRule(): void {
+ this.postMessage('modifyRule', null);
+ }
+
+ public static exitModifyRule(): void {
+ this.postMessage('ruleList', null);
+ }
+
+ public static disableLoader(): void {
+ this.postMessage('disable-loader', null);
+ }
+
+ public static exit(mode: ExitMode, data: string): void {
+ if (mode === ExitMode.Cancel) {
+ this.postMessage('exit', null);
+ } else if (mode === ExitMode.Done) {
+ this.postMessage('exit', data);
+ }
+ }
+
+ /* Private Methods */
+
+ private static postMessage(eventName: string, data: string): void {
+ window.parent.postMessage(
+ {
+ type: eventName,
+ data: data
+ },
+ '*'
+ );
+ }
+
+ private static getQueryParamsObj(query: string): object {
+ return query
+ .substring(1) // removes '?' that always appears as prefix to the query-string
+ .split('&') // splits query-string to "key=value" strings
+ .map(p => p.split('=')) // splits each "key=value" string to [key,value] array
+ .reduce((res, p) => {
+ // converts to a dictionary (object) of params
+ res[p[0]] = p[1];
+ return res;
+ }, {});
+ }
+}
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
new file mode 100644
index 0000000..c68c706
--- /dev/null
+++ b/public/src/app/rule-engine/rule-list/rule-list.component.html
@@ -0,0 +1,73 @@
+<div class="container">
+ <div class="header">
+ <span style="font-size: 18px;">Rule Engine</span>
+ <div style="display:flex">
+ <button mat-raised-button (click)="translateRules()" color="primary" [disabled]="store.ruleList.length === 0" style="margin-left: 20px;"
+ data-tests-id="btnTranslate">
+ Translate
+ </button>
+ <app-bar-icons [tabName]="this.store.tabParmasForRule[0].name"></app-bar-icons>
+ </div>
+ </div>
+
+ <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;">
+ {{ error }}
+ </div>
+
+ <app-version-type-select #versionEventType [versions]="versions" [metaData]="metaData" (nodesUpdated)="handleUpdateNode($event)"
+ (refrashRuleList)="handlePropertyChange()"></app-version-type-select>
+
+ <div *ngIf="targetSource && store.ruleList.length === 0" style="margin: 30px 0; display: flex; align-items: center; justify-content: center; flex-direction: column;">
+
+ <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>
+
+ <div *ngIf="store.ruleList.length > 0">
+ <div style="padding: 10px 0;">
+ Rules
+ </div>
+ <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>
+ </div>
+
+ <div style="margin: 30px 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 (click)="removeItem(item.uid)" data-tests-id="deleteRule" class="btn-list" mat-icon-button>
+ <mat-icon class="md-24">delete</mat-icon>
+ </button>
+ </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
new file mode 100644
index 0000000..c4aee05
--- /dev/null
+++ b/public/src/app/rule-engine/rule-list/rule-list.component.scss
@@ -0,0 +1,109 @@
+.container {
+ // margin: 1rem;
+ position: relative;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .header {
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: #191919;
+ border-bottom: 2px solid #d2d2d2;
+ padding-bottom: 0.5rem;
+ margin: 1rem;
+ }
+
+ .item {
+ border: 1px solid #d2d2d2;
+ padding: 0 10px;
+ height: 40px;
+ }
+
+ .mat-fab,
+ .mat-mini-fab,
+ .mat-raised-button {
+ box-shadow: none;
+ }
+}
+.my-full-screen-dialog .mat-dialog-container {
+ max-width: none;
+ width: 100vw;
+ height: 100vh;
+ padding: 0;
+}
+
+.my-confrim-dialog .mat-dialog-container {
+ max-width: 600px;
+ width: 500px;
+ height: 200px;
+ padding: 0;
+}
+
+.btn-list {
+ display: flex !important;
+ justify-content: center !important;
+ color: #d2d2d2 !important;
+
+ &:hover {
+ color: #009fdb !important;
+ }
+}
+
+.hr {
+ display: block;
+ margin: 10px 0 10px 0;
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
+ width: 100%;
+}
+
+.mat-fab,
+.mat-mini-fab,
+.mat-raised-button {
+ box-shadow: none;
+}
+
+.mat-mini-fab .mat-button-wrapper {
+ padding: 0 !important;
+}
+.mat-icon {
+ // width: 18px;
+ // height: 18px;
+ display: flex !important;
+ justify-content: center !important;
+ align-items: center !important;
+}
+/* Rules for sizing the icon. */
+.material-icons.md-18 {
+ font-size: 18px;
+}
+.material-icons.md-24 {
+ font-size: 24px;
+}
+.material-icons.md-30 {
+ font-size: 30px;
+}
+.material-icons.md-36 {
+ font-size: 36px;
+}
+.material-icons.md-48 {
+ font-size: 48px;
+}
+
+/* Rules for using icons as black on a light background. */
+.material-icons.md-dark {
+ color: rgba(0, 0, 0, 0.54);
+}
+.material-icons.md-dark.md-inactive {
+ color: rgba(0, 0, 0, 0.26);
+}
+
+/* Rules for using icons as white on a dark background. */
+.material-icons.md-light {
+ color: rgba(255, 255, 255, 1);
+}
+.material-icons.md-light.md-inactive {
+ color: rgba(255, 255, 255, 0.3);
+}
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
new file mode 100644
index 0000000..45cfbd0
--- /dev/null
+++ b/public/src/app/rule-engine/rule-list/rule-list.component.ts
@@ -0,0 +1,197 @@
+import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
+import { MatDialog } from '@angular/material';
+import { ActionListComponent } from '../action-list/action-list.component';
+import { RuleEngineApiService } from '../api/rule-engine-api.service';
+import { ConfirmPopupComponent } from '../confirm-popup/confirm-popup.component';
+import { Store } from '../../store/store';
+import { isEmpty } from 'lodash';
+import { ToastrService } from 'ngx-toastr';
+import { timer } from 'rxjs/observable/timer';
+
+const primaryColor = '#009fdb';
+
+@Component({
+ selector: 'app-rule-list',
+ templateUrl: './rule-list.component.html',
+ styleUrls: ['./rule-list.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class RuleListComponent {
+ @ViewChild('versionEventType') versionType;
+ error: Array<string>;
+ // list = new Array();
+ schema;
+ targetSource;
+ dialogRef;
+ crud;
+ hoveredIndex;
+ params;
+ versions;
+ metaData;
+
+ private errorHandler(error: any) {
+ this.store.loader = false;
+ console.log(error);
+ this.error = [];
+ if (typeof error === 'string') {
+ this.error.push(error);
+ } else {
+ console.log(error.notes);
+ const errorFromServer = Object.values(error)[0] as any;
+ if (Object.keys(error)[0] === 'serviceExceptions') {
+ this.error = errorFromServer.map(x => x.formattedErrorMessage);
+ } else {
+ this.error = errorFromServer.formattedErrorMessage;
+ }
+ }
+ }
+
+ private getListOfRules() {
+ this._ruleApi.getListOfRules().subscribe(
+ response => {
+ console.log('res: %o', response);
+ if (response && Object.keys(response).length !== 0) {
+ this.versionType.updateData(
+ response.version,
+ response.eventType,
+ true
+ );
+ this.store.updateRuleList(Object.values(response.rules));
+ this.targetSource = response.schema;
+ } else {
+ this.store.resetRuleList();
+ this.versionType.updateVersionTypeFlag(false);
+ this.targetSource = null;
+ // 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.store.loader = false;
+ },
+ error => {
+ this.errorHandler(error);
+ }
+ );
+ }
+
+ constructor(
+ private _ruleApi: RuleEngineApiService,
+ public dialog: MatDialog,
+ private toastr: ToastrService,
+ public store: Store
+ ) {
+ this.store.loader = true;
+ this.params = {
+ vfcmtUuid: this.store.mcUuid,
+ nodeName: this.store.tabParmasForRule[0].name,
+ nodeId: this.store.tabParmasForRule[0].nid,
+ fieldName: this.store.configurationForm[0].name,
+ userId: 'ym903w', // 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);
+ this.getListOfRules();
+ }
+
+ handlePropertyChange() {
+ this.store.loader = true;
+ this.error = null;
+ this.getListOfRules();
+ }
+
+ translateRules() {
+ this.store.loader = true;
+ // send translate JSON
+ this._ruleApi.translate().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.assignment.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);
+ }
+ );
+ }
+
+ handleUpdateNode(data) {
+ this.targetSource = data.nodes;
+ this.store.resetRuleList();
+ }
+
+ removeItem(uid) {
+ this.dialogRef = this.dialog.open(ConfirmPopupComponent, {
+ panelClass: 'my-confrim-dialog',
+ disableClose: true
+ });
+ this.dialogRef.afterClosed().subscribe(result => {
+ // if the user want to delete
+ 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);
+ }
+ );
+ }
+ });
+ }
+
+ openAction(item): 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
+ });
+ this.store.isLeftVisible = false;
+
+ this._ruleApi.updateVersionLock.subscribe(() => {
+ this.versionType.updateVersionTypeFlag(true);
+ });
+ }
+}
diff --git a/public/src/app/rule-engine/slide-panel/slide-panel.component.html b/public/src/app/rule-engine/slide-panel/slide-panel.component.html
new file mode 100644
index 0000000..f0ee27e
--- /dev/null
+++ b/public/src/app/rule-engine/slide-panel/slide-panel.component.html
@@ -0,0 +1,8 @@
+<div class="panes" [@slide]="activePane">
+ <div style="height: 100%">
+ <ng-content select="[leftPane]"></ng-content>
+ </div>
+ <div style="height: 100%">
+ <ng-content select="[rightPane]"></ng-content>
+ </div>
+</div>
diff --git a/public/src/app/rule-engine/slide-panel/slide-panel.component.scss b/public/src/app/rule-engine/slide-panel/slide-panel.component.scss
new file mode 100644
index 0000000..2c9f00a
--- /dev/null
+++ b/public/src/app/rule-engine/slide-panel/slide-panel.component.scss
@@ -0,0 +1,15 @@
+:host {
+ display: block;
+ overflow: hidden;
+ height: 100%;
+}
+
+.panes {
+ height: 100%;
+ width: 200%;
+
+ display: flex;
+ div {
+ flex: 1;
+ }
+}
diff --git a/public/src/app/rule-engine/slide-panel/slide-panel.component.ts b/public/src/app/rule-engine/slide-panel/slide-panel.component.ts
new file mode 100644
index 0000000..d7aa652
--- /dev/null
+++ b/public/src/app/rule-engine/slide-panel/slide-panel.component.ts
@@ -0,0 +1,27 @@
+import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
+import {
+ animate,
+ state,
+ style,
+ transition,
+ trigger
+} from '@angular/animations';
+
+type PaneType = 'left' | 'right';
+
+@Component({
+ selector: 'app-slide-panel',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ templateUrl: './slide-panel.component.html',
+ styleUrls: ['./slide-panel.component.scss'],
+ animations: [
+ trigger('slide', [
+ state('left', style({ transform: 'translateX(0)' })),
+ state('right', style({ transform: 'translateX(-50%)' })),
+ transition('* => *', animate(300))
+ ])
+ ]
+})
+export class SlidePanelComponent {
+ @Input() activePane: PaneType = 'left';
+}
diff --git a/public/src/app/rule-engine/target/target.component.html b/public/src/app/rule-engine/target/target.component.html
new file mode 100644
index 0000000..7a321ef
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.component.html
@@ -0,0 +1,28 @@
+<form #targetFrm="ngForm" novalidate class="target">
+ <div class="top-select">
+ <span class="label" style="border-right: none;">Target</span>
+ <input class="text-input" style="border-right: none;" type="text" [(ngModel)]="selectedNode.id" (ngModelChange)="inputChange()"
+ ngModel required name="targetInput" data-tests-id="inputTarget">
+ <span class="label clickable" data-tests-id="openTargetTree" style="border-left: none;" (click)="showOption = !showOption">
+ <img src="{{imgBase}}/target.svg" alt="target">
+ </span>
+ </div>
+ <div class="bottom-select" *ngIf="showOption" [@toggleDropdown]>
+ <div class="filter-container" style="display: flex; border-bottom: 1px solid #F2F2F2;margin-bottom: 1rem; width:100%;">
+ <input id="filter" #filter class="filter" (keyup)="tree.treeModel.filterNodes(filter.value)" placeholder="Search..." />
+ <button mat-raised-button style="min-width: 18px; box-shadow: none; display: flex; justify-content: center;" (click)="tree.treeModel.clearFilter(); filter.value = ''">
+ <mat-icon>clear</mat-icon>
+ </button>
+ </div>
+
+ <tree-root #tree [focused]="true" class="targetTree" (event)="onEvent($event)" [nodes]="nodes" [options]="options">
+ <ng-template #treeNodeTemplate let-node let-index="index">
+ <span *ngIf="node.data.isRequired" class="required"></span>
+ <span data-tests-id="targetNode">
+ {{ node.data.name }}
+ </span>
+ </ng-template>
+ </tree-root>
+
+ </div>
+</form>
diff --git a/public/src/app/rule-engine/target/target.component.scss b/public/src/app/rule-engine/target/target.component.scss
new file mode 100644
index 0000000..ed2d70e
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.component.scss
@@ -0,0 +1,99 @@
+.targetTree {
+ tree-viewport {
+ overflow: hidden;
+ }
+}
+
+.conatiner {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ justify-content: space-between;
+
+ .center-content {
+ display: flex;
+ width: 100%;
+
+ .action-info {
+ background: #93cdff;
+ padding: 6px;
+ border-radius: 5px;
+ height: 20px;
+ margin: 0 10px;
+ }
+
+ .regex {
+ max-width: 250px;
+ float: right;
+ display: flex;
+ align-items: center;
+ padding: 20px 10px;
+
+ .label {
+ border: 1px solid #d2d2d2;
+ padding: 0 5px;
+ height: 30px;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ }
+ }
+ }
+}
+.target {
+ width: 100%;
+ .top-select {
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ }
+ .label {
+ border: 1px solid #d2d2d2;
+ padding: 0 5px;
+ height: 30px;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ }
+
+ .bottom-select {
+ border: 1px solid #ccc;
+ padding: 7px;
+ .filter-container {
+ padding: 5px;
+ .filter {
+ background: #fff;
+ color: black;
+ font: inherit;
+ border: 0;
+ outline: 0;
+ padding: 10px;
+ width: 100%;
+ }
+ }
+ }
+}
+
+.small-padding {
+ padding-right: 10px;
+}
+
+.text-input {
+ width: 100%;
+ height: 30px;
+ margin: 0;
+ padding: 0 5px;
+ border: 1px solid #d2d2d2;
+}
+
+.clickable {
+ cursor: pointer;
+}
+
+.required::before {
+ content: '*';
+ color: red;
+}
diff --git a/public/src/app/rule-engine/target/target.component.spec.ts b/public/src/app/rule-engine/target/target.component.spec.ts
new file mode 100644
index 0000000..6ddd8cd
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.component.spec.ts
@@ -0,0 +1,57 @@
+import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MatButtonModule, MatIconModule } from '@angular/material';
+// component
+import { TargetComponent } from './target.component';
+
+describe('TargetComponent', () => {
+ let component: TargetComponent;
+ let fixture: ComponentFixture<TargetComponent>;
+ let de: DebugElement;
+ let el: HTMLElement;
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ FormsModule,
+ BrowserAnimationsModule,
+ MatButtonModule,
+ MatIconModule
+ ],
+ providers: [],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ declarations: [TargetComponent]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ // create component and test fixture
+ fixture = TestBed.createComponent(TargetComponent);
+ // get test component from the fixture
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should be created', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should open target tree when click on button', () => {
+ const openTargetElement = fixture.debugElement
+ .query(By.css('span[data-tests-id=openTargetTree]'))
+ .nativeElement.click();
+
+ fixture.detectChanges();
+
+ const treeContainer = fixture.debugElement.query(
+ By.css('.filter-container')
+ );
+ expect(treeContainer).not.toBeNull();
+ });
+});
diff --git a/public/src/app/rule-engine/target/target.component.ts b/public/src/app/rule-engine/target/target.component.ts
new file mode 100644
index 0000000..f17cdef
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.component.ts
@@ -0,0 +1,77 @@
+import {
+ Component,
+ ViewEncapsulation,
+ ViewChild,
+ Input,
+ Output,
+ EventEmitter
+} from '@angular/core';
+import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component';
+import {
+ trigger,
+ state,
+ animate,
+ transition,
+ style
+} from '@angular/animations';
+import { fuzzysearch, getBranchRequierds, validation } from './target.util';
+import { environment } from '../../../environments/environment';
+import { NgForm } from '@angular/forms';
+
+@Component({
+ selector: 'app-target',
+ templateUrl: './target.component.html',
+ styleUrls: ['./target.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+ animations: [
+ trigger('toggleDropdown', [
+ transition('void => *', [
+ style({ opacity: 0, offset: 0, height: 0 }),
+ animate('300ms cubic-bezier(0.17, 0.04, 0.03, 0.94)')
+ ]),
+ transition('* => void', [
+ style({ opacity: 1, offset: 1, height: 'auto' }),
+ animate('100ms cubic-bezier(0.17, 0.04, 0.03, 0.94)')
+ ])
+ ])
+ ]
+})
+export class TargetComponent {
+ imgBase = environment.imagePath;
+ showOption = false;
+ selectedNode = { name: '', id: '' };
+ @Input() nodes;
+ @Output() onTargetChange = new EventEmitter();
+ @ViewChild(TreeComponent) private tree: TreeComponent;
+ @ViewChild('targetFrm') targetFrm: NgForm;
+ options: ITreeOptions = {
+ animateExpand: true,
+ animateSpeed: 30,
+ animateAcceleration: 1.2
+ };
+
+ filterFn(value, treeModel: TreeModel) {
+ treeModel.filterNodes(node => fuzzysearch(value, node.data.name));
+ }
+
+ inputChange() {
+ this.onTargetChange.emit(this.selectedNode.id);
+ }
+
+ updateMode(action) {
+ this.selectedNode = {
+ id: action.target,
+ name: ''
+ };
+ }
+
+ onEvent(event) {
+ if (event.eventName === 'activate') {
+ if (event.node.data.children === null) {
+ this.selectedNode = event.node.data;
+ this.onTargetChange.emit(this.selectedNode);
+ this.showOption = false;
+ }
+ }
+ }
+}
diff --git a/public/src/app/rule-engine/target/target.util.ts b/public/src/app/rule-engine/target/target.util.ts
new file mode 100644
index 0000000..6a6df62
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.util.ts
@@ -0,0 +1,50 @@
+export function getBranchRequierds(node, requiredArr) {
+ if (node.parent) {
+ if (node.parent.data.hasOwnProperty('requiredChildren')) {
+ requiredArr.push(node.parent.data.requiredChildren);
+ }
+ return getBranchRequierds(node.parent, requiredArr);
+ }
+ return requiredArr;
+}
+
+export function validation(node, userSelection) {
+ const requiredArr = [];
+ const validationRequired = getBranchRequierds(node, requiredArr);
+ const nonValidationArr = [];
+ validationRequired.forEach(nodeRequireds => {
+ return nodeRequireds.forEach(levelRequired => {
+ if (userSelection.filter(node => node === levelRequired).length === 0) {
+ nonValidationArr.push(levelRequired);
+ }
+ return;
+ });
+ });
+ return nonValidationArr;
+}
+
+export function fuzzysearch(needle, haystack) {
+ const haystackLC = haystack.toLowerCase();
+ const needleLC = needle.toLowerCase();
+
+ const hlen = haystack.length;
+ const nlen = needleLC.length;
+
+ if (nlen > hlen) {
+ return false;
+ }
+ if (nlen === hlen) {
+ return needleLC === haystackLC;
+ }
+ outer: for (let i = 0, j = 0; i < nlen; i++) {
+ const nch = needleLC.charCodeAt(i);
+
+ while (j < hlen) {
+ if (haystackLC.charCodeAt(j++) === nch) {
+ continue outer;
+ }
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/public/src/app/rule-engine/target/target.validation.spec.ts b/public/src/app/rule-engine/target/target.validation.spec.ts
new file mode 100644
index 0000000..71dc083
--- /dev/null
+++ b/public/src/app/rule-engine/target/target.validation.spec.ts
@@ -0,0 +1,83 @@
+import { TestBed, async } from '@angular/core/testing';
+import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component';
+import { validation, getBranchRequierds } from './target.util';
+
+const _nodes = [
+ {
+ id: 1,
+ name: 'North America',
+ requiredChildren: ['United States'],
+ children: [
+ {
+ id: 11,
+ name: 'United States',
+ requiredChildren: ['New York', 'Florida'],
+ children: [
+ { id: 111, name: 'New York' },
+ { id: 112, name: 'California' },
+ { id: 113, name: 'Florida' }
+ ]
+ },
+ { id: 12, name: 'Canada' }
+ ]
+ },
+ {
+ name: 'South America',
+ children: [{ name: 'Argentina', children: [] }, { name: 'Brazil' }]
+ },
+ {
+ name: 'Europe',
+ children: [
+ { name: 'England' },
+ { name: 'Germany' },
+ { name: 'France' },
+ { name: 'Italy' },
+ { name: 'Spain' }
+ ]
+ }
+];
+
+const tree = new TreeModel();
+
+describe('treeTest', () => {
+ beforeAll(() => {
+ tree.setData({
+ nodes: _nodes,
+ options: null,
+ events: null
+ });
+ });
+
+ it('should return node branch requireds', () => {
+ // console.log('root', tree.getFirstRoot().data.name);
+ // console.log(tree.getNodeBy((node) => node.data.name === 'California').data.uuid);
+ // console.log(tree.getNodeBy((node) => node.data.name === 'California').id);
+ // console.log(tree.getNodeById(1));
+ const selectedNode = tree.getNodeBy(
+ node => node.data.name === 'California'
+ );
+ const result = getBranchRequierds(selectedNode, []);
+ const expected = [['New York', 'Florida'], ['United States']];
+
+ expect(result.length).toBeGreaterThan(1);
+ expect(result).toEqual(expected);
+ });
+
+ it('should return empty array - success state', () => {
+ const userSelect = ['Florida', 'New York', 'United States'];
+ const selectedNode = tree.getNodeBy(node => node.data.name === 'New York');
+ const result = validation(selectedNode, userSelect);
+
+ expect(result.length).toEqual(0);
+ expect(result).toEqual([]);
+ });
+
+ it('should return validation array - missing required filed', () => {
+ const userSelect = ['New York'];
+ const selectedNode = tree.getNodeBy(node => node.data.name === 'New York');
+ const result = validation(selectedNode, userSelect);
+ const expected = ['Florida', 'United States'];
+
+ expect(result).toEqual(expected);
+ });
+});
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
new file mode 100644
index 0000000..79b9eae
--- /dev/null
+++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.html
@@ -0,0 +1,34 @@
+<div class="selected-event">
+
+ <div style="flex:1; display: flex; align-items: center;">
+
+ <span class="field-label required" style="margin-right: 10px;">Mapping Target</span>
+ <select name="mappingTarget" [(ngModel)]="mappingTarget" (ngModelChange)="onChangeMapping($event)" data-tests-id="mappingDdl"
+ style="height: 27px; padding: 0.3rem; margin-right: 18px;" class="field-select">
+ <option [ngValue]="null" disabled>Select Mapping</option>
+ <option *ngFor="let target of advancedSetting" [value]="target.name" data-tests-id="templateOptions">{{target.name}}</option>
+ </select>
+
+ <span class="field-label required" style="font-size: 13px; margin-right: 10px; display: flex;
+ align-items: center;" [ngClass]="{'required' : !readOnly}">
+ Version
+ </span>
+ <select *ngIf="!readOnly" style="height: 27px; padding: 0.3rem; margin-right: 18px;" [(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: 27px; padding: 0.3rem; width:100px; margin-right: 18px; border: 1px solid #D2D2D2; display: flex; align-items: center; background: #F2F2F2">{{selectedVersion}}</span>
+
+ <span class="field-label required" style="font-size: 13px; display: flex; align-items: center; width: 100px;" [ngClass]="{'required' : !readOnly}">
+ Event Domain
+ </span>
+ <select *ngIf="!readOnly" style="height: 27px; padding: 0.3rem;" [(ngModel)]="selectedEvent" (ngModelChange)="onSelectEventType($event)"
+ 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: 27px; 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>
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
new file mode 100644
index 0000000..9f7bad3
--- /dev/null
+++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.scss
@@ -0,0 +1,46 @@
+.selected-event {
+ display: flex;
+ margin: 10px 0;
+ // align-items: center;
+ flex-direction: column;
+ margin-bottom: 30px;
+}
+
+.small-padding {
+ padding-right: 1rem;
+}
+
+.btn {
+ padding: 6px;
+ margin: 6px 8px 6px 8px;
+ min-width: 88px;
+ border-radius: 3px;
+ font-size: 14px;
+ text-align: center;
+ text-transform: uppercase;
+ text-decoration: none;
+ border: none;
+ outline: none;
+}
+
+.target-field {
+ width: 370px;
+ display: flex;
+ align-items: center;
+ margin: 10px;
+ .field-label {
+ padding-right: 10px;
+ }
+ .required::before {
+ content: '*';
+ color: red;
+ padding-right: 5px;
+ }
+ .field-select {
+ flex: 1;
+ width: 100%;
+ min-width: 250px;
+ padding: 5px 0 5px 5px;
+ margin: 0;
+ }
+}
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
new file mode 100644
index 0000000..b4170a5
--- /dev/null
+++ b/public/src/app/rule-engine/version-type-select/version-type-select.component.ts
@@ -0,0 +1,86 @@
+import { Component, Output, EventEmitter, Input } from '@angular/core';
+import { RuleEngineApiService } from '../api/rule-engine-api.service';
+import { Store } from '../../store/store';
+
+@Component({
+ selector: 'app-version-type-select',
+ templateUrl: './version-type-select.component.html',
+ styleUrls: ['./version-type-select.component.scss']
+})
+export class VersionTypeSelectComponent {
+ mappingTarget: string;
+ selectedEvent: String;
+ selectedVersion: String;
+ events: Array<String>;
+ loader: boolean;
+ editMode = false;
+ readOnly = false;
+ @Input() versions;
+ @Input() metaData;
+ @Output() nodesUpdated = new EventEmitter();
+ @Output() refrashRuleList = new EventEmitter();
+ advancedSetting;
+
+ constructor(private _ruleApi: RuleEngineApiService, public store: Store) {
+ this.selectedVersion = null;
+ this.selectedEvent = null;
+ // set ddl with the first option value.
+ this.mappingTarget = this.store.configurationForm[0].name;
+ this.advancedSetting = this.store.configurationForm.filter(item => {
+ if (
+ !(
+ item.hasOwnProperty('constraints') &&
+ !item.assignment.value.includes('get_input')
+ )
+ ) {
+ return item;
+ }
+ });
+ }
+
+ onChangeMapping(configurationKey) {
+ console.log('changing propertiy key:', configurationKey);
+ this._ruleApi.setFieldName(configurationKey);
+ this.refrashRuleList.next();
+ }
+
+ updateData(version, eventType, isList) {
+ this.selectedVersion = version;
+ this.selectedEvent = eventType;
+ this.readOnly = true;
+ }
+
+ updateVersionTypeFlag(flag) {
+ this.readOnly = flag;
+ if (flag === false) {
+ this.selectedVersion = null;
+ this.selectedEvent = null;
+ }
+ }
+
+ onSelectVersion(version, eventType) {
+ if (typeof eventType === 'undefined') {
+ this.selectedEvent = '';
+ this.events = this.metaData
+ .filter(x => x.version === version)
+ .map(x => x.eventTypes)[0];
+ if (eventType) {
+ this.editMode = true;
+ this.selectedEvent = eventType + 'Fields';
+ }
+ }
+ }
+
+ onSelectEventType(eventType) {
+ this.loader = true;
+ this._ruleApi
+ .getSchema(this.selectedVersion, this.selectedEvent)
+ .subscribe(tree => {
+ console.log('tree: ', tree);
+ this.loader = false;
+ this.nodesUpdated.emit({
+ nodes: tree
+ });
+ });
+ }
+}
diff --git a/public/src/app/rule-frame/rule-frame.component.html b/public/src/app/rule-frame/rule-frame.component.html
new file mode 100644
index 0000000..10f3032
--- /dev/null
+++ b/public/src/app/rule-frame/rule-frame.component.html
@@ -0,0 +1,19 @@
+<div style="position: relative; display: flex; justify-content: flex-end; height: 100%;">
+
+ <div *ngIf="!tabName.includes('map')" style="margin: 1em;">
+ <app-bar-icons [tabName]="tabName"></app-bar-icons>
+ </div>
+
+ <!-- rule engine -->
+ <div style="width: 100%;" *ngIf="tabName.includes('map')">
+ <app-slide-panel [activePane]="store.isLeftVisible ? 'left' : 'right'">
+ <div leftPane style="height: 100%; overflow: auto;">
+ <app-rule-list></app-rule-list>
+ </div>
+ <div rightPane style="height: 100%; overflow: auto;">
+ <app-action-list></app-action-list>
+ </div>
+ </app-slide-panel>
+ </div>
+
+</div>
diff --git a/public/src/app/rule-frame/rule-frame.component.scss b/public/src/app/rule-frame/rule-frame.component.scss
new file mode 100644
index 0000000..2a95e01
--- /dev/null
+++ b/public/src/app/rule-frame/rule-frame.component.scss
@@ -0,0 +1,10 @@
+.frame {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+ border: 0 none;
+ box-sizing: border-box;
+}
diff --git a/public/src/app/rule-frame/rule-frame.component.ts b/public/src/app/rule-frame/rule-frame.component.ts
new file mode 100644
index 0000000..4d5f999
--- /dev/null
+++ b/public/src/app/rule-frame/rule-frame.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnDestroy, Input, ViewChild } from '@angular/core';
+import { Store } from '../store/store';
+import { BarIconsComponent } from '../bar-icons/bar-icons.component';
+
+@Component({
+ selector: 'app-rule-frame',
+ templateUrl: './rule-frame.component.html',
+ styleUrls: ['./rule-frame.component.scss']
+})
+export class RuleFrameComponent implements OnDestroy {
+ expandSetting = false;
+ configuration;
+ mappingTarget: string;
+ showHeaderBtn = true;
+ @Input() tabName: string;
+ // @ViewChild(BarIconsComponent) barFormsRef: BarIconsComponent;
+
+ constructor(public store: Store) {
+ this.store.isLeftVisible = true;
+ }
+
+ ngOnDestroy() {}
+
+ onChangeMapping(configurationKey) {
+ console.log('changing ifrmae entry', configurationKey);
+ }
+
+ isPropertyDdl(property) {
+ return property.hasOwnProperty('constraints');
+ }
+
+ enableSetting() {
+ this.expandSetting = !this.expandSetting;
+ }
+}
diff --git a/public/src/app/store/store.ts b/public/src/app/store/store.ts
new file mode 100644
index 0000000..a9f2431
--- /dev/null
+++ b/public/src/app/store/store.ts
@@ -0,0 +1,98 @@
+import { Injectable } from '@angular/core';
+import { observable, computed, action, toJS, reaction } from 'mobx';
+import { findIndex } from 'lodash';
+
+@Injectable()
+export class Store {
+ @observable sdcParmas;
+ @observable isOwner;
+ @observable mcUuid;
+ @observable cdump;
+ @observable tabsProperties;
+ @observable tabIndex = 0;
+ @observable isEditMode = false;
+ @observable loader = false;
+ @observable cdumpIsDirty = false;
+ @observable expandAdvancedSetting = [];
+ @observable generalflow;
+ @observable vfiName;
+ // error dialog
+ @observable displayErrorDialog = false;
+ @observable ErrorContent = [];
+
+ // rule-engine
+ @observable tabParmasForRule;
+ @observable ruleList = new Array();
+ @observable ruleEditorInitializedState;
+ @observable isLeftVisible;
+ @observable inprogress;
+
+ @action
+ updateRuleInList(rule) {
+ console.log('current list:', toJS(this.ruleList));
+ console.log('new rule', rule);
+ const ruleIndex = findIndex(this.ruleList, function(ruleFromList) {
+ console.log(
+ `find match rule: list - ${ruleFromList.uid}, rule - ${rule.uid}`
+ );
+ return ruleFromList.uid === rule.uid;
+ });
+ if (ruleIndex > -1) {
+ console.log('update rule');
+ this.ruleList[ruleIndex] = rule;
+ } else {
+ console.log('new rule');
+ this.ruleList.push(rule);
+ }
+ }
+
+ @action
+ updateRuleList(listOfRules) {
+ this.ruleList = listOfRules;
+ console.log(toJS(this.ruleList));
+ }
+
+ @action
+ removeRuleFromList(uid) {
+ this.ruleList = this.ruleList.filter(item => item.uid !== uid);
+ }
+
+ @action
+ resetRuleList() {
+ this.ruleList = new Array();
+ }
+
+ @action
+ changeStateForEditor(data) {
+ this.ruleEditorInitializedState = data;
+ }
+
+ @action
+ setTabIndex(value) {
+ this.tabIndex = value;
+ }
+
+ @action
+ setTabsProperties(nodes) {
+ this.tabsProperties = nodes.map(tabItem => {
+ return tabItem.properties.map(x => {
+ if (!x.assignment) {
+ x.assignment = {};
+ x.assignment.value = '';
+ } else if (typeof x.assignment.value === 'object') {
+ x.assignment.value = JSON.stringify(x.assignment.value);
+ }
+ return x;
+ });
+ });
+ nodes.map(() => {
+ this.expandAdvancedSetting.push(false);
+ });
+ console.log('tabsProperties: %o', this.tabsProperties.toJS());
+ }
+
+ @computed
+ get configurationForm() {
+ return this.tabIndex >= 0 ? this.tabsProperties[this.tabIndex] : null;
+ }
+}