summaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/pages/composition
diff options
context:
space:
mode:
authorandre.schmid <andre.schmid@est.tech>2020-11-18 18:13:58 +0000
committerChristophe Closset <christophe.closset@intl.att.com>2021-01-15 13:59:55 +0000
commitbd5a1006210092f9ac5c48352cc94f6264e961ef (patch)
treea91d4fc711dacb4e9833a1f7ff5134ff8407c931 /catalog-ui/src/app/ng2/pages/composition
parent3849231a17930b1bb2ba09af15673bfd07538b9d (diff)
Initial support for relationship_templates
Change-Id: Ia246b9f11a77815c0585abfa0b3de5433728001a Issue-ID: SDC-3435 Signed-off-by: andre.schmid <andre.schmid@est.tech>
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/composition')
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts2
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts43
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.html53
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.less73
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.spec.ts125
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.ts72
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.html141
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less211
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.spec.ts234
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.ts235
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/model/operation.ts39
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.css0
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.html118
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.spec.ts49
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.ts111
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.html35
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.less29
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.spec.ts66
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.ts163
19 files changed, 1792 insertions, 7 deletions
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts
index 45a7d4c576..c48231f2c6 100644
--- a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts
@@ -65,6 +65,7 @@ import {
import { CompositionGraphLinkUtils } from './utils/composition-graph-links-utils';
import { CompositionGraphPaletteUtils } from './utils/composition-graph-palette-utils';
import { ServicePathGraphUtils } from './utils/composition-graph-service-path-utils';
+import { RelationshipOperationsStepComponent } from "app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component";
declare const window: any;
@@ -266,6 +267,7 @@ export class CompositionGraphComponent implements AfterViewInit {
steps.push(new StepModel(fromNodeName, FromNodeStepComponent));
steps.push(new StepModel(toNodeName, ToNodeStepComponent));
steps.push(new StepModel('Properties', PropertiesStepComponent));
+ steps.push(new StepModel('Operations', RelationshipOperationsStepComponent));
const wizardTitle = 'Connect: ' + fromNodeName + ' to ' + toNodeName;
const modalInstance = this.modalService.createMultiStepsWizard(wizardTitle, steps, this.createLinkFromMenu, ConnectionWizardHeaderComponent);
modalInstance.instance.open();
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts
index 80464dc970..5039e573db 100644
--- a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts
@@ -9,6 +9,19 @@ import {FormElementsModule} from "../../../../components/ui/form-components/form
import {ConnectionWizardHeaderComponent} from "./connection-wizard-header/connection-wizard-header.component";
import {ConnectionPropertiesViewComponent} from "./connection-properties-view/connection-properties-view.component";
import {BrowserModule} from "@angular/platform-browser";
+import {RelationshipOperationsStepComponent} from './relationship-operations-step/relationship-operations-step.component';
+import {InterfaceOperationModule} from "../../../interface-operation/interface-operation.module";
+import {UiElementsModule} from "../../../../components/ui/ui-elements.module";
+import {TranslateModule} from "../../../../shared/translator/translate.module";
+import {SvgIconModule} from "onap-ui-angular/dist/svg-icon/svg-icon.module";
+import {OperationCreatorModule} from "../../../interface-operation/operation-creator/operation-creator.module";
+import {CreateInterfaceOperationComponent} from './create-interface-operation/create-interface-operation.component';
+import {DropdownModule} from "onap-ui-angular/dist/form-elements/dropdown/dropdown.module";
+import {InputModule} from "onap-ui-angular/dist/form-elements/text-elements/input/input.module";
+import {FormsModule, ReactiveFormsModule} from "@angular/forms";
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {CreateInputRowComponent} from './create-interface-operation/create-input-row/create-input-row.component';
+import {InterfaceOperationListComponent} from './relationship-operations-step/interface-operation-list/interface-operation-list.component';
@NgModule({
declarations: [
@@ -16,24 +29,40 @@ import {BrowserModule} from "@angular/platform-browser";
ToNodeStepComponent,
PropertiesStepComponent,
ConnectionWizardHeaderComponent,
- ConnectionPropertiesViewComponent
- ],
- imports: [
- FormElementsModule,
- PropertyTableModule,
- SelectRequirementOrCapabilityModule,
- BrowserModule
+ ConnectionPropertiesViewComponent,
+ RelationshipOperationsStepComponent,
+ CreateInterfaceOperationComponent,
+ CreateInputRowComponent,
+ InterfaceOperationListComponent
],
+ imports: [
+ FormElementsModule,
+ PropertyTableModule,
+ SelectRequirementOrCapabilityModule,
+ BrowserModule,
+ InterfaceOperationModule,
+ UiElementsModule,
+ TranslateModule,
+ SvgIconModule,
+ OperationCreatorModule,
+ DropdownModule,
+ InputModule,
+ FormsModule,
+ SdcUiComponentsModule,
+ ReactiveFormsModule
+ ],
exports: [
FromNodeStepComponent,
ToNodeStepComponent,
PropertiesStepComponent,
+ RelationshipOperationsStepComponent,
ConnectionWizardHeaderComponent,
ConnectionPropertiesViewComponent
],
entryComponents: [FromNodeStepComponent,
ToNodeStepComponent,
PropertiesStepComponent,
+ RelationshipOperationsStepComponent,
ConnectionWizardHeaderComponent,
ConnectionPropertiesViewComponent
],
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.html
new file mode 100644
index 0000000000..5181f32711
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.html
@@ -0,0 +1,53 @@
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ Copyright (C) 2021 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=========================================================
+ -->
+
+<div class="cell field-name">
+
+ <div [formGroup]="formGroup">
+ <input id="propertyAssignmentNameInput" class="value-input" formControlName="name"/>
+ </div>
+ <div *ngIf="formName.invalid && (formName.dirty || formName.touched)"
+ class="input-error">
+ <div *ngIf="formName.errors.required">
+ {{'OPERATION_INPUT_NAME_REQUIRED' | translate}}
+ </div>
+ </div>
+</div>
+
+<div class="cell field-type">
+ {{propertyAssignment.type}}
+</div>
+
+<div class="cell field-property">
+ <div [formGroup]="formGroup">
+ <input id="propertyAssignmentValueInput" class="value-input"
+ formControlName="value"/>
+ </div>
+</div>
+
+<div class="cell remove" *ngIf="!isReadOnly">
+ <svg-icon
+ name="trash-o"
+ mode="info"
+ size="small"
+ [attr.data-tests-id]="'propertyAssignment-remove-' + (propertyAssignment.name || 'unnamed')"
+ (click)="onDelete(propertyAssignment)"
+ [clickable]="true">
+ </svg-icon>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.less
new file mode 100644
index 0000000000..316d49e406
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.less
@@ -0,0 +1,73 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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';
+
+.remove {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg-icon {
+ position: relative;
+ right: -3px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+}
+
+
+.cell {
+ min-height: 50px;
+ padding: 10px;
+ display: flex;
+ align-items: center;
+
+ > * {
+ flex-basis: 100%;
+ }
+
+ /deep/ select {
+ height: 30px;
+ }
+
+ input {
+ height: 30px;
+ padding-left: 10px;
+ text-indent: 6px;
+ border: solid 1px @main_color_o;
+ }
+
+ select {
+ width: 100%;
+ }
+
+ &.field-property {
+ &:last-child {
+ flex: 1;
+ }
+
+ .no-properties-error {
+ color: @func_color_q;
+ font-style: italic;
+ }
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.spec.ts
new file mode 100644
index 0000000000..cc1fd52273
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.spec.ts
@@ -0,0 +1,125 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {CreateInputRowComponent} from './create-input-row.component';
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {ReactiveFormsModule} from "@angular/forms";
+import {TranslatePipe} from "../../../../../../shared/translator/translate.pipe";
+import {PropertyAssignment} from "../../../../../../../models/properties-inputs/property-assignment";
+
+describe('CreateInputRowComponent', () => {
+ let component: CreateInputRowComponent;
+ let fixture: ComponentFixture<CreateInputRowComponent>;
+ const nameField = 'name';
+ const valueField = 'value';
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ CreateInputRowComponent, TranslatePipe ],
+ imports: [ SdcUiComponentsModule, ReactiveFormsModule ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CreateInputRowComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('form is invalid when empty', () => {
+ expect(component.formGroup.valid).toBeFalsy();
+ });
+
+ it('name field validity', () => {
+ expect(component.formGroup.valid).toBe(false);
+ expect(component.formGroup.contains(nameField)).toBe(true);
+ const nameFormControl = component.formGroup.get(nameField);
+ expect(nameFormControl.valid).toBeFalsy();
+ let validationErrors = nameFormControl.errors || {};
+ expect(validationErrors['required']).toBeTruthy();
+
+ nameFormControl.setValue('');
+ validationErrors = nameFormControl.errors || {};
+ expect(validationErrors['required']).toBeTruthy();
+
+ nameFormControl.setValue('a');
+ expect(nameFormControl.valid).toBeTruthy();
+ });
+
+ it('value field validity', () => {
+ expect(component.formGroup.valid).toBeFalsy();
+ expect(component.formGroup.contains(valueField)).toBeTruthy();
+ const valueFormControl = component.formGroup.get(valueField);
+ expect(valueFormControl.valid).toBeTruthy();
+ });
+
+ it('test set value when form valid', () => {
+ expect(component.formGroup.valid).toBeFalsy();
+ expect(component.propertyAssignment.name).toBeFalsy();
+ expect(component.propertyAssignment.value).toBeFalsy();
+ const nameFormCtrl = component.formGroup.get(nameField);
+ nameFormCtrl.setValue('aName');
+ const valueFormCtrl = component.formGroup.get(valueField);
+ valueFormCtrl.setValue('aValue');
+ expect(component.formGroup.valid).toBeTruthy();
+ expect(component.propertyAssignment.name).toBe('aName');
+ expect(component.propertyAssignment.value).toBe('aValue');
+ });
+
+ it('test propertyAssignment initialization', () => {
+ const propertyAssignment = new PropertyAssignment();
+ propertyAssignment.name = 'aName';
+ propertyAssignment.value = 'aValue';
+ component.propertyAssignment = propertyAssignment;
+ component.ngOnInit();
+ expect(component.formGroup.valid).toBeTruthy();
+ const nameFormCtrl = component.formGroup.get(nameField);
+ expect(nameFormCtrl.value).toBe(propertyAssignment.name);
+ const valueFormCtrl = component.formGroup.get(valueField);
+ expect(valueFormCtrl.value).toBe(propertyAssignment.value);
+ });
+
+ it('test propertyAssignment form binding', () => {
+ const propertyAssignment = new PropertyAssignment();
+ component.propertyAssignment = propertyAssignment;
+ component.ngOnInit();
+ const nameFormCtrl = component.formGroup.get(nameField);
+ nameFormCtrl.setValue('anotherName');
+ const valueFormCtrl = component.formGroup.get(valueField);
+ valueFormCtrl.setValue('anotherValue');
+ expect(nameFormCtrl.value).toBe(propertyAssignment.name);
+ expect(valueFormCtrl.value).toBe(propertyAssignment.value);
+ });
+
+ it('test input deletion', () => {
+ const expectedPropertyAssignment = new PropertyAssignment();
+ let actualPropertyAssignment = null;
+ component.onDeleteEvent.subscribe((value) => actualPropertyAssignment = value);
+ component.onDelete(expectedPropertyAssignment);
+ expect(actualPropertyAssignment).toBe(expectedPropertyAssignment);
+ });
+
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.ts
new file mode 100644
index 0000000000..629c5f3e49
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-input-row/create-input-row.component.ts
@@ -0,0 +1,72 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {PropertyAssignment} from "../../../../../../../models/properties-inputs/property-assignment";
+import {FormControl, FormGroup, Validators} from "@angular/forms";
+import {filter} from "rxjs/operators";
+
+@Component({
+ selector: 'app-create-input-row',
+ templateUrl: './create-input-row.component.html',
+ styleUrls: ['./create-input-row.component.less']
+})
+export class CreateInputRowComponent implements OnInit {
+
+ @Input() propertyAssignment: PropertyAssignment;
+ @Input() isReadOnly: boolean;
+ @Output('onDelete') onDeleteEvent: EventEmitter<PropertyAssignment> = new EventEmitter();
+ formGroup: FormGroup;
+
+ constructor() { }
+
+ ngOnInit() {
+ if (!this.propertyAssignment) {
+ this.propertyAssignment = new PropertyAssignment();
+ }
+ this.formGroup = new FormGroup({
+ name: new FormControl(this.propertyAssignment.name, [
+ Validators.required
+ ]),
+ value: new FormControl(this.propertyAssignment.value)
+ });
+
+ this.formGroup.statusChanges
+ .pipe(
+ filter(() => this.formGroup.valid))
+ .subscribe(() => this.onFormValid());
+ }
+
+ onDelete(propertyAssignment: PropertyAssignment) {
+ this.onDeleteEvent.emit(propertyAssignment);
+ }
+
+ get formName() {
+ return this.formGroup.get('name');
+ }
+
+ get formValue() {
+ return this.formGroup.get('value');
+ }
+
+ private onFormValid() {
+ this.propertyAssignment.name = this.formName.value;
+ this.propertyAssignment.value = this.formValue.value;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.html
new file mode 100644
index 0000000000..a53135c6a9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.html
@@ -0,0 +1,141 @@
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ Copyright (C) 2021 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=========================================================
+ -->
+
+<div class="operation-creator">
+ <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+ <form [formGroup]="form">
+ <div class="w-sdc-form">
+
+ <div class="side-by-side">
+ <div class="form-item">
+ <sdc-dropdown
+ label="{{ 'OPERATION_INTERFACE_TYPE' | translate }}"
+ [required]="true"
+ testId="interface-name"
+ [selectedOption]="selectedInterfaceType"
+ placeHolder="Select..."
+ [disabled]="isReadOnly"
+ (changed)="onSelectInterfaceType($event)"
+ [options]="interfaceTypeOptions">
+ </sdc-dropdown>
+ <div *ngIf="interfaceTypeFormCtrl.invalid && (interfaceTypeFormCtrl.dirty || interfaceTypeFormCtrl.touched)"
+ class="input-error">
+ <div *ngIf="interfaceTypeFormCtrl.errors.required">
+ {{'OPERATION_INTERFACE_REQUIRED_ERROR' | translate}}
+ </div>
+ </div>
+ </div>
+
+ <div class="form-item">
+ <sdc-dropdown
+ #operationDropdown
+ label="{{ 'OPERATION_NAME' | translate }}"
+ [required]="true"
+ testId="operation-name"
+ [selectedOption]="selectedOperation"
+ placeHolder="Select..."
+ [disabled]="isReadOnly"
+ (changed)="onSelectOperation($event)"
+ [options]="operationOptions">
+ </sdc-dropdown>
+ <div *ngIf="operationTypeFormCtrl.invalid && (operationTypeFormCtrl.dirty || operationTypeFormCtrl.touched)"
+ class="input-error">
+ <div *ngIf="operationTypeFormCtrl.errors.required">
+ {{'OPERATION_OPERATION_REQUIRED_ERROR' | translate}}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="i-sdc-form-item sdc-input">
+ <label for="implementationInput" class="sdc-label__label required">{{ 'OPERATION_IMPLEMENTATION' | translate }}</label>
+ <div class="sdc-input-wrapper">
+ <input id="implementationInput" formControlName="implementation" required="required" class="sdc-input__input"/>
+ </div>
+ <div *ngIf="implementationFormCtrl.invalid && (implementationFormCtrl.dirty || implementationFormCtrl.touched)"
+ class="input-error">
+ <div *ngIf="implementationFormCtrl.errors.required">
+ {{'OPERATION_IMPLEMENTATION_REQUIRED_ERROR' | translate}}
+ </div>
+ <div *ngIf="implementationFormCtrl.errors.minLength">
+ minLength
+ </div>
+ </div>
+ </div>
+
+ <div class="separator-buttons">
+ <tabs tabStyle="round-tabs" [hideIndicationOnTabChange]="true">
+ <tab tabTitle="Inputs"></tab>
+ </tabs>
+ <a
+ class="add-param-link add-btn"
+ *ngIf="!isReadOnly"
+ data-tests-id="addInputParameter"
+ [ngClass]="{'disabled':isReadOnly}"
+ (click)="addInput()">{{ 'OPERATION_ADD_INPUT' | translate }}</a>
+ </div>
+
+ <div class="generic-table">
+ <div class="header-row table-row">
+ <span class="cell header-cell field-name">{{ 'OPERATION_PARAM_NAME' | translate }}</span>
+ <span class="cell header-cell field-type">{{ 'OPERATION_PARAM_TYPE' | translate }}</span>
+ <span class="cell header-cell field-property">
+ {{ 'OPERATION_PARAM_VALUE' | translate }}
+ </span>
+ <span class="cell header-cell remove" *ngIf="!isReadOnly">●●●</span>
+ </div>
+ <div *ngIf="!validateInputs()"
+ class="input-error">
+ <div *ngIf="inputErrorMap.get('duplicatedName')">
+ {{ 'OPERATION_INPUT_NAME_UNIQUE_ERROR' | translate }}
+ </div>
+ <div *ngIf="inputErrorMap.get('invalidName')">
+ {{ 'OPERATION_INPUT_NAME_ERROR' | translate }}
+ </div>
+ </div>
+
+ <app-create-input-row
+ *ngFor="let input of inputs$ | async"
+ class="data-row"
+ [formGroup]=""
+ [propertyAssignment]="input"
+ (onDelete)="onDeleteInput($event)"
+ [isReadOnly]="isReadOnly">
+ </app-create-input-row>
+ </div>
+ <div class="create-interface-operation-footer">
+ <sdc-button class="create-operation-btn"
+ testId="addBtn"
+ [type]="'primary'"
+ [size]="'small'"
+ [text]="'OPERATION_ADD' | translate"
+ [disabled]="form.invalid"
+ (click)="createOperation()">
+ </sdc-button>
+ <sdc-button class="cancel-operation-btn"
+ testId="cancelBtn"
+ [type]="'secondary'"
+ [size]="'small'"
+ [text]="'OPERATION_CANCEL' | translate"
+ (click)="onClickCancel()">
+ </sdc-button>
+ </div>
+ </div>
+ </form>
+</div> \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less
new file mode 100644
index 0000000000..4c7f8aba48
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less
@@ -0,0 +1,211 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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';
+
+.operation-creator {
+ font-family: @font-opensans-regular;
+ user-select: none;
+ padding-top: 12px;
+ padding-bottom: 20px;
+
+ .i-sdc-form-label {
+ font-size: 12px;
+ }
+
+ .w-sdc-form .i-sdc-form-item {
+ margin-bottom: 15px;
+ }
+
+ textarea {
+ min-height: 74px;
+ margin-bottom: 18px;
+ }
+
+ /deep/ .sdc-dropdown__component-container {
+ .sdc-dropdown__header {
+ height: 38px;
+ line-height: 35px;
+
+ svg-icon {
+ margin: 13px 6px;
+ }
+ }
+ }
+
+ /deep/ .sdc-input {
+ margin-bottom: 0;
+
+ .sdc-input__input {
+ height: 38px;
+ }
+ }
+
+ .side-by-side {
+ display: flex;
+
+ .form-item {
+ flex: 1;
+
+ &:first-child {
+ margin-right: 14px;
+ flex-basis: 37%;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ &:nth-child(3) {
+ margin-left: 14px;
+ flex: 0.4;
+ }
+
+ .i-sdc-form-file-upload {
+ height: 37px;
+ margin-bottom: 0;
+
+ .i-sdc-form-file-name {
+ padding: 8px 10px;
+ }
+
+ .i-sdc-form-file-upload-x-btn {
+ top: 13px;
+ }
+
+ .file-upload-browse-btn {
+ height: 100%;
+ padding: 7px 6px;
+ z-index: 1;
+ }
+ }
+
+ }
+ }
+
+ .archive-warning {
+ font-family: @font-opensans-bold;
+ color: @main_color_i;
+ }
+
+ .no-workflow-warning {
+ font-family: @font-opensans-bold;
+ color: @sdcui_color_red;
+ float: right;
+ }
+
+ .input-param-title {
+ font-size: 16px;
+ text-transform: uppercase;
+ }
+
+ .separator-buttons {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 10px;
+
+ .add-param-link {
+ &:not(.disabled):hover {
+ cursor: pointer;
+ }
+ }
+
+ .tab {
+ width: 84px;
+ text-align: center;
+ }
+ }
+
+ .generic-table {
+ max-height: 244px;
+ min-height: 91px;
+ background: @main_color_p;
+
+ .header-row .header-cell {
+ .info-icon {
+ float: right;
+ position: relative;
+ top: 2px;
+ }
+ /deep/ .tooltip-inner {
+ padding: 2px;
+ max-width: 270px;
+ font-size: 11px;
+ }
+ &.remove {
+ padding: 10px;
+ font-size: 10px;
+ }
+ }
+
+ .data-row {
+ &.empty-msg {
+ .bold-message {
+ font-family: @font-opensans-bold;
+ }
+
+ :first-child {
+ &:not(:only-child) {
+ margin: 6px 0;
+ }
+ }
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 14px;
+ }
+ }
+
+ /deep/ .cell {
+ &.field-name, &.field-type {
+ flex: 1;
+ }
+
+ &.field-property {
+ &, &:last-child {
+ flex: 1;
+ }
+ }
+
+ &.field-mandatory {
+ flex: 0.5;
+ text-align: center;
+ }
+
+ &.remove {
+ min-width: 40px;
+ max-width: 40px;
+ }
+ }
+
+ }
+
+ .create-interface-operation-footer {
+ border-top: solid 1px #eaeaea;
+ padding: 10px;
+ margin: 10px;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ sdc-button {
+ margin: 0 5px;
+ }
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.spec.ts
new file mode 100644
index 0000000000..8b830e0e62
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.spec.ts
@@ -0,0 +1,234 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {CreateInterfaceOperationComponent} from './create-interface-operation.component';
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {CreateInputRowComponent} from "./create-input-row/create-input-row.component";
+import {FormControl, ReactiveFormsModule} from "@angular/forms";
+import {TabModule} from "../../../../../components/ui/tabs/tabs.module";
+import {UiElementsModule} from "../../../../../components/ui/ui-elements.module";
+import {TranslateService} from "../../../../../shared/translator/translate.service";
+import {TranslatePipe} from "../../../../../shared/translator/translate.pipe";
+import {Operation} from "./model/operation";
+import {PropertyAssignment} from "../../../../../../models/properties-inputs/property-assignment";
+
+describe('CreateInterfaceOperationComponent', () => {
+ let component: CreateInterfaceOperationComponent;
+ let fixture: ComponentFixture<CreateInterfaceOperationComponent>;
+ const interfaceTypeFormName = 'interfaceType';
+ const operationTypeFormName = 'operationType';
+ const implementationFormName = 'implementation';
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [CreateInterfaceOperationComponent, CreateInputRowComponent, TranslatePipe],
+ imports: [SdcUiComponentsModule, ReactiveFormsModule, TabModule,
+ UiElementsModule],
+ providers: [
+ {provide: TranslateService, useValue: {}}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CreateInterfaceOperationComponent);
+ component = fixture.componentInstance;
+ component.interfaceTypeMap = new Map<string, Array<string>>();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('form is invalid when empty', () => {
+ component.ngOnInit();
+ expect(component.form.valid).toBeFalsy();
+ });
+
+ it('interface type field validity', () => {
+ component.ngOnInit();
+ expect(component.form.valid).toBeFalsy();
+ expect(component.form.contains(interfaceTypeFormName)).toBeTruthy();
+ assertRequiredFormCtrl(component.interfaceTypeFormCtrl, 'anInterfaceType');
+ });
+
+ it('operation type field validity', () => {
+ component.ngOnInit();
+ expect(component.form.valid).toBeFalsy();
+ expect(component.form.contains(operationTypeFormName)).toBeTruthy();
+ assertRequiredFormCtrl(component.operationTypeFormCtrl, 'anOperationType');
+ });
+
+ it('implementation type field validity', () => {
+ component.ngOnInit();
+ expect(component.form.valid).toBeFalsy();
+ expect(component.form.contains(implementationFormName)).toBeTruthy();
+ assertRequiredFormCtrl(component.implementationFormCtrl, 'just/an/implementation.sh');
+ });
+
+ it('test loadInterfaceOptions on init', () => {
+ const interfaceTypeMap = new Map<string, Array<string>>();
+ interfaceTypeMap.set('interface3', new Array<string>());
+ interfaceTypeMap.set('interface4', new Array<string>());
+ interfaceTypeMap.set('interface2', new Array<string>());
+ interfaceTypeMap.set('interface1', new Array<string>());
+ component.interfaceTypeMap = interfaceTypeMap;
+ component.ngOnInit();
+ expect(component.interfaceTypeOptions.length).toBe(4);
+ expect(component.interfaceTypeOptions[0].label).toBe('interface1');
+ expect(component.interfaceTypeOptions[0].value).toBe('interface1');
+ expect(component.interfaceTypeOptions[1].label).toBe('interface2');
+ expect(component.interfaceTypeOptions[1].value).toBe('interface2');
+ expect(component.interfaceTypeOptions[2].label).toBe('interface3');
+ expect(component.interfaceTypeOptions[2].value).toBe('interface3');
+ expect(component.interfaceTypeOptions[3].label).toBe('interface4');
+ expect(component.interfaceTypeOptions[3].value).toBe('interface4');
+ });
+
+ it('test loadOperationOptions on init', () => {
+ const interfaceTypeMap = new Map<string, Array<string>>();
+ const interface1Operations = new Array<string>('interface1Operation3',
+ 'interface1Operation1', 'interface1Operation2', 'interface1Operation4');
+ interfaceTypeMap.set('interface1', interface1Operations);
+ component.interfaceTypeMap = interfaceTypeMap;
+ component.selectedInterfaceType = {
+ value: 'interface1',
+ type: null,
+ label: 'interface1',
+ }
+ component.ngOnInit();
+ expect(component.operationOptions.length).toBe(4);
+ expect(component.operationOptions[0].label).toBe('interface1Operation1');
+ expect(component.operationOptions[0].value).toBe('interface1Operation1');
+ expect(component.operationOptions[1].label).toBe('interface1Operation2');
+ expect(component.operationOptions[1].value).toBe('interface1Operation2');
+ expect(component.operationOptions[2].label).toBe('interface1Operation3');
+ expect(component.operationOptions[2].value).toBe('interface1Operation3');
+ expect(component.operationOptions[3].label).toBe('interface1Operation4');
+ expect(component.operationOptions[3].value).toBe('interface1Operation4');
+ });
+
+ it('test onSelectInterfaceType', () => {
+ const interfaceTypeMap = new Map<string, Array<string>>();
+ interfaceTypeMap.set('interface1', new Array<string>('', '', '', ''));
+ interfaceTypeMap.set('interface2', new Array<string>());
+ component.interfaceTypeMap = interfaceTypeMap;
+ component.ngOnInit();
+ const selectedInterfaceType = {
+ value: 'interface1',
+ type: null,
+ label: 'interface1',
+ }
+ component.onSelectInterfaceType(selectedInterfaceType);
+ expect(component.selectedInterfaceType).toBe(selectedInterfaceType);
+ expect(component.interfaceTypeFormCtrl.value).toBe(selectedInterfaceType.value);
+ expect(component.operationOptions.length).toBe(4);
+ });
+
+ it('test onSelectOperation', () => {
+ component.ngOnInit();
+
+ const selectedOperationType = {
+ value: 'operation1',
+ type: null,
+ label: 'operation1',
+ }
+ component.onSelectOperation(selectedOperationType);
+ expect(component.selectedOperation).toBe(selectedOperationType);
+ expect(component.operationTypeFormCtrl.value).toBe(selectedOperationType.value);
+ });
+
+ it('test onChangeImplementation', () => {
+ component.ngOnInit();
+ let implementation = null;
+ component.onChangeImplementation(implementation);
+ expect(component.implementation).toBe(implementation);
+ implementation = '';
+ component.onChangeImplementation(implementation);
+ expect(component.implementation).toBe(null);
+ implementation = ' ';
+ component.onChangeImplementation(implementation);
+ expect(component.implementation).toBe('');
+ implementation = 'implementation';
+ component.onChangeImplementation(implementation);
+ expect(component.implementation).toBe(implementation);
+ });
+
+ it('test createOperation with valid operation', () => {
+ component.ngOnInit();
+ const interfaceType = 'interfaceType';
+ const operationType = 'operationType';
+ const implementation = 'implementation';
+ component.interfaceTypeFormCtrl.setValue(interfaceType);
+ component.operationTypeFormCtrl.setValue(operationType);
+ component.implementationFormCtrl.setValue(implementation);
+ const inputs = new Array<PropertyAssignment>();
+ const input1 = new PropertyAssignment('string');
+ input1.name = 'input1';
+ input1.value = 'input1Value';
+ inputs.push(input1)
+ component.inputs = inputs;
+ let operation: Operation = null;
+ component.addOperation.subscribe((operation1) => {
+ operation = operation1;
+ });
+ component.createOperation();
+ expect(operation).toBeTruthy();
+ expect(operation.interfaceType).toBe(interfaceType);
+ expect(operation.operationType).toBe(operationType);
+ expect(operation.implementation).toBe(implementation);
+ expect(operation.inputs).toBe(inputs);
+ });
+
+ it('test createOperation with invalid operation', () => {
+ component.ngOnInit();
+ let operation: Operation = null;
+ component.addOperation.subscribe((operation1) => {
+ operation = operation1;
+ });
+ component.createOperation();
+ expect(operation).toBe(null);
+ });
+
+ it('test onDeleteInput with not found input', () => {
+ component.ngOnInit();
+ const input1 = new PropertyAssignment('string');
+ const input2 = new PropertyAssignment('string');
+ input2.name = 'input2';
+ const input3 = new PropertyAssignment('string');
+ input3.name = 'input3';
+ component.inputs = new Array<PropertyAssignment>(input1, input2, input3);
+ component.onDeleteInput(input2);
+ expect(component.inputs.length).toBe(2);
+ expect(component.inputs.find(input => input.name === input2.name)).toBeFalsy();
+ });
+
+ function assertRequiredFormCtrl(formControl: FormControl, valueToSet: any): void {
+ expect(formControl.valid).toBeFalsy();
+ let validationErrors = formControl.errors || {};
+ expect(validationErrors['required']).toBeTruthy();
+ formControl.setValue('');
+ expect(validationErrors['required']).toBeTruthy();
+ formControl.setValue(valueToSet);
+ expect(formControl.valid).toBeTruthy();
+ }
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.ts
new file mode 100644
index 0000000000..9990ac8484
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.ts
@@ -0,0 +1,235 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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, ViewChild} from '@angular/core';
+import {IDropDownOption} from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models";
+import {DropDownComponent} from "onap-ui-angular/dist/components";
+import {PropertyAssignment} from "../../../../../../models/properties-inputs/property-assignment";
+import {Observable} from "rxjs";
+import {Operation} from "./model/operation";
+import {FormControl, FormGroup, Validators} from "@angular/forms";
+
+@Component({
+ selector: 'app-create-interface-operation',
+ templateUrl: './create-interface-operation.component.html',
+ styleUrls: ['./create-interface-operation.component.less']
+})
+export class CreateInterfaceOperationComponent implements OnInit {
+ @Input('interfaceTypeMap') interfaceTypeMap: Map<string, Array<string>>;
+ @Input('operation') private operation: Operation;
+ @Output('addOperation') addOperation: EventEmitter<Operation> = new EventEmitter<Operation>();
+ @ViewChild('operationDropdown') operationDropdown: DropDownComponent;
+
+ form: FormGroup;
+ isLoading: boolean;
+ isReadOnly: boolean;
+ currentInOutTab: string;
+ interfaceTypeOptions: Array<TypedDropDownOption>;
+ operationOptions: Array<TypedDropDownOption>;
+ selectedInterfaceType: TypedDropDownOption;
+ selectedOperation: TypedDropDownOption;
+ implementation: string;
+ inputs$: Observable<Array<PropertyAssignment>>;
+ inputs: Array<PropertyAssignment>;
+ inputErrorMap: Map<string, boolean>;
+ validationMessageList: Array<string>;
+ interfaceTypeFormCtrl: FormControl;
+ operationTypeFormCtrl: FormControl;
+ implementationFormCtrl: FormControl;
+
+ TYPE_INPUT = 'Inputs';
+ TYPE_OUTPUT = 'Outputs';
+
+
+ constructor() {
+ this.currentInOutTab = this.TYPE_INPUT;
+ this.inputErrorMap = new Map<string, boolean>();
+ }
+
+ ngOnInit() {
+ if (!this.operation) {
+ this.operation = new Operation();
+ }
+
+ this.interfaceTypeFormCtrl = new FormControl(this.operation.interfaceType, [
+ Validators.required
+ ]);
+ this.operationTypeFormCtrl = new FormControl(this.operation.operationType, [
+ Validators.required
+ ]);
+ this.implementationFormCtrl = new FormControl(this.operation.implementation, [
+ Validators.required
+ ]);
+ this.form = new FormGroup({
+ interfaceType: this.interfaceTypeFormCtrl,
+ operationType: this.operationTypeFormCtrl,
+ implementation: this.implementationFormCtrl
+ });
+
+ this.isLoading = true;
+ this.isReadOnly = false;
+ this.loadInterfaceOptions();
+ this.loadOperationOptions();
+ this.loadOperationInputs();
+ this.isLoading = false;
+ }
+
+ private loadInterfaceOptions() {
+ this.interfaceTypeOptions = new Array<TypedDropDownOption>();
+ const interfaceTypeList = Array.from(this.interfaceTypeMap.keys());
+ interfaceTypeList.sort();
+ interfaceTypeList.forEach(interfaceType => {
+ this.interfaceTypeOptions.push(this.createInterfaceDropdownOption(interfaceType));
+ });
+ }
+
+ private loadOperationOptions() {
+ this.operationOptions = new Array<TypedDropDownOption>();
+ if (!this.selectedInterfaceType) {
+ return;
+ }
+
+ const operationArray: Array<string> = this.interfaceTypeMap.get(this.selectedInterfaceType.value);
+ operationArray.sort();
+ operationArray.forEach(operationName =>
+ this.operationOptions.push(new TypedDropDownOption(operationName, operationName))
+ );
+ this.operationDropdown.allOptions = <IDropDownOption[]> this.operationOptions;
+ }
+
+ private loadOperationInputs() {
+ this.inputs = new Array<PropertyAssignment>();
+ this.inputs$ = Observable.of(this.inputs);
+ }
+
+ descriptionValue(): string {
+ return this.operation.description;
+ }
+
+ addInput() {
+ this.inputs.push(new PropertyAssignment('string'));
+ }
+
+ onSelectInterfaceType(selectedOption: IDropDownOption) {
+ this.selectedInterfaceType = <TypedDropDownOption> selectedOption;
+ this.operation.interfaceType = selectedOption.value;
+ this.interfaceTypeFormCtrl.setValue(this.operation.interfaceType);
+ this.loadOperationOptions();
+ }
+
+ private createInterfaceDropdownOption(type: string) {
+ let label = type;
+ const lastDot = label.lastIndexOf('.');
+ if (lastDot > -1) {
+ label = label.substr(lastDot + 1);
+ }
+ return new TypedDropDownOption(type, label);
+ }
+
+ onSelectOperation(selectedOption: IDropDownOption) {
+ this.selectedOperation = <TypedDropDownOption> selectedOption;
+ this.operation.operationType = selectedOption.value;
+ this.operationTypeFormCtrl.setValue(this.operation.operationType);
+ }
+
+ onChangeImplementation(implementation: string) {
+ this.implementation = implementation ? implementation.trim() : null;
+ this.operation.implementation = this.implementation;
+ }
+
+ onDeleteInput(input: PropertyAssignment): void {
+ const index = this.inputs.indexOf(input);
+ this.inputs.splice(index, 1);
+ }
+
+ createOperation() {
+ this.form.updateValueAndValidity();
+ if (this.isValid()) {
+ this.operation.interfaceType = this.interfaceTypeFormCtrl.value;
+ this.operation.operationType = this.operationTypeFormCtrl.value;
+ this.operation.implementation = this.implementationFormCtrl.value;
+ if (this.inputs) {
+ this.operation.inputs = this.inputs;
+ }
+ this.addOperation.emit(this.operation);
+ }
+ }
+
+ onClickCancel() {
+ this.addOperation.emit(null);
+ }
+
+ private isValid(): boolean {
+ if (this.form.invalid) {
+ return false;
+ }
+
+ return this.validateInputs();
+ }
+
+ validateInputs(): boolean {
+ this.inputErrorMap = new Map<string, boolean>();
+ if (!this.inputs) {
+ return true;
+ }
+
+ const inputNameSet = new Set<string>();
+ this.inputs.forEach(value => {
+ if (value.name) {
+ value.name = value.name.trim();
+ if (!value.name) {
+ this.inputErrorMap.set('invalidName', true);
+ }
+ } else {
+ this.inputErrorMap.set('invalidName', true);
+ }
+ if (value.value) {
+ value.value = value.value.trim();
+ }
+ //for later check of duplicate input name
+ inputNameSet.add(value.name);
+ });
+
+ if (inputNameSet.size != this.inputs.length) {
+ this.inputErrorMap.set('duplicatedName', true);
+ }
+
+ return this.inputErrorMap.size == 0;
+ }
+
+}
+
+class DropDownOption implements IDropDownOption {
+ value: string;
+ label: string;
+
+ constructor(value: string, label?: string) {
+ this.value = value;
+ this.label = label || value;
+ }
+}
+
+class TypedDropDownOption extends DropDownOption {
+ type: string;
+
+ constructor(value: string, label?: string, type?: string) {
+ super(value, label);
+ this.type = type;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/model/operation.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/model/operation.ts
new file mode 100644
index 0000000000..d44481dbf9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/model/operation.ts
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {PropertyAssignment} from "../../../../../../../models/properties-inputs/property-assignment";
+
+export class Operation {
+ interfaceType: string;
+ operationType: string;
+ implementation: string;
+ description: string;
+ inputs: Array<PropertyAssignment>;
+
+
+ constructor(operation?:Operation) {
+ if (operation) {
+ this.interfaceType = operation.interfaceType;
+ this.operationType = operation.operationType;
+ this.implementation = operation.implementation;
+ this.description = operation.description;
+ this.inputs = operation.inputs;
+ }
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.css b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.css
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.html
new file mode 100644
index 0000000000..360af52dbf
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.html
@@ -0,0 +1,118 @@
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ Copyright (C) 2021 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=========================================================
+ -->
+
+<div class="operation-list">
+
+ <div
+ class="empty-list-container"
+ *ngIf="!(interfaceOperationList$ | async)?.length">
+ <span>No operation provided</span>
+ </div>
+
+ <div *ngIf="(interfaceOperationList$ | async)?.length">
+
+ <div class="expand-collapse">
+ <a
+ class="link"
+ data-tests-id="expand-all"
+ [ngClass]="{'disabled': isAllExpanded()}"
+ (click)="toggleAllExpand()">
+ {{ 'INTERFACE_EXPAND_ALL' | translate }}
+ </a> |
+ <a
+ class="link"
+ data-tests-id="collapse-all"
+ [ngClass]="{'disabled': isAllCollapsed()}"
+ (click)="toggleAllCollapse()">
+ {{ 'INTERFACE_COLLAPSE_ALL' | translate }}
+ </a>
+ </div>
+
+ <div
+ class="interface-row"
+ *ngFor="let interfaceType of getKeys(interfaceTypeMap)">
+
+ <div
+ class="interface-accordion"
+ (click)="toggleCollapse(interfaceType)">
+ <span
+ class="chevron-container"
+ [ngClass]="{'isCollapsed': isInterfaceCollapsed(interfaceType)}">
+ <svg-icon
+ name="caret1-down-o"
+ mode="primary"
+ size="small">
+ </svg-icon>
+ </span>
+ <span class="interface-name">{{interfaceType}}</span>
+ </div>
+
+ <div class="generic-table" *ngIf="isInterfaceCollapsed(interfaceType)">
+ <div class="header-row table-row">
+ <span
+ class="cell header-cell field-name">
+ {{ 'OPERATION_NAME' | translate }}
+ </span>
+ <span
+ class="cell header-cell field-description">
+ {{ 'OPERATION_IMPLEMENTATION' | translate }}
+ </span>
+ <span
+ class="cell header-cell field-actions header-actions">
+ ●●●
+ </span>
+ </div>
+ <div
+ class="data-row"
+ *ngFor="let operation of interfaceTypeMap.get(interfaceType)"
+ [attr.data-tests-id]="'operation-' + operation.operationType"
+ (click)="onEditOperation(operation)">
+ <span
+ class="cell field-name"
+ [attr.data-tests-id]="'operation-' + operation.operationType + '-name'">
+ {{operation.operationType}}
+ </span>
+ <span
+ class="cell field-description"
+ [attr.data-tests-id]="'operation-' + operation.operationType + '-implementation'">
+ {{operation.implementation}}
+ </span>
+ <span class="cell field-actions">
+ <span
+ class="delete-action"
+ [attr.data-tests-id]="'remove-operation-' + operation.operationType"
+ (click)="onRemoveOperation($event, operation)">
+ <svg-icon
+ *ngIf="!isReadOnly"
+ name="trash-o"
+ mode="info"
+ size="small"
+ [clickable]="true">
+ </svg-icon>
+ </span>
+ </span>
+ </div>
+
+ </div>
+
+ </div>
+
+ </div>
+
+</div> \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.spec.ts
new file mode 100644
index 0000000000..584edf01d3
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.spec.ts
@@ -0,0 +1,49 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {InterfaceOperationListComponent} from './interface-operation-list.component';
+import {UiElementsModule} from "../../../../../../components/ui/ui-elements.module";
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {TranslatePipe} from "../../../../../../shared/translator/translate.pipe";
+import {TranslateService} from "../../../../../../shared/translator/translate.service";
+
+describe('InterfaceOperationListComponent', () => {
+ let component: InterfaceOperationListComponent;
+ let fixture: ComponentFixture<InterfaceOperationListComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [InterfaceOperationListComponent, TranslatePipe],
+ imports: [SdcUiComponentsModule]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(InterfaceOperationListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.ts
new file mode 100644
index 0000000000..e14baf060b
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/interface-operation-list/interface-operation-list.component.ts
@@ -0,0 +1,111 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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 {BehaviorSubject} from "rxjs";
+import {Operation} from "../../create-interface-operation/model/operation";
+
+@Component({
+ selector: 'app-interface-operation-list',
+ templateUrl: './interface-operation-list.component.html',
+ styleUrls: ['./interface-operation-list.component.css']
+})
+export class InterfaceOperationListComponent implements OnInit {
+
+ @Input('readonly') isReadOnly: boolean;
+ @Input() set interfaceOperationList(value: Array<Operation>) {
+ this.interfaceOperationList$.next(value);
+ }
+ @Output('onRemoveOperation') onRemoveOperationEmitter: EventEmitter<Operation> = new EventEmitter<Operation>();
+
+ interfaceOperationList$: BehaviorSubject<Array<Operation>>;
+ interfaceTypeMap: Map<string, Array<Operation>>;
+ expandCollapseControlMap: Map<string, boolean>;
+
+ constructor() {
+ this.interfaceOperationList$ = new BehaviorSubject<Array<Operation>>(new Array<Operation>());
+ this.expandCollapseControlMap = new Map<string, boolean>();
+ }
+
+ ngOnInit() {
+ this.loadInterfaces();
+ }
+
+ private loadInterfaces() {
+ this.interfaceOperationList$.subscribe(operationArray => {
+ this.interfaceTypeMap = new Map<string, Array<Operation>>();
+ operationArray.forEach(operation => {
+ if (this.interfaceTypeMap.has(operation.interfaceType)) {
+ let operations = this.interfaceTypeMap.get(operation.interfaceType);
+ operations.push(operation);
+ operations.sort((a, b) => a.operationType.localeCompare(b.operationType));
+ this.interfaceTypeMap.set(operation.interfaceType, operations);
+ } else {
+ this.interfaceTypeMap.set(operation.interfaceType, new Array(operation))
+ }
+ if (!this.expandCollapseControlMap.has(operation.interfaceType)) {
+ this.expandCollapseControlMap.set(operation.interfaceType, true);
+ }
+ });
+ });
+ }
+
+ toggleAllExpand() {
+ this.toggleAll(true);
+ }
+
+ toggleAllCollapse() {
+ this.toggleAll(false);
+ }
+
+ private toggleAll(toggle: boolean) {
+ for (const key of Array.from(this.expandCollapseControlMap.keys())) {
+ this.expandCollapseControlMap.set(key, toggle);
+ }
+ }
+
+ isAllExpanded(): boolean {
+ return Array.from(this.expandCollapseControlMap.values()).every(value => value);
+ }
+
+ isAllCollapsed(): boolean {
+ return Array.from(this.expandCollapseControlMap.values()).every(value => !value);
+ }
+
+
+ onRemoveOperation($event: MouseEvent, operation: any) {
+ this.onRemoveOperationEmitter.emit(operation);
+ }
+
+ onEditOperation(operation?: any) {
+
+ }
+
+ getKeys(interfaceTypeMap: Map<string, Array<Operation>>) {
+ return Array.from(interfaceTypeMap.keys());
+ }
+
+ toggleCollapse(interfaceType: string) {
+ this.expandCollapseControlMap.set(interfaceType, !this.expandCollapseControlMap.get(interfaceType));
+ }
+
+ isInterfaceCollapsed(interfaceType: string): boolean {
+ return this.expandCollapseControlMap.get(interfaceType);
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.html
new file mode 100644
index 0000000000..7c49af88c3
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.html
@@ -0,0 +1,35 @@
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ Copyright (C) 2021 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=========================================================
+ -->
+<div class="relationship-operations-container">
+ <div id="relationship-operation-actions" class="actions">
+ <a
+ class="add-param-link add-btn"
+ *ngIf="!enableAddOperation"
+ (click)="addOperation()">{{ 'OPERATION_ADD1' | translate }}</a>
+ </div>
+ <div id="operation-list-container" class="operation-list-container">
+ <app-interface-operation-list [interfaceOperationList]="operationList" [readonly]="false" (onRemoveOperation)="onRemoveOperation($event)"></app-interface-operation-list>
+ </div>
+ <div class="operations-create-container">
+ <div>
+ <app-create-interface-operation *ngIf="enableAddOperation" [interfaceTypeMap]="interfaceTypeMap" (addOperation)="operationAdded($event)">
+ </app-create-interface-operation>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.less
new file mode 100644
index 0000000000..b2ac1892d9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.less
@@ -0,0 +1,29 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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=========================================================
+ */
+
+.relationship-operations-container {
+ .actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 10px;
+ }
+ .operation-list-container {
+ margin: 10px 0 10px 0;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.spec.ts
new file mode 100644
index 0000000000..9dcf998940
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.spec.ts
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 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 { RelationshipOperationsStepComponent } from './relationship-operations-step.component';
+import {CreateInterfaceOperationComponent} from "../create-interface-operation/create-interface-operation.component";
+import {InterfaceOperationListComponent} from "./interface-operation-list/interface-operation-list.component";
+import {TranslatePipe} from "../../../../../shared/translator/translate.pipe";
+import {TranslateService} from "../../../../../shared/translator/translate.service";
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {CreateInputRowComponent} from "../create-interface-operation/create-input-row/create-input-row.component";
+import {ReactiveFormsModule} from "@angular/forms";
+import {TabModule} from "../../../../../components/ui/tabs/tabs.module";
+import {UiElementsModule} from "../../../../../components/ui/ui-elements.module";
+import {RouterModule} from "@angular/router";
+import {APP_BASE_HREF} from "@angular/common";
+import {ConnectionWizardService} from "../connection-wizard.service";
+import {ComponentServiceNg2} from "../../../../../services/component-services/component.service";
+
+describe('RelationshipOperationsStepComponent', () => {
+ let component: RelationshipOperationsStepComponent;
+ let fixture: ComponentFixture<RelationshipOperationsStepComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ RelationshipOperationsStepComponent, CreateInterfaceOperationComponent,
+ CreateInputRowComponent, InterfaceOperationListComponent, TranslatePipe ],
+ providers: [
+ {provide: TranslateService, useValue: {}},
+ {provide: '$stateParams', useValue: {}},
+ {provide: ConnectionWizardService, useValue: {}},
+ {provide: ComponentServiceNg2, useValue: {}},
+ ],
+ imports: [SdcUiComponentsModule, ReactiveFormsModule, TabModule, UiElementsModule]
+
+
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RelationshipOperationsStepComponent);
+ component = fixture.componentInstance;
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.ts
new file mode 100644
index 0000000000..d595c2b8f6
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component.ts
@@ -0,0 +1,163 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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, Inject, OnInit} from '@angular/core';
+import {IStepComponent} from "../../../../../../models/wizard-step";
+import {ConnectionWizardService} from "../connection-wizard.service";
+import {Component as IComponent} from "../../../../../../models/components/component";
+import {ComponentServiceNg2} from "../../../../../services/component-services/component.service";
+import {Observable} from "rxjs";
+import {Operation} from "../create-interface-operation/model/operation";
+
+@Component({
+ selector: 'app-relationship-operations-step',
+ templateUrl: './relationship-operations-step.component.html',
+ styleUrls: ['./relationship-operations-step.component.less']
+})
+export class RelationshipOperationsStepComponent implements OnInit, IStepComponent {
+
+ private connectionWizardService: ConnectionWizardService;
+ private componentService: ComponentServiceNg2;
+ interfaceTypeMap: Map<string, Array<string>>;
+ component: IComponent;
+ operationList: Array<Operation>;
+ operationList$: Observable<Array<Operation>>;
+ enableAddOperation: boolean;
+
+ constructor(@Inject('$stateParams') private stateParams,
+ connectionWizardService: ConnectionWizardService,
+ componentService: ComponentServiceNg2) {
+ this.component = stateParams.component;
+ this.componentService = componentService;
+ this.connectionWizardService = connectionWizardService;
+ this.interfaceTypeMap = new Map<string, Array<string>>();
+ }
+
+ ngOnInit() {
+ this.loadOperationList();
+ this.loadInterfaceTypeMap();
+ }
+
+ private loadOperationList(): void {
+ if (this.connectionWizardService.selectedMatch.operations) {
+ this.operationList = this.connectionWizardService.selectedMatch.operations.slice();
+ } else {
+ this.operationList = new Array<Operation>();
+ }
+ this.operationList$ = Observable.of(this.operationList);
+ }
+
+ private loadInterfaceTypeMap(): void {
+ this.componentService.getInterfaceTypes(null).subscribe(response => {
+ for (const interfaceType in response) {
+ let operationList = response[interfaceType];
+ //ignore interfaceTypes that doesn't contain operations
+ if (operationList && operationList.length > 0) {
+ //remove operations already on the list
+ const existingOperations =
+ this.operationList.filter(operation => operation.interfaceType === interfaceType);
+ operationList = operationList
+ .filter(operationType => !existingOperations.find(operation => operation.operationType === operationType));
+ if (operationList && operationList.length > 0) {
+ operationList.sort();
+ this.interfaceTypeMap.set(interfaceType, operationList);
+ }
+ }
+ }
+ });
+ }
+
+ preventBack(): boolean {
+ return false;
+ }
+
+ preventNext(): boolean {
+ return false;
+ }
+
+ addOperation() {
+ this.enableAddOperation = !this.enableAddOperation;
+ }
+
+ operationAdded(operation: Operation) {
+ this.enableAddOperation = false;
+ if (operation) {
+ const foundOperation = this.operationList
+ .find(operation1 => operation1.interfaceType === operation.interfaceType
+ && operation1.operationType === operation.operationType);
+ if (foundOperation) {
+ return;
+ }
+ this.operationList.push(operation);
+ this.operationList = this.operationList.slice();
+ this.connectionWizardService.selectedMatch.addToOperations(operation);
+ this.removeFromInterfaceMap(operation);
+ }
+ }
+
+ onRemoveOperation(operation: Operation) {
+ if (!this.operationList) {
+ return;
+ }
+ const index = this.operationList.indexOf(operation);
+ if (index > -1) {
+ this.operationList.splice(index, 1);
+ this.operationList = this.operationList.slice();
+ this.connectionWizardService.selectedMatch.removeFromOperations(operation);
+ this.addToInterfaceMap(operation);
+ }
+ }
+
+ private removeFromInterfaceMap(operation: Operation) {
+ if (!this.interfaceTypeMap.has(operation.interfaceType)) {
+ return;
+ }
+ const operationList = this.interfaceTypeMap.get(operation.interfaceType);
+ if (!operationList) {
+ return;
+ }
+
+ const index = operationList.indexOf(operation.operationType);
+ if (index > -1) {
+ operationList.splice(index, 1);
+ }
+ if (operationList.length == 0) {
+ this.interfaceTypeMap.delete(operation.interfaceType);
+ } else {
+ this.interfaceTypeMap.set(operation.interfaceType, operationList);
+ }
+ }
+
+ private addToInterfaceMap(operation: Operation) {
+ if (!this.interfaceTypeMap.has(operation.interfaceType)) {
+ this.interfaceTypeMap.set(operation.interfaceType, new Array<string>(operation.operationType));
+ return;
+ }
+
+ const operationList = this.interfaceTypeMap.get(operation.interfaceType);
+ if (!operationList) {
+ this.interfaceTypeMap.set(operation.interfaceType, new Array<string>(operation.operationType));
+ return;
+ }
+ operationList.push(operation.operationType);
+ operationList.sort();
+ this.interfaceTypeMap.set(operation.interfaceType, operationList);
+ }
+
+}