UI Support for operation milestones 15/135815/5
authorJvD_Ericsson <jeff.van.dam@est.tech>
Wed, 16 Aug 2023 13:43:13 +0000 (14:43 +0100)
committerMichael Morris <michael.morris@est.tech>
Fri, 22 Sep 2023 07:43:34 +0000 (07:43 +0000)
Issue-ID: SDC-4601
Signed-off-by: JvD_Ericsson <jeff.van.dam@est.tech>
Change-Id: I9088a1d004ae3a3470aee8d831066584fd26b0d4

21 files changed:
catalog-ui/src/app/models/interfaceOperation.ts
catalog-ui/src/app/models/operation.ts
catalog-ui/src/app/ng2/components/ui/tabs/tab/tab.component.ts
catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.html
catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.less
catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/add-input/add-input.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts
catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts
catalog-ui/src/app/utils/common-utils.ts

index a6279a5..fdf26aa 100644 (file)
@@ -25,6 +25,17 @@ import {PROPERTY_DATA, PROPERTY_TYPES} from "../utils/constants";
 import {ToscaFunction} from "./tosca-function";
 import {SubPropertyToscaFunction} from "./sub-property-tosca-function";
 
+export enum MilestoneEnum {
+    on_entry = 'on_entry',
+    on_success = 'on_success',
+    on_failure = 'on_failure',
+    on_timeout = 'on_timeout'
+}
+
+export enum ActivityTypesEnum {
+    Delegate = 'delegate'
+}
+
 export class InputOperationParameter {
     name: string;
     type: string;
@@ -96,12 +107,63 @@ export interface IOperationParamsList {
     listToscaDataDefinition: Array<InputOperationParameter>;
 }
 
+export class Milestone {
+    activities: IActivityParameterList;
+    filterParams: IActivityParameterList;
+
+    constructor(param?: any) {
+        if (param) {
+            this.activities = param.activityParams;
+            this.filterParams = param.filterParams;
+        }
+    }
+}
+
+export class ActivityParameter {
+    type: string;
+    workflow: string;
+    inputs: IOperationParamsList;
+
+    constructor(param?: any) {
+        if (param) {
+            this.type = param.type;
+            this.workflow = param.workflow;
+            this.inputs = param.inputs;
+        }
+    }
+}
+
+export interface IActivityParameterList {
+    listToscaDataDefinition: Array<ActivityParameter>;
+}
+
+export class FilterParameter {
+    name: string;
+    constraint: string;
+    filterValue: any;
+    toscaFunction?: ToscaFunction;
+
+    constructor(param?: any) {
+        if (param) {
+            this.name = param.name;
+            this.constraint = param.constraint;
+            this.filterValue = param.filterValue;
+            this.toscaFunction = param.toscaFunction;
+        }
+    }
+}
+
+export interface IFilterParameterList {
+    listToscaDataDefinition: Array<FilterParameter>;
+}
+
 export class BEInterfaceOperationModel {
     name: string;
     description: string;
     uniqueId: string;
     inputs: IOperationParamsList;
     implementation: ArtifactModel;
+    milestones: Object;
 
     constructor(operation?: any) {
         if (operation) {
@@ -110,6 +172,7 @@ export class BEInterfaceOperationModel {
             this.uniqueId = operation.uniqueId;
             this.inputs = operation.inputs;
             this.implementation = operation.implementation;
+            this.milestones = operation.milestones;
         }
     }
 }
@@ -126,6 +189,7 @@ export class InterfaceOperationModel extends BEInterfaceOperationModel {
     uniqueId: string;
     inputParams: IOperationParamsList;
     implementation: ArtifactModel;
+    milestones: Object
 
     constructor(operation?: any) {
         super(operation);
@@ -147,6 +211,7 @@ export class InterfaceOperationModel extends BEInterfaceOperationModel {
             if (operation.implementation) {
                 this.implementation = new ArtifactModel(operation.implementation);
             }
+            this.milestones = operation.milestones;
         }
     }
 
index 3d4917f..3b2f203 100644 (file)
@@ -101,6 +101,8 @@ export class OperationModel extends BEOperationModel{
     workflowName: string;
     workflowVersion: string;
 
+    milestones: Object;
+
     protected OperationTypeEnum: Array<String> = [
         'Create',
         'Delete',
@@ -125,6 +127,7 @@ export class OperationModel extends BEOperationModel{
             this.artifactData = operation.artifactData;
             this.workflowName = operation.workflowName;
             this.workflowVersion = operation.workflowVersion;
+            this.milestones = operation.milestones;
         }
     }
 
index e0eacdc..8e0b0a5 100644 (file)
@@ -34,6 +34,7 @@ export class Tab {
     @Input('tabTitle') title: string;
     @Input() active:boolean = false;
     @Input() show:boolean = true;
+    @Input() highlight?: string;
     @Input() indication?: number;
 
 }
index d52dccc..d6cf693 100644 (file)
@@ -16,7 +16,7 @@
 
 <div class="tabs {{tabStyle}}">
 <ng-container *ngFor="let tab of tabs">
-    <div class="tab" *ngIf="tab.show" (click)="selectTab(tab)" [class.active]="tab.active" [attr.data-tests-id]="tab.title">
+    <div class="tab" *ngIf="tab.show" (click)="selectTab(tab)" [class.active]="tab.active" [attr.data-tests-id]="tab.title" [ngStyle]="{'color': tab.highlight}">
         {{tab.title}}
         <div class="tab-indication" *ngIf="tab.indication" [@indicatorAnimation]="tab.indication">{{tab.indication}}</div>
     </div>
index db60be5..d37868d 100644 (file)
             }
         }
     }
+
+    &.basic-tabs .tab {
+        color: @main_color_n;
+        flex: 0 0 auto;
+
+        &:after {
+            display:block;
+            content: '';
+            border-bottom: 2px solid @main_color_a;
+            transform: scaleX(0);
+            transition: transform 200ms ease-in-out;
+        }
+
+        &.active {
+            color: @main_color_a;
+            &:after {
+                transform: scaleX(1.2);
+            }
+        }
+    }
 }
index 04210ae..dfdaa77 100644 (file)
@@ -142,6 +142,8 @@ export class InterfaceOperationsComponent {
     toscaArtifactTypes: Array<DropdownValue> = [];
     componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
     validImplementationProps: boolean = true;
+    validMilestoneActivities: boolean = true;
+    validMilestoneFilters: boolean = true;
 
     @Input() component: ComponentInstance;
     @Input() isViewOnly: boolean;
@@ -224,6 +226,11 @@ export class InterfaceOperationsComponent {
             return disable;
         }
 
+        const validMilestoneFilters = this.modalInstance.instance.dynamicContent.instance.validMilestoneFilters;
+        const validMilestoneActivities = this.modalInstance.instance.dynamicContent.instance.validMilestoneActivities;
+        if (!validMilestoneActivities || !validMilestoneFilters) {
+            return disable;
+        }
         let enableAddArtifactImplementation = this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation;
         if(enableAddArtifactImplementation) {
             const validImplementationProps = this.modalInstance.instance.dynamicContent.instance.validImplementationProps;
@@ -273,6 +280,8 @@ export class InterfaceOperationsComponent {
                 validityChangedCallback: this.disableSaveButton,
                 isViewOnly: this.isViewOnly,
                 validImplementationProps: this.validImplementationProps,
+                validMilestoneActivities: this.validMilestoneActivities,
+                validMilestoneFilters: this.validMilestoneFilters,
                 isEdit: true,
                 modelName: this.componentMetaData.model
             }
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html
new file mode 100644 (file)
index 0000000..ce8eb2f
--- /dev/null
@@ -0,0 +1,106 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+-->
+
+<div class="sub-operation-activities">
+  <div class="group-with-border content-row">
+    <form novalidate class="w-sdc-form two-columns" [formGroup]="activityForm">
+      <div formArrayName="activityFormList">
+        <label class="activities-label"> Activities </label>
+        <div *ngFor="let activity of activities; let idx = index">
+          <div class="side-by-side group-with-border-blue">
+            <div class="form-item">
+              <label class="sdc-timeout-label">Type: </label>
+              <div class="sdc-dropdown">
+                <select class="i-sdc-form-select"
+                        data-tests-id="activity-type"
+                        [value]="activity.type"
+                        [disabled]="isViewOnly"
+                        (change)="onActivityTypeChange($event.target.value, idx)"
+                        required>
+                  <option *ngIf="activity" [value]="activity.type" hidden selected>
+                    {{ activity.type }}
+                  </option>
+                  <option *ngFor="let activityType of activityTypes"
+                          [value]="activityType">
+                    {{ activityType }}
+                  </option>
+                </select>
+              </div>
+            </div>
+            <div class="form-item">
+              <div class="side-by-side">
+                <div class="form-item-big">
+                  <label>Workflow: </label>
+                  <input type="text"
+                     class="i-sdc-form-input"
+                     [disabled]="isViewOnly"
+                     (input)="onActivityValueChange($event.target.value, idx)"
+                     [value]="activity.workflow"
+                     [ngClass]="{'disabled': isViewOnly}"
+                     required/>
+                </div>
+                <div class="form-item-icon">
+                  <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromActivities(idx)"></span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="w-sdc-form-columns-wrapper">
+            <div class="validation-errors">
+              <ng-container *ngFor="let validation of validationMessages.activity">
+                <div class="input-error" *ngIf="activityFormArray.at(idx).hasError(validation.type);">
+                  {{ validation.message }}
+                </div>
+              </ng-container>
+            </div>
+          </div>
+          <div class="group-with-border content-row" *ngIf="dataTypeMap">
+            <input-list
+                [title]="'INPUT_LIST_TITLE' | translate"
+                [emptyMessage]="'INPUT_LIST_EMPTY' | translate"
+                [inputs]="getInputs(idx)"
+                [dataTypeMap]="dataTypeMap"
+                [isViewOnly]="isViewOnly"
+                [allowDeletion]="true"
+                [componentInstanceMap]="componentInstanceMap"
+                (onValueChange)="onInputValueChange($event, idx)"
+                (onDelete)="onInputDelete($event, idx)"
+            >
+            </input-list>
+          </div>
+          <div class="group-with-border content-row">
+              <app-add-input
+                  [dataTypeMap]="dataTypeMap$"
+                  [isView]="isViewOnly"
+                  [defaultType]="DEFAULT_INPUT_TYPE"
+                  [existingInputNames]="collectInputNames(idx)"
+                  (onAddInput)="onAddInput($event, idx)"
+              >
+              </app-add-input>
+          </div>
+        </div>
+        <div class="add-button-container group-with-border" *ngIf="!isViewOnly">
+          <a class="add-btn" data-tests-id="add-input.add-input-link"
+             (click)="addActivity()">Add Activity</a>
+        </div>
+      </div>
+    </form>
+  </div>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less
new file mode 100644 (file)
index 0000000..99a0534
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+@import '../../../../../../../assets/styles/variables.less';
+@import '../../../../../../../assets/styles/override.less';
+
+.group-with-border {
+    margin: 10px 0;
+    padding: 10px 0;
+    border-top: 1px solid @tlv_color_u;
+    .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+    }
+}
+
+.group-with-border-blue {
+    margin: 10px 0;
+    padding: 10px 0;
+    border-top: 1px solid @tlv_color_x;
+    .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+    }
+}
+
+.i-sdc-form-select, .i-sdc-form-input {
+    color: #5a5a5a;
+    font-family: OpenSans-Regular, sans-serif;
+    font-size: 14px;
+    background-color: #ffffff;
+    border-radius: 2px;
+    margin: 0;
+    padding: 0;
+    border: solid 1px #d2d2d2;
+    height: 30px;
+    width: 100%;
+    display: block;
+}
+
+.side-by-side {
+    display: flex;
+
+    .form-item {
+        flex: 1 1 auto;
+
+        &:first-child {
+            margin-right: 14px;
+            flex-basis: 37%;
+            flex-grow: 0;
+            flex-shrink: 0;
+        }
+
+        &:nth-child(3) {
+            margin-left: 14px;
+            flex: 0.4;
+        }
+    }
+
+    .form-item-big {
+        flex: 1 1 auto;
+        flex-grow: 0;
+        flex-basis: 90%;
+    }
+
+    .form-item-icon {
+        margin-left: auto;
+        margin-right: 14px;
+
+        .sprite-new {
+            margin-top: 110%;
+        }
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts
new file mode 100644 (file)
index 0000000..20b574b
--- /dev/null
@@ -0,0 +1,63 @@
+
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivitiesListComponent } from './activities-list.component';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { TranslateModule } from '../../../../../shared/translator/translate.module';
+import { AddInputComponent } from '../add-input/add-input.component';
+import { InputListComponent } from '../input-list/input-list.component';
+import { InputListItemComponent } from '../input-list/input-list-item/input-list-item.component';
+import { SdcUiComponentsModule } from "onap-ui-angular/dist";
+import { ToscaFunctionModule } from '../../../../properties-assignment/tosca-function/tosca-function.module';
+
+describe('ActivitiesListComponent', () => {
+  let component: ActivitiesListComponent;
+  let fixture: ComponentFixture<ActivitiesListComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ 
+        ActivitiesListComponent,
+        AddInputComponent,
+        InputListComponent,
+        InputListItemComponent,
+      ],
+      imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        TranslateModule,
+        SdcUiComponentsModule,
+        ToscaFunctionModule
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ActivitiesListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts
new file mode 100644 (file)
index 0000000..b265464
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import {
+  ActivityParameter,
+  IActivityParameterList,
+  InputOperationParameter,
+  IOperationParamsList,
+  ActivityTypesEnum
+} from "../../../../../../models/interfaceOperation";
+import { DataTypeModel } from "../../../../../../models/data-types";
+import { Observable } from "rxjs/Observable";
+import { InstanceFeDetails } from "../../../../../../models/instance-fe-details";
+import {
+  AbstractControl,
+  FormArray,
+  FormControl,
+  FormGroup, 
+  ValidationErrors,
+  ValidatorFn,
+  Validators
+} from '@angular/forms';
+
+@Component({
+  selector: 'activities-list',
+  templateUrl: './activities-list.component.html',
+  styleUrls: ['./activities-list.component.less']
+})
+export class ActivitiesListComponent implements OnInit {
+
+  @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map();
+  @Input() dataTypeMap: Map<string, DataTypeModel>;;
+  @Input() dataTypeMap$: Observable<Map<string, DataTypeModel>>;
+  @Input() existingActivities: IActivityParameterList;
+  @Input() isViewOnly: boolean;
+  @Output('activitiesChangeEvent') activitiesChangeEvent: EventEmitter<any> = new EventEmitter<any>();
+
+  readonly DEFAULT_INPUT_TYPE: string = "tosca.dataTypes.tmf.milestoneJeopardyData";
+  readonly DEFAULT_INPUT_NAME: string = "TMFMilestoneJeopardyData";
+  activityTypes: string[] = [];
+  activities: ActivityParameter[] = [];
+  activityFormArray: FormArray = new FormArray([]);
+  activityForm: FormGroup = new FormGroup (
+    {
+      'activityFormList': this.activityFormArray
+    }
+  );
+  validationMessages = {
+    activity: [
+      { type: 'required', message: 'Activity type and value is required'}
+    ]
+  };
+
+  ngOnInit() {
+    Object.keys(ActivityTypesEnum).forEach(key => {
+      this.activityTypes.push(ActivityTypesEnum[key])
+    });
+    this.activityForm.valueChanges.subscribe(() => {
+      this.emitOnActivityChange();
+    });
+    if (this.existingActivities && this.existingActivities.listToscaDataDefinition && this.existingActivities.listToscaDataDefinition.length > 0) {
+      this.existingActivities.listToscaDataDefinition.forEach(val => {
+        this.activities.push(val);
+        this.activityFormArray.push(
+          new FormControl(val, [Validators.required, this.formControlValidator()])
+        );
+      })
+    }
+  }
+
+  private emitOnActivityChange(): void {
+    this.activitiesChangeEvent.emit({
+      activities: this.activities,
+      valid: this.activityForm.valid
+    });
+  }
+
+  addActivity() {
+    let input = new class implements IOperationParamsList {
+      listToscaDataDefinition: Array<InputOperationParameter> = [];
+    }
+    let activityParameter: ActivityParameter = {
+      type: null,
+      workflow: null,
+      inputs: input
+    }
+    this.activities.push(activityParameter);
+    this.activityFormArray.push(
+      new FormControl(activityParameter, [Validators.required, this.formControlValidator()])
+    );
+
+    let index = this.activities.indexOf(activityParameter);
+    let inputOperationParameter: InputOperationParameter = new InputOperationParameter();
+    inputOperationParameter.name = this.DEFAULT_INPUT_NAME;
+    inputOperationParameter.type = this.DEFAULT_INPUT_TYPE;
+    inputOperationParameter.valid = true;
+    this.activities[index].inputs.listToscaDataDefinition.push(inputOperationParameter);
+    this.activities[index].inputs.listToscaDataDefinition = Array.from(this.activities[index].inputs.listToscaDataDefinition);
+  }
+
+  private formControlValidator(): ValidatorFn {
+    return (control: AbstractControl): ValidationErrors | null => {
+      const activity = control.value;
+      if (!activity || !activity.type || !activity.workflow) {
+        return {required:true};
+      }
+      return null;
+    }
+  }
+
+  removeFromActivities (index: number) {
+    this.activities.splice(index, 1);
+    this.activityFormArray.removeAt(index);
+  }
+
+  onActivityTypeChange(type: string, index: number) {
+    let activity = this.activityFormArray.controls[index].value;
+    activity.type = type;
+    this.activityFormArray.controls[index].setValue(activity);
+  }
+
+  onActivityValueChange (value: any, index: number) {
+    let activity = this.activityFormArray.controls[index].value;
+    activity.workflow = value;
+    this.activityFormArray.controls[index].setValue(activity);
+  }
+
+  collectInputNames(index: number) {
+    return this.activities[index].inputs.listToscaDataDefinition.map((input) => input.name);
+}
+
+  onAddInput(inputOperationParameter: InputOperationParameter, index: number) {
+    this.activities[index].inputs.listToscaDataDefinition.push(inputOperationParameter);
+    this.activities[index].inputs.listToscaDataDefinition = Array.from(this.activities[index].inputs.listToscaDataDefinition);
+  }
+
+  getInputs(index: number) {
+    if (this.activities[index].inputs.listToscaDataDefinition) {
+      let test: InputOperationParameter[] = this.activities[index].inputs.listToscaDataDefinition;
+      return test;
+    }
+    return {};
+  }
+
+  onInputValueChange(changedInput: InputOperationParameter, index: number) {
+    if (changedInput.value instanceof Object) {
+        changedInput.value = JSON.stringify(changedInput.value);
+    }
+    const inputOperationParameter = this.activities[index].inputs.listToscaDataDefinition.find(value => value.name == changedInput.name);
+    inputOperationParameter.toscaFunction = null;
+    inputOperationParameter.value = changedInput.value;
+    inputOperationParameter.subPropertyToscaFunctions = changedInput.subPropertyToscaFunctions;
+    if (changedInput.isToscaFunction()) {
+        inputOperationParameter.toscaFunction = changedInput.toscaFunction;
+        inputOperationParameter.value = changedInput.toscaFunction.buildValueString();
+    }
+}
+
+  onInputDelete(inputName: string, index: number) {
+    const currentInputs = this.activities[index].inputs.listToscaDataDefinition;
+    const input1 = currentInputs.find(value => value.name === inputName);
+    const indexOfInput = currentInputs.indexOf(input1);
+    if (indexOfInput === -1) {
+        console.error(`Could not delete input '${inputName}'. Input not found.`);
+        return;
+    }
+    currentInputs.splice(currentInputs.indexOf(input1), 1);
+    this.activities[index].inputs.listToscaDataDefinition = Array.from(currentInputs);
+  }
+}
index c757d5f..5620d19 100644 (file)
@@ -38,6 +38,7 @@ export class AddInputComponent implements OnInit {
   @Input('dataTypeMap') dataTypeMap$: Observable<Map<string, DataTypeModel>>;
   @Input('isView') isView: boolean;
   @Input() existingInputNames: Array<string> = [];
+  @Input('defaultType') defaultType: string;
   @Output('onAddInput') onAddInputEvent: EventEmitter<InputOperationParameter>;
 
   dataTypeMap: Map<string, DataTypeModel>;
@@ -143,6 +144,9 @@ export class AddInputComponent implements OnInit {
   }
 
   showAddInput() {
+    if (this.defaultType) {
+      this.inputToAdd.type = this.dataTypeMap.get(this.defaultType) ? this.defaultType : undefined;
+    }
     this.showForm = true;
     this.showAddLink = false;
   }
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html
new file mode 100644 (file)
index 0000000..3103421
--- /dev/null
@@ -0,0 +1,168 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+-->
+
+<div class="sub-operation-filters">
+  <div class="group-with-border content-row">
+    <form novalidate class="w-sdc-form two-columns" [formGroup]="filterForm">
+      <div formArrayName="filterFormList">
+        <label class="filters-label"> Filters </label>
+        <div *ngFor="let filter of filters; let idx = index">
+          <div class="side-by-side group-with-border">
+            <div class="form-item">
+              <div class="side-by-side">
+                <div class="form-item">
+                  <label class="sdc-timeout-label">Name: </label>
+                  <input type="text"
+                     class="i-sdc-form-input"
+                     [disabled]="isViewOnly"
+                     [value]="filter.name"
+                     [ngClass]="{'disabled': isViewOnly}"
+                     (input)="onFilterNameChange($event.target.value, idx)"
+                     required/>
+                </div>
+                <div class="form-item">
+                  <label>Constraint: </label>
+                  <div class="sdc-dropdown">
+                    <select class="i-sdc-form-select"
+                            data-tests-id="filter-type"
+                            [value]="filter.constraint"
+                            [disabled]="isViewOnly"
+                            (change)="onFilterConstraintChange($event.target.value, idx)"
+                            required>
+                      <option *ngIf="filter" [value]="filter.constraint" hidden selected>
+                        {{ ConstraintTypesMapping[filter.constraint] }}
+                      </option>
+                      <option *ngFor="let operatorType of operatorTypes"
+                            [value]="operatorType">
+                      {{ ConstraintTypesMapping[operatorType] }}
+                      </option>
+                    </select>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="form-item">
+              <div class="side-by-side">
+                <div class="form-item-big">
+                  <fieldset class="padding-zero" [disabled]="isViewOnly" >
+                    <label>Value: </label>
+                    <input type="radio" [name]="'hasGetFunctionValue.' + filter.name + idx"
+                          [value]="false"
+                          [checked]="!isToscaFunction(idx)"
+                          (change)="onValueTypeChange($event.target.value, idx)"/> Value
+                    <input type="radio" [name]="'hasGetFunctionValue.' + filter.name + idx"
+                          [checked]="isToscaFunction(idx)"
+                          (change)="onValueTypeChange($event.target.value, idx)"
+                          [value]="true"/> {{'TOSCA_FUNCTION_LABEL' | translate}}
+                  </fieldset>
+                  <div *ngIf="isToscaFunction(idx)">
+                    <div *ngIf="componentInstanceMap">
+                      <tosca-function
+                          [property]="getAsProperty(idx)"
+                          [allowClear]="false"
+                          [customToscaFunctions]="customToscaFunctions"
+                          [componentInstanceMap]="componentInstanceMap"
+                          (onValidityChange)="onToscaFunctionValidityChange($event, idx)">
+                      </tosca-function>
+                    </div>
+                    <div *ngIf="!componentInstanceMap">
+                      <tosca-function
+                          [property]="getAsProperty(idx)"
+                          [allowClear]="false"
+                          [customToscaFunctions]="customToscaFunctions"
+                          (onValidityChange)="onToscaFunctionValidityChange($event, idx)">
+                      </tosca-function>
+                    </div>
+                  </div>
+                  <div *ngIf="!isToscaFunction(idx)">
+                    <div *ngIf="filter.constraint == 'validValues'">
+                      <div class="add-btn padding-bottom"
+                          [ngClass]="{'disabled': isViewOnly}"
+                          (click)="addToList(idx)">
+                          Add to List
+                      </div>
+                      <div class="w-sdc-form-columns-wrapper padding-bottom" *ngFor="let value of constraintValuesArray(idx); let valueIndex = index; trackBy:trackByFn">
+                        <div class="w-sdc-form-column">
+                          <input type="text" required
+                            (change)="onChangeConstrainValueIndex(idx, $event.target.value, valueIndex)"
+                            [disabled]="isViewOnly"
+                            [value]="getInRangeValue(idx, valueIndex)"/>
+                        </div>
+                        <div class="w-sdc-form-column">
+                          <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromList(idx, valueIndex)"></span>
+                        </div>
+                      </div>
+                    </div>
+                    <div *ngIf="filter.constraint == 'inRange'">
+                      <div class="side-by-side">
+                        <div class="form-item-50">
+                          <input type="text" required
+                          (input)="onChangeConstrainValueIndex(idx, $event.target.value, 0)"
+                          [disabled]="isViewOnly"
+                          [value]="getInRangeValue(idx, 0)"/>
+                        </div>
+                        <div class="form-item-50">
+                          <input type="text" required
+                          (input)="onChangeConstrainValueIndex(idx, $event.target.value, 1)"
+                          [disabled]="isViewOnly"
+                          [value]="getInRangeValue(idx, 1)"/>
+                        </div>
+                      </div>
+                    </div>
+                    <div *ngIf="filter.constraint != 'inRange' && filter.constraint != 'validValues'">
+                      <input type="text"
+                      class="i-sdc-form-input"
+                      [disabled]="isViewOnly"
+                      [ngClass]="{'disabled': isViewOnly}"
+                      [value]="filter.filterValue"
+                      (input)="onFilterValueChange($event.target.value, idx)"
+                      required/>
+                    </div>
+                  </div>
+                </div>
+                <div class="form-item-icon">
+                  <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromFilters(idx)"></span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="w-sdc-form-columns-wrapper">
+            <div class="validation-errors">
+              <ng-container *ngFor="let validation of validationMessages.filter">
+                <div class="input-error" *ngIf="filterFormArray.at(idx).hasError(validation.type);">
+                  {{ validation.message }}
+                </div>
+              </ng-container>
+            </div>
+          </div>
+        </div>
+
+        <div class="add-button-container group-with-border" *ngIf="!isViewOnly && activitiesExist">
+          <a class="add-btn" data-tests-id="add-input.add-input-link"
+             (click)="addFilter()">Add Filter</a>
+        </div>
+        <div *ngIf="!activitiesExist">
+          Must have at leat one actifity before adding filters
+        </div>
+      </div>
+    </form>
+  </div>
+</div>
+
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less
new file mode 100644 (file)
index 0000000..a17b4b9
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+@import '../../../../../../../assets/styles/variables.less';
+@import '../../../../../../../assets/styles/override.less';
+
+.group-with-border {
+    margin: 10px 0;
+    padding: 10px 0;
+    border-top: 1px solid @tlv_color_u;
+    .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+    }
+}
+
+.group-with-border-blue {
+    margin: 10px 0;
+    padding: 10px 0;
+    border-top: 1px solid @tlv_color_x;
+    .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+    }
+}
+
+.i-sdc-form-select, .i-sdc-form-input {
+    color: #5a5a5a;
+    font-family: OpenSans-Regular, sans-serif;
+    font-size: 14px;
+    background-color: #ffffff;
+    border-radius: 2px;
+    margin: 0;
+    padding: 0;
+    border: solid 1px #d2d2d2;
+    height: 30px;
+    width: 100%;
+    display: block;
+}
+
+.padding-bottom {
+    padding-bottom: 10px;
+}
+
+.padding-zero {
+    padding: 0px;
+}
+
+.side-by-side {
+    display: flex;
+
+    .form-item {
+        flex: 1 1 auto;
+
+        &:first-child {
+            margin-right: 14px;
+            flex-basis: 37%;
+            flex-grow: 0;
+            flex-shrink: 0;
+        }
+
+        &:nth-child(3) {
+            margin-left: 14px;
+            flex: 0.4;
+        }
+    }
+
+    .form-item-50 {
+        flex: 1 1 auto;
+        flex-grow: 0;
+        flex-basis: 50%;
+
+        &:first-child {
+            margin-right: 14px;
+        }
+
+        &:nth-child(3) {
+            margin-left: 14px;
+        }
+    }
+
+    .form-item-big {
+        flex: 1 1 auto;
+        flex-grow: 0;
+        flex-basis: 90%;
+    }
+
+    .form-item-icon {
+        margin-left: auto;
+        margin-right: 14px;
+
+        .sprite-new {
+            margin-top: 110%;
+        }
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts
new file mode 100644 (file)
index 0000000..3b9fdbf
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FiltersListComponent } from './filters-list.component';
+import { TranslateModule } from '../../../../../shared/translator/translate.module';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ToscaFunctionModule } from '../../../../properties-assignment/tosca-function/tosca-function.module';
+import { SdcUiComponentsModule } from "onap-ui-angular/dist";
+
+describe('FiltersListComponent', () => {
+  let component: FiltersListComponent;
+  let fixture: ComponentFixture<FiltersListComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FiltersListComponent ],
+      imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        TranslateModule,
+        SdcUiComponentsModule,
+        ToscaFunctionModule
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FiltersListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts
new file mode 100644 (file)
index 0000000..5aaaea5
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges} from '@angular/core';
+import {
+  AbstractControl,
+  FormArray,
+  FormControl,
+  FormGroup, ValidationErrors,
+  ValidatorFn,
+  Validators
+} from '@angular/forms';
+import {
+  FilterParameter,
+  IFilterParameterList
+} from '../../../../../../models/interfaceOperation';
+import { InstanceFeDetails } from '../../../../../../models/instance-fe-details';
+import { ConstraintTypes, ConstraintTypesMapping } from '../../../../../pages/properties-assignment/constraints/constraints.component';
+import {CustomToscaFunction } from '../../../../../../models/default-custom-functions';
+import { ToscaFunction } from '../../../../../../models/tosca-function';
+import { PropertyBEModel } from '../../../../../../models/properties-inputs/property-be-model';
+import { ToscaFunctionValidationEvent } from '../../../../properties-assignment/tosca-function/tosca-function.component';
+
+@Component({
+  selector: 'filters-list',
+  templateUrl: './filters-list.component.html',
+  styleUrls: ['./filters-list.component.less']
+})
+export class FiltersListComponent implements OnInit {
+
+  @Input() customToscaFunctions: Array<CustomToscaFunction> = [];
+  @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map();
+  @Input() activitiesExist: boolean;
+  @Input() existingFilters: IFilterParameterList;
+  @Input() isViewOnly: boolean;
+  @Output('filtersChangeEvent') filtersChangeEvent: EventEmitter<any> = new EventEmitter<any>();
+
+  isAProperty: Map<number, PropertyBEModel> = new Map();
+  operatorTypes: any[];
+  ConstraintTypesMapping = ConstraintTypesMapping;
+  filters: FilterParameter[] = [];
+  filterFormArray: FormArray = new FormArray([]);
+  filterForm: FormGroup = new FormGroup (
+    {
+      'filterFormList': this.filterFormArray
+    }
+  );
+  validationMessages = {
+    filter: [
+      { type: 'required', message: 'Filter name, constraint, and value is required'}
+    ]
+  };
+
+  ngOnInit () {
+    this.operatorTypes = Object.keys(ConstraintTypes).map((key) => ConstraintTypes[key]);
+  }
+
+  ngOnChanges (changes: SimpleChanges) {
+    this.filterForm.valueChanges.subscribe(() => {
+      this.emitOnFilterChange();
+    });
+    if (!changes.activitiesExist) {
+      return;
+    }
+    if (changes.activitiesExist.currentValue) {
+      this.initFilters();
+    } else {
+      this.filters = [];
+      this.filterFormArray = new FormArray([]);
+      this. filterForm = new FormGroup (
+        {
+          'filterFormList': this.filterFormArray
+        }
+      );
+    }
+  }
+
+  private initFilters () {
+    if (this.existingFilters && this.existingFilters.listToscaDataDefinition && this.existingFilters.listToscaDataDefinition.length > 0) {
+      this.existingFilters.listToscaDataDefinition.forEach(val => {
+        this.filters.push(val);
+        this.filterFormArray.push(
+          new FormControl(val, [Validators.required, this.formControlValidator()])
+        );
+      })
+    }
+  }
+
+  private emitOnFilterChange (): void {
+    this.filtersChangeEvent.emit({
+      filters: this.filters,
+      valid: this.filterForm.valid
+    });
+  }
+
+  addFilter () {
+    let filterParameter: FilterParameter = {
+      name: null,
+      constraint: null,
+      filterValue: null
+    }
+    this.filters.push(filterParameter);
+    this.filterFormArray.push(
+      new FormControl(filterParameter, [Validators.required, this.formControlValidator()])
+    );
+  }
+
+  private formControlValidator (): ValidatorFn {
+    return (control: AbstractControl): ValidationErrors | null => {
+      const filter = control.value;
+
+      if (!filter || !filter.name || !filter.constraint || !filter.filterValue) {
+        return {required:true};
+      }
+      return null;
+    }
+  }
+
+  removeFromFilters (index: number) {
+    this.filters.splice(index, 1);
+    this.filterFormArray.removeAt(index);
+  }
+
+  onFilterConstraintChange (type: string, index: number) {
+    let filter = this.filterFormArray.controls[index].value;
+    filter.constraint = type;
+    if (ConstraintTypes.valid_values == type || ConstraintTypes.in_range == type) {
+      filter.filterValue = [];
+    } else {
+      filter.filterValue = '';
+    }
+    this.filterFormArray.controls[index].setValue(filter);
+  }
+
+  onFilterValueChange (value: any, index: number) {
+    let filter = this.filterFormArray.controls[index].value;
+    filter.filterValue = value;
+    this.filterFormArray.controls[index].setValue(filter);
+  }
+
+  onFilterNameChange (value: any, index: number) {
+    let filter = this.filterFormArray.controls[index].value;
+    filter.name = value;
+    this.filterFormArray.controls[index].setValue(filter);
+  }
+
+  getInRangeValue (index: number, valueIndex: number): string {
+    const value = this.filters[index].filterValue;
+
+    if (!value || !value[valueIndex]) {
+      return '';
+    }
+
+    return value[valueIndex];
+  }
+
+  onChangeConstrainValueIndex (index: number, newValue: any, valueIndex: number) {
+    let filter = this.filterFormArray.controls[index].value;
+    if (!filter.filterValue) {
+      filter.filterValue = [];
+    }
+    filter.filterValue[valueIndex] = newValue;
+    this.filterFormArray.controls[index].setValue(filter);
+  }
+
+  constraintValuesArray (index: number) {
+    let filters = this.filterForm.get('filterFormList') as FormArray;
+    return filters.at(index).value.filterValue;
+  }
+
+  addToList (filterIndex: number) {
+    this.constraintValuesArray(filterIndex).push('');
+  }
+
+  removeFromList (filterIndex: number, valueIndex: number) {
+    this.constraintValuesArray(filterIndex).splice(valueIndex, 1);
+  }
+
+  onValueTypeChange (value: string, index: number) {
+    if (value === 'true') {
+      let filter = this.filterFormArray.controls[index].value;
+      if (!filter.toscaFunction) {
+        filter.toscaFunction = {} as ToscaFunction;
+      }
+      filter.filterValue = '';
+      this.filterFormArray.controls[index].setValue(filter);
+    } else {
+      let filter = this.filterFormArray.controls[index].value;
+      filter.toscaFunction = undefined;
+      this.filterFormArray.controls[index].setValue(filter);
+      if (this.isAProperty.has(index)) {
+        this.isAProperty.delete(index);
+      }
+    }
+  }
+
+  getAsProperty(index: number) {
+    if (!this.isAProperty.has(index)) {
+      let filter = this.filterFormArray.controls[index].value;
+      let property = new PropertyBEModel();
+      property.type = 'any';
+      property.toscaFunction = filter.toscaFunction;
+      property.value = filter.filterValue;
+      this.isAProperty.set(index, property);
+      return property;
+    }
+    return this.isAProperty.get(index);
+  }
+
+  onToscaFunctionValidityChange(validationEvent: ToscaFunctionValidationEvent, index: number):void {
+    if (validationEvent.isValid) {
+      let filter = this.filterFormArray.controls[index].value;
+      filter.toscaFunction = validationEvent.toscaFunction;
+      filter.filterValue = validationEvent.toscaFunction.buildValueString();
+      this.filterFormArray.controls[index].setValue(filter);
+    }
+  }
+
+  isToscaFunction(index: number): boolean {
+    let filter = this.filterFormArray.controls[index].value;
+    let toscaFunction = filter.toscaFunction;
+    return toscaFunction;
+  }
+
+}
index 116b9eb..80dd252 100644 (file)
@@ -95,6 +95,7 @@ export class InputListItemComponent implements OnInit {
       this.property.toscaFunction = this.toscaFunction;
       this.valueObjRef = this.toscaFunction.value;
     } else {
+      this.property = this.property ? this.property : new PropertyBEModel();
       this.property.toscaFunction = undefined;
     }
   }
index 38aed30..7876555 100644 (file)
                 <option value="day">Days</option>
               </select>
         </div>
+
+        <div class="group-with-border content-row">
+            <label class="sub-operations-label"> Sub operations </label>
+            <tabs tabStyle="basic-tabs" [hideIndicationOnTabChange]="true" (tabChanged)="tabChanged($event)">
+                <div *ngFor="let milestone of milestones">
+                  <tab tabTitle="{{milestone}}" [active]="isActiveTab(milestone)" [highlight]="isInvalidActivity(milestone)">
+                    <filters-list
+                        [customToscaFunctions]="customToscaFunctions"
+                        [activitiesExist]="getExistingActivities(milestone) ? true : false"
+                        [isViewOnly]="isViewOnly"
+                        [componentInstanceMap]="componentInstanceMap"
+                        [existingFilters]="getExistingFilters(milestone)"
+                        (filtersChangeEvent)="filtersChangeEvent($event, milestone)">
+                    </filters-list>
+                    <activities-list
+                        [isViewOnly]="isViewOnly"
+                        [dataTypeMap]="dataTypeMap"
+                        [dataTypeMap$]="dataTypeMap$"
+                        [componentInstanceMap]="componentInstanceMap"
+                        [existingActivities]="getExistingActivities(milestone)"
+                        (activitiesChangeEvent)="activitiesChangeEvent($event, milestone)">
+                    </activities-list>
+                  </tab>
+                </div>
+              </tabs>
+        </div>
     </form>
 </div>
index 5c5ec1b..d3f02e7 100644 (file)
 import {Component, EventEmitter, Output, ViewChild} from '@angular/core';
 import { FormControl } from '@angular/forms';
 import {UIInterfaceModel} from "../interface-operations.component";
-import {InputOperationParameter, InterfaceOperationModel, IOperationParamsList} from "../../../../../models/interfaceOperation";
+import {
+    ActivityParameter,
+    IActivityParameterList,
+    IFilterParameterList,
+    InputOperationParameter,
+    InterfaceOperationModel,
+    IOperationParamsList,
+    FilterParameter,
+    Milestone,
+    MilestoneEnum
+} from "../../../../../models/interfaceOperation";
 import {TranslateService} from "../../../../shared/translator/translate.service";
 import {DropdownValue} from "../../../../components/ui/form-components/dropdown/ui-element-dropdown.component";
 import {ArtifactModel} from "../../../../../models/artifacts";
@@ -57,7 +67,9 @@ export class InterfaceOperationHandlerComponent {
         validityChangedCallback: Function;
         isViewOnly: boolean;
         isEdit: boolean;
-        validImplementationProps:boolean;
+        validImplementationProps: boolean;
+        validMilestoneActivities: boolean;
+        validMilestoneFilters: boolean;
         modelName: string;
     };
 
@@ -73,7 +85,9 @@ export class InterfaceOperationHandlerComponent {
     isLoading: boolean = false;
     isViewOnly: boolean;
     isEdit: boolean;
-    validImplementationProps:boolean;
+    validImplementationProps: boolean;
+    validMilestoneActivities: boolean;
+    validMilestoneFilters: boolean;
     interfaceTypes: Array<DropdownValue> = [];
     interfaceTypeOptions: Array<DropDownOption> = [];
     selectedInterfaceType: DropDownOption = undefined;
@@ -92,6 +106,10 @@ export class InterfaceOperationHandlerComponent {
     inputTypeOptions: any[];
     timeoutValue = new FormControl('');
     timeoutType = new FormControl('');
+    invalidMilestones: string[] = [];
+    activeTab: string;
+    milestones = Object.keys(MilestoneEnum)
+            .map(key => MilestoneEnum[key]);
 
     constructor(private dataTypeService: DataTypeService,
                 private componentServiceNg2: ComponentServiceNg2,
@@ -102,6 +120,8 @@ export class InterfaceOperationHandlerComponent {
         this.isViewOnly = this.input.isViewOnly;
         this.isEdit = this.input.isEdit;
         this.validImplementationProps = this.input.validImplementationProps;
+        this.validMilestoneActivities = this.input.validMilestoneActivities;
+        this.validMilestoneFilters = this.input.validMilestoneFilters;
         this.componentInstanceMap =  this.input.componentInstanceMap ? this.input.componentInstanceMap : null;
         this.interfaceType = this.input.selectedInterface.type;
         this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation);
@@ -125,6 +145,9 @@ export class InterfaceOperationHandlerComponent {
                 this.timeoutType.setValue("day");
             }
         }
+        if (!this.operationToUpdate.milestones) {
+            this.operationToUpdate.milestones = {};
+        }
         this.initCustomToscaFunctions();
         this.initInputs();
         this.removeImplementationQuote();
@@ -441,6 +464,107 @@ export class InterfaceOperationHandlerComponent {
         this.operationToUpdate.operationType = dropDownOption ? dropDownOption.value : undefined;
         this.selectedInterfaceOperation = dropDownOption ? dropDownOption : undefined;
     }
+
+    getExistingFilters(key: string) {
+        if (this.operationToUpdate.milestones[key] && this.operationToUpdate.milestones[key].filters) {
+            return this.operationToUpdate.milestones[key].filters
+        }
+        return undefined;
+    }
+
+    filtersChangeEvent($event: any, milestone: string) {
+        if ($event.valid) {
+            if (this.invalidMilestones.indexOf(milestone) > -1) {
+                this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1);
+                this.validMilestoneFilters = this.invalidMilestones.length < 1;
+                this.validMilestoneActivities = this.invalidMilestones.length < 1;
+            }
+            let operationMilestone = this.operationToUpdate.milestones[milestone];
+            if (!operationMilestone) {
+                operationMilestone = new Milestone();
+            }
+            operationMilestone.filters = new class implements IFilterParameterList {
+                listToscaDataDefinition: Array<FilterParameter> = [];
+            }
+            let milestoneFilters = $event.filters;
+            for (let filter of milestoneFilters) {
+                let filterParameter = new FilterParameter();
+                filterParameter.constraint = filter.constraint;
+                filterParameter.name = filter.name;
+                filterParameter.filterValue = filter.filterValue;
+                filterParameter.toscaFunction = filter.toscaFunction;
+                operationMilestone.filters.listToscaDataDefinition.push(filterParameter);
+            }
+            this.operationToUpdate.milestones[milestone] = operationMilestone;
+        } else {
+            if (this.invalidMilestones.indexOf(milestone) == -1) {
+                this.invalidMilestones.push(milestone);
+            }
+            this.validMilestoneFilters = false;
+            this.validMilestoneActivities = false;
+        }
+    }
+
+    getExistingActivities(key: string) {
+        if (
+            this.operationToUpdate.milestones[key]
+            && this.operationToUpdate.milestones[key].activities
+            && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition
+            && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition.length > 0
+        ) {
+            return this.operationToUpdate.milestones[key].activities
+        }
+        return undefined;
+    }
+
+    activitiesChangeEvent($event: any, milestone: string) {
+        if ($event.valid) {
+            if (this.invalidMilestones.indexOf(milestone) > -1) {
+                this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1);
+                this.validMilestoneActivities = this.invalidMilestones.length < 1;
+                this.validMilestoneFilters  = this.invalidMilestones.length < 1;
+            }
+            let operationMilestone = this.operationToUpdate.milestones[milestone];
+            if (!operationMilestone) {
+                operationMilestone = new Milestone();
+            }
+            operationMilestone.activities = new class implements IActivityParameterList {
+                listToscaDataDefinition: Array<ActivityParameter> = [];
+            }
+            let milestoneActivities = $event.activities;
+            for (let activity of milestoneActivities) {
+                let activityParameter = new ActivityParameter();
+                activityParameter.type = activity.type;
+                activityParameter.workflow = activity.workflow;
+                activityParameter.inputs = activity.inputs;
+                operationMilestone.activities.listToscaDataDefinition.push(activityParameter);
+            }
+            this.operationToUpdate.milestones[milestone] = operationMilestone;
+        } else {
+            if (this.invalidMilestones.indexOf(milestone) == -1) {
+                this.invalidMilestones.push(milestone);
+            }
+            this.validMilestoneActivities = false;
+            this.validMilestoneFilters = false;
+        }
+    }
+
+    isActiveTab(title: string): boolean {
+        if (this.activeTab) {
+            return this.activeTab == title;
+        }
+        return this.milestones[0] == title;
+    }
+
+    tabChanged = (event) => {
+        this.activeTab = event.title;
+      }
+
+    isInvalidActivity(title: string) {
+        if (this.invalidMilestones.indexOf(title) > -1) {
+            return "#cf2a2a";
+        }
+      }
 }
 
 class DropDownOption implements IDropDownOption {
index 3fa20ab..f79de44 100644 (file)
 *  ============LICENSE_END=========================================================
 */
 
-import {NgModule} from "@angular/core";
-import {CommonModule} from "@angular/common";
+import { NgModule} from '@angular/core';
+import { CommonModule} from '@angular/common';
 
-import {FormsModule, ReactiveFormsModule} from "@angular/forms";
-import {BrowserModule} from '@angular/platform-browser';
-import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module";
-import {TranslateModule} from "app/ng2/shared/translator/translate.module";
-import {AddInputComponent} from './add-input/add-input.component';
-import {InputListComponent} from './input-list/input-list.component';
-import {InputListItemComponent} from './input-list/input-list-item/input-list-item.component';
-import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component";
-import {InterfaceOperationHandlerComponent} from "./interface-operation-handler.component";
-import {SdcUiComponentsModule} from "onap-ui-angular/dist";
-import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module";
-import {PropertyTableModule} from "app/ng2/components/logic/properties-table/property-table.module";
-import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function/tosca-function.module';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module';
+import { TranslateModule } from 'app/ng2/shared/translator/translate.module';
+import { AddInputComponent } from './add-input/add-input.component';
+import { InputListComponent } from './input-list/input-list.component';
+import { InputListItemComponent } from './input-list/input-list-item/input-list-item.component';
+import { PropertyParamRowComponent } from './property-param-row/property-param-row.component';
+import { InterfaceOperationHandlerComponent } from './interface-operation-handler.component';
+import { SdcUiComponentsModule } from 'onap-ui-angular/dist';
+import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module';
+import { PropertyTableModule } from 'app/ng2/components/logic/properties-table/property-table.module';
+import { ToscaFunctionModule } from '../../../properties-assignment/tosca-function/tosca-function.module';
+import { TabModule } from 'app/ng2/components/ui/tabs/tabs.module';
+import { FiltersListComponent } from './filters-list/filters-list.component';
+import { ActivitiesListComponent } from './activities-list/activities-list.component';
 
 @NgModule({
     declarations: [
@@ -42,7 +45,9 @@ import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function
         PropertyParamRowComponent,
         AddInputComponent,
         InputListComponent,
-        InputListItemComponent
+        InputListItemComponent,
+        FiltersListComponent,
+        ActivitiesListComponent
     ],
     imports: [
         CommonModule,
@@ -54,7 +59,8 @@ import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function
         UiElementsModule,
         PropertyTableModule,
         ReactiveFormsModule,
-        ToscaFunctionModule
+        ToscaFunctionModule,
+        TabModule
     ],
     exports: [
         PropertyParamRowComponent,
index 23c855e..82afb0a 100644 (file)
@@ -156,7 +156,9 @@ export class InterfaceDefinitionComponent {
     openOperation: OperationModel;
     enableWorkflowAssociation: boolean;
     workflowIsOnline: boolean;
-    validImplementationProps:boolean = true;
+    validImplementationProps: boolean = true;
+    validMilestoneActivities: boolean = true;
+    validMilestoneFilters: boolean = true;
     serviceInterfaces: InterfaceModel[];
 
     @Input() component: IComponent;
@@ -186,7 +188,6 @@ export class InterfaceDefinitionComponent {
     ngOnInit(): void {
         this.isLoading = true;
         this.interfaces = [];
-        //this.disableFlag = this.readonly;
         this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner'));
         Observable.forkJoin(
             this.ComponentServiceNg2.getInterfaceOperations(this.component),
@@ -218,7 +219,7 @@ export class InterfaceDefinitionComponent {
                 });
                 this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
                 this.loadingInstances = false;
-                
+
             };
             if (this.enableWorkflowAssociation && this.workflowIsOnline) {
                 this.WorkflowServiceNg2.getWorkflows().subscribe(
@@ -275,7 +276,6 @@ export class InterfaceDefinitionComponent {
             }
             this.interfaces = newInterfaces.map((interf) => new UIInterfaceModel(interf));
         } else {
-            //this.disableFlag = this.readonly;
             this.interfaces = this.serviceInterfaces.map((interf) => new UIInterfaceModel(interf));
         }
         this.sortInterfaces();
@@ -299,7 +299,12 @@ export class InterfaceDefinitionComponent {
         if (this.component.isService()) {
             return disable;
         }
-    
+        const validMilestoneActivities = this.modalInstance.instance.dynamicContent.instance.validMilestoneActivities;
+        const validMilestoneFilters = this.modalInstance.instance.dynamicContent.instance.validMilestoneFilters;
+        if (!validMilestoneActivities || !validMilestoneFilters) {
+            return disable;
+        }
+
         let selectedInterfaceOperation = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceOperation;
         let isInterfaceOperation:boolean = !(typeof selectedInterfaceOperation == 'undefined' || _.isEmpty(selectedInterfaceOperation));
         let selectedInterfaceType = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceType;
@@ -344,6 +349,8 @@ export class InterfaceDefinitionComponent {
                 validityChangedCallback: this.disableSaveButton,
                 isViewOnly: this.readonly,
                 validImplementationProps: this.validImplementationProps,
+                validMilestoneActivities: this.validMilestoneActivities,
+                validMilestoneFilters: this.validMilestoneFilters,
                 'isEdit': isEdit,
                 interfaceTypesMap: this.interfaceTypesMap,
                 modelName: this.component.model
index b4e184a..92398e8 100644 (file)
@@ -181,13 +181,16 @@ export class CommonUtils {
                         newOperation.interfaceType = interf.type;
                         newOperation.interfaceId = interf.uniqueId;
 
-                        const {inputs, outputs} = operation;
+                        const {inputs, outputs, milestones} = operation;
                         if (inputs) {
                             newOperation.createInputsList(inputs.listToscaDataDefinition);
                         }
                         if (outputs) {
                             newOperation.createOutputsList(outputs.listToscaDataDefinition);
                         }
+                        if (milestones) {
+                            newOperation.milestones = milestones;
+                        }
 
                         return newOperation;
                     }