summaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/pages/interface-definition
diff options
context:
space:
mode:
authorvasraz <vasyl.razinkov@est.tech>2022-03-24 18:31:14 +0000
committerVasyl Razinkov <vasyl.razinkov@est.tech>2022-04-04 16:56:40 +0000
commit4aff8f5eafb6fbd6cc2c764fa1a5a676fa05c89c (patch)
tree4fbb91db254b1e4791830f5f91673e58376b293e /catalog-ui/src/app/ng2/pages/interface-definition
parentf6b81e6da9b95ec5ef2c8b2b7b50fb8de9f3dd28 (diff)
Implement adding Interface to VFC
Change-Id: I7cd8b82c306294d897d37d486aa3eeff7ca4206d Signed-off-by: Vasyl Razinkov <vasyl.razinkov@est.tech> Issue-ID: SDC-3893 Signed-off-by: andre.schmid <andre.schmid@est.tech>
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/interface-definition')
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html35
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts235
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html211
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less200
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts582
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts52
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html100
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less73
-rw-r--r--catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts257
9 files changed, 1684 insertions, 61 deletions
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html
index 25ccf111a1..f3043ffebd 100644
--- a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html
@@ -24,6 +24,13 @@
<div>{{ 'INTERFACE_DATA_EMPTY' | translate }}</div>
</div>
</div>
+ <div
+ class="top-add-btn add-btn"
+ [ngClass]="{'disabled': readonly}"
+ data-tests-id="add-operation"
+ (click)="onSelectInterfaceOperation(undefined, undefined)">
+ {{ 'INTERFACE_ADD_OPERATION' | translate }}
+ </div>
<div class="operation-list">
<div *ngIf="!isInterfaceListEmpty()">
<div class="expand-collapse" *ngIf="isOperationListEmpty()">
@@ -38,11 +45,11 @@
</a>
</div>
- <div class="interface-row" *ngFor="let interface of interfaces">
- <div class="interface-accordion" (click)="interface.toggleCollapse()">
+ <div class="interface-row" *ngFor="let interface1 of interfaces">
+ <div class="interface-accordion" (click)="interface1.toggleCollapse()">
<span
class="chevron-container"
- [ngClass]="{'isCollapsed': interface.isCollapsed}"
+ [ngClass]="{'isCollapsed': interface1.isCollapsed}"
*ngIf="isOperationListEmpty()">
<svg-icon
name="caret1-down-o"
@@ -50,10 +57,10 @@
size="small">
</svg-icon>
</span>
- <span class="interface-name">{{interface.type}}</span>
+ <span class="interface-name">{{interface1.type}}</span>
</div>
- <div class="generic-table" *ngIf="!interface.isCollapsed && isOperationListEmpty()">
+ <div class="generic-table" *ngIf="!interface1.isCollapsed && isOperationListEmpty()">
<div class="header-row table-row">
<span
class="cell header-cell field-name header-name">
@@ -63,20 +70,14 @@
{{ 'INTERFACE_HEADER_DESCRIPTION' | translate }}
</span>
</div>
-
- <div class="data-row" *ngFor="let operation of interface.operations"
- (click)="onSelectInterfaceOperation(interface, operation)">
- <span
- class="cell field-name">
- {{operation.name}}
- </span>
- <span class="cell field-description"
- [ngClass]="{'collapsed': operation.isCollapsed}">
- {{operation.getDescriptionEllipsis()}}
+ <div class="data-row" *ngFor="let operation of interface1.operations" (click)="onSelectInterfaceOperation(interface1, operation)">
+ <span class="cell field-name">{{operation.name}}</span>
+ <span class="cell field-description" [ngClass]="{'collapsed': operation.isCollapsed}">
+ {{operation.getDescriptionEllipsis()}}
<span class="more-or-less link" (click)="operation.toggleCollapsed($event)">
- {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}}
+ {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}}
+ </span>
</span>
- </span>
</div>
</div>
</div>
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts
index 8dd17f60e2..c9a6d07057 100644
--- a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts
@@ -20,26 +20,27 @@
*/
import {Component, ComponentRef, Inject, Input} from '@angular/core';
import {Component as IComponent} from 'app/models/components/component';
+import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service';
import {ISdcConfig, SdcConfigToken} from "app/ng2/config/sdc-config.config";
import {TranslateService} from "app/ng2/shared/translator/translate.service";
-
+import {IModalButtonComponent, SdcUiServices} from 'onap-ui-angular';
import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
+
import {ModalService} from 'app/ng2/services/modal.service';
-import {ButtonModel, CapabilitiesGroup, ModalModel, OperationModel} from 'app/models';
+import {
+ ButtonModel,
+ CapabilitiesGroup,
+ InputBEModel,
+ InterfaceModel,
+ ModalModel,
+ OperationModel,
+ WORKFLOW_ASSOCIATION_OPTIONS
+} from 'app/models';
import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service';
-
-import {SdcUiServices} from 'onap-ui-angular';
import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
-import {
- ComponentInterfaceDefinitionModel,
- InputOperationParameter,
- InterfaceOperationModel
-} from "../../../models/interfaceOperation";
-import {
- PropertyParamRowComponent
-} from "../composition/interface-operatons/operation-creator/property-param-row/property-param-row.component";
+import {InterfaceOperationModel} from "../../../models/interfaceOperation";
import {
InterfaceOperationHandlerComponent
} from "../composition/interface-operatons/operation-creator/interface-operation-handler.component";
@@ -49,15 +50,17 @@ import {
import {ToscaArtifactModel} from "../../../models/toscaArtifact";
import {ToscaArtifactService} from "../../services/tosca-artifact.service";
import {
- UIInterfaceOperationModel
-} from "../composition/interface-operatons/interface-operations.component";
+ InterfaceOperationComponent
+} from "../interface-operation/interface-operation.page.component";
+import {Observable} from "rxjs/Observable";
+import {PluginsService} from 'app/ng2/services/plugins.service';
export class UIOperationModel extends OperationModel {
isCollapsed: boolean = true;
isEllipsis: boolean;
MAX_LENGTH = 75;
- constructor(operation: UIOperationModel) {
+ constructor(operation: OperationModel) {
super(operation);
if (!operation.description) {
@@ -109,15 +112,14 @@ class ModalTranslation {
}
}
-export class UIInterfaceModel extends ComponentInterfaceDefinitionModel {
+export class UIInterfaceModel extends InterfaceModel {
isCollapsed: boolean = false;
constructor(interf?: any) {
super(interf);
- this.operations = _.map(
- this.operations,
- (operation) => new UIInterfaceOperationModel(operation)
- );
+ if (this.operations) {
+ this.operations = this.operations.map((operation) => new UIOperationModel(operation));
+ }
}
toggleCollapse() {
@@ -130,16 +132,14 @@ export class UIInterfaceModel extends ComponentInterfaceDefinitionModel {
selector: 'interface-definition',
templateUrl: './interface-definition.page.component.html',
styleUrls: ['interface-definition.page.component.less'],
- providers: [ModalService, TranslateService]
+ providers: [ModalService, TranslateService, InterfaceOperationComponent]
})
-
export class InterfaceDefinitionComponent {
modalInstance: ComponentRef<ModalComponent>;
interfaces: UIInterfaceModel[];
- inputs: Array<InputOperationParameter> = [];
+ inputs: InputBEModel[];
- properties: Array<PropertyParamRowComponent> = [];
deploymentArtifactsFilePath: Array<DropdownValue> = [];
toscaArtifactTypes: Array<DropdownValue> = [];
@@ -153,6 +153,10 @@ export class InterfaceDefinitionComponent {
capabilities: CapabilitiesGroup;
isViewOnly: boolean;
+ openOperation: OperationModel;
+ enableWorkflowAssociation: boolean;
+ workflowIsOnline: boolean;
+
@Input() component: IComponent;
@Input() readonly: boolean;
@Input() enableMenuItems: Function;
@@ -167,19 +171,52 @@ export class InterfaceDefinitionComponent {
private modalServiceNg2: ModalService,
private modalServiceSdcUI: SdcUiServices.ModalService,
private topologyTemplateService: TopologyTemplateService,
- private toscaArtifactService: ToscaArtifactService
+ private toscaArtifactService: ToscaArtifactService,
+ private ComponentServiceNg2: ComponentServiceNg2,
+ private WorkflowServiceNg2: WorkflowServiceNg2,
+ private ModalServiceSdcUI: SdcUiServices.ModalService,
+ private PluginsService: PluginsService
) {
this.modalTranslation = new ModalTranslation(translateService);
this.interfaceTypesMap = new Map<string, string[]>();
}
ngOnInit(): void {
- console.info("this.component.lifecycleState ", this.component.lifecycleState);
- if (this.component) {
- this.isViewOnly = this.component.componentMetadata.isComponentDataEditable();
- this.initInterfaceDefinition();
- this.loadInterfaceTypes();
- this.loadToscaArtifacts();
+ this.isLoading = true;
+ this.interfaces = [];
+ this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner'));
+ Observable.forkJoin(
+ this.ComponentServiceNg2.getInterfaceOperations(this.component),
+ this.ComponentServiceNg2.getComponentInputs(this.component),
+ this.ComponentServiceNg2.getInterfaceTypes(this.component),
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.component.componentType, this.component.uniqueId)
+ ).subscribe((response: any[]) => {
+ const callback = (workflows) => {
+ this.isLoading = false;
+ this.initInterfaces(response[0].interfaces);
+ this.sortInterfaces();
+ this.inputs = response[1].inputs;
+ this.interfaceTypes = response[2];
+ this.workflows = (workflows.items) ? workflows.items : workflows;
+ this.capabilities = response[3].capabilities;
+ };
+ if (this.enableWorkflowAssociation && this.workflowIsOnline) {
+ this.WorkflowServiceNg2.getWorkflows().subscribe(
+ callback,
+ (err) => {
+ this.workflowIsOnline = false;
+ callback([]);
+ }
+ );
+ } else {
+ callback([]);
+ }
+ });
+ }
+
+ initInterfaces(interfaces: InterfaceModel[]): void {
+ if (interfaces) {
+ this.interfaces = interfaces.map((interf) => new UIInterfaceModel(interf));
}
}
@@ -190,15 +227,18 @@ export class InterfaceDefinitionComponent {
private disableSaveButton = (): boolean => {
return this.isViewOnly ||
(this.isEnableAddArtifactImplementation()
- && (!this.modalInstance.instance.dynamicContent.instance.toscaArtifactTypeSelected ||
- !this.modalInstance.instance.dynamicContent.instance.artifactName)
+ && (!this.modalInstance.instance.dynamicContent.toscaArtifactTypeSelected ||
+ !this.modalInstance.instance.dynamicContent.artifactName)
);
}
onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) {
+ const isEdit = operation !== undefined;
const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal);
- const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () =>
- this.updateOperation(), this.disableSaveButton);
+ const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue',
+ () => isEdit ? this.updateOperation() : this.createOperationCallback(),
+ this.disableSaveButton
+ );
const interfaceDataModal: ModalModel =
new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', [saveButton, cancelButton], 'custom');
this.modalInstance = this.modalServiceNg2.createCustomModal(interfaceDataModal);
@@ -209,12 +249,14 @@ export class InterfaceDefinitionComponent {
{
deploymentArtifactsFilePath: this.deploymentArtifactsFilePath,
toscaArtifactTypes: this.toscaArtifactTypes,
- selectedInterface: interfaceModel,
- selectedInterfaceOperation: operation,
+ selectedInterface: interfaceModel ? interfaceModel : new UIInterfaceModel(),
+ selectedInterfaceOperation: operation ? operation : new InterfaceOperationModel(),
validityChangedCallback: this.disableSaveButton,
isViewOnly: this.isViewOnly,
+ isEdit: isEdit,
interfaceTypesMap: this.interfaceTypesMap,
- });
+ }
+ );
this.modalInstance.instance.open();
}
@@ -239,6 +281,27 @@ export class InterfaceDefinitionComponent {
this.modalServiceNg2.closeCurrentModal();
}
+ private createOperationCallback(): void {
+ const operationToUpdate = this.modalInstance.instance.dynamicContent.instance.operationToUpdate;
+ console.log('createOperationCallback', operationToUpdate);
+ console.log('this.component', this.component);
+ this.componentServiceNg2.createComponentInterfaceOperation(this.component.uniqueId, this.component.getTypeUrl(), operationToUpdate)
+ .subscribe((newOperation: InterfaceOperationModel) => {
+ const foundInterface = this.interfaces.find(value => value.type === newOperation.interfaceType);
+ if (foundInterface) {
+ foundInterface.operations.push(new UIOperationModel(new OperationModel(newOperation)));
+ } else {
+ const uiInterfaceModel = new UIInterfaceModel();
+ uiInterfaceModel.type = newOperation.interfaceType;
+ uiInterfaceModel.uniqueId = newOperation.interfaceType;
+ uiInterfaceModel.operations = [];
+ uiInterfaceModel.operations.push(new UIOperationModel(new OperationModel(newOperation)));
+ this.interfaces.push(uiInterfaceModel);
+ }
+ });
+ this.modalServiceNg2.closeCurrentModal();
+ }
+
private handleEnableAddArtifactImplementation = (newOperation: InterfaceOperationModel): InterfaceOperationModel => {
if (!this.isEnableAddArtifactImplementation()) {
newOperation.implementation.artifactType = null;
@@ -248,7 +311,7 @@ export class InterfaceDefinitionComponent {
}
private isEnableAddArtifactImplementation = (): boolean => {
- return this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation;
+ return this.modalInstance.instance.dynamicContent.enableAddArtifactImplementation;
}
private initInterfaceDefinition() {
@@ -257,7 +320,7 @@ export class InterfaceDefinitionComponent {
this.topologyTemplateService.getComponentInterfaceOperations(this.component.componentType, this.component.uniqueId)
.subscribe((response) => {
if (response.interfaces) {
- this.interfaces = _.map(response.interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel));
+ this.interfaces = response.interfaces.map((interfaceModel) => new UIInterfaceModel(interfaceModel));
}
this.isLoading = false;
});
@@ -301,11 +364,11 @@ export class InterfaceDefinitionComponent {
}
isAllCollapsed(): boolean {
- return _.every(this.interfaces, (interfaceData) => interfaceData.isCollapsed);
+ return this.interfaces.every((interfaceData) => interfaceData.isCollapsed);
}
isAllExpanded(): boolean {
- return _.every(this.interfaces, (interfaceData) => !interfaceData.isCollapsed);
+ return this.interfaces.every((interfaceData) => !interfaceData.isCollapsed);
}
isInterfaceListEmpty(): boolean {
@@ -313,8 +376,92 @@ export class InterfaceDefinitionComponent {
}
isOperationListEmpty(): boolean {
- return _.filter(this.interfaces, (interfaceData) =>
- interfaceData.operations && interfaceData.operations.length > 0).length > 0;
+ return this.interfaces.filter((interfaceData) => interfaceData.operations && interfaceData.operations.length > 0).length > 0;
+ }
+
+ onRemoveOperation = (event: Event, operation: OperationModel): void => {
+ event.stopPropagation();
+
+ const deleteButton: IModalButtonComponent = {
+ id: 'deleteButton',
+ text: this.modalTranslation.DELETE_BUTTON,
+ type: 'primary',
+ size: 'small',
+ closeModal: true,
+ callback: () => {
+ this.ComponentServiceNg2
+ .deleteInterfaceOperation(this.component, operation)
+ .subscribe(() => {
+ const curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType);
+ const index = curInterf.operations.findIndex((el) => el.uniqueId === operation.uniqueId);
+ curInterf.operations.splice(index, 1);
+ if (!curInterf.operations.length) {
+ const interfIndex = this.interfaces.findIndex((interf) => interf.type === operation.interfaceType);
+ this.interfaces.splice(interfIndex, 1);
+ }
+ });
+ }
+ };
+
+ const cancelButton: IModalButtonComponent = {
+ id: 'cancelButton',
+ text: this.modalTranslation.CANCEL_BUTTON,
+ type: 'secondary',
+ size: 'small',
+ closeModal: true,
+ callback: () => {
+ this.openOperation = null;
+ },
+ };
+
+ this.ModalServiceSdcUI.openWarningModal(
+ this.modalTranslation.DELETE_TITLE,
+ this.modalTranslation.deleteText(operation.name),
+ 'deleteOperationModal',
+ [deleteButton, cancelButton],
+ );
+ }
+
+ private createOperation = (operation: OperationModel): void => {
+ this.ComponentServiceNg2.createInterfaceOperation(this.component, operation).subscribe((response: OperationModel) => {
+ this.openOperation = null;
+
+ let curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType);
+
+ if (!curInterf) {
+ curInterf = new UIInterfaceModel({
+ type: response.interfaceType,
+ uniqueId: response.uniqueId,
+ operations: []
+ });
+ this.interfaces.push(curInterf);
+ }
+
+ const newOpModel = new UIOperationModel(response);
+ curInterf.operations.push(newOpModel);
+ this.sortInterfaces();
+
+ if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL && operation.artifactData) {
+ this.ComponentServiceNg2.uploadInterfaceOperationArtifact(this.component, newOpModel, operation).subscribe();
+ } else if (response.workflowId && operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING) {
+ this.WorkflowServiceNg2.associateWorkflowArtifact(this.component, response).subscribe();
+ } else if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW) {
+ this.$state.go('workspace.plugins', {path: 'workflowDesigner'});
+ }
+ });
+ }
+
+ private enableOrDisableSaveButton = (shouldEnable: boolean): void => {
+ const saveButton = this.modalInstance.instance.dynamicContent.getButtonById('saveButton');
+ saveButton.disabled = !shouldEnable;
+ }
+
+ private sortInterfaces(): void {
+ this.interfaces = this.interfaces.filter((interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces
+ this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically
+ this.interfaces.forEach((interf) => {
+ interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically
+ });
}
}
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html
new file mode 100644
index 0000000000..687c79fe86
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html
@@ -0,0 +1,211 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2022 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="operation-creator-interface-definition">
+ <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+
+ <form 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]="getSelectedDropdown(interfaceNames, operation.interfaceType)"
+ placeHolder="Select..."
+ [disabled]="readonly"
+ (changed)="onSelectInterface($event)"
+ [options]="interfaceNames">
+ </sdc-dropdown>
+ </div>
+
+ <div class="form-item" *ngIf="!isInterfaceOther()">
+ <sdc-dropdown
+ #operationNamesDropdown
+ label="{{ 'OPERATION_NAME' | translate }}"
+ [required]="true"
+ testId="operation-name"
+ [selectedOption]="getSelectedDropdown(operationNames, operation.name)"
+ placeHolder="Select..."
+ [disabled]="readonly"
+ (changed)="onSelectOperationName($event)"
+ [options]="operationNames">
+ </sdc-dropdown>
+ </div>
+ <div class="form-item" *ngIf="isInterfaceOther()">
+ <sdc-input
+ label="{{ 'OPERATION_NAME' | translate }}"
+ [(value)]="operation.name"
+ testId="operationName"
+ (valueChange)="onChangeName()"
+ [disabled]="readonly">
+ </sdc-input>
+ </div>
+
+ </div>
+
+ <div class="i-sdc-form-item sdc-input">
+ <span class="sdc-input__label">{{ 'OPERATION_DESCRIPTION' | translate }}</span>
+ <textarea
+ data-tests-id="operationDescription"
+ rows="2"
+ name="description"
+ [(ngModel)]="descriptionValue"
+ [ngClass]="{'disabled': readonly}">
+ </textarea>
+ </div>
+
+ <div class="side-by-side" *ngIf="enableWorkflowAssociation">
+ <div class="form-item">
+ <sdc-dropdown
+ #workflowAssignmentDropdown
+ label="{{ 'OPERATION_WORKFLOW_ASSIGNMENT' | translate }}"
+ placeHolder="Select..."
+ testId="association-type"
+ [selectedOption]="toDropDownOption(workflowAssociationType)"
+ [options]="associationOptions"
+ (changed)="toggleAssociateWorkflow($event)"
+ [disabled]="readonly">
+ </sdc-dropdown>
+ </div>
+
+ <div class="form-item" *ngIf="!isUsingExistingWF() && !isUsingExternalWF()"></div>
+
+ <div
+ *ngIf="isUsingExternalWF()"
+ class="form-item sdc-input">
+ <label class="sdc-input__label">{{ 'OPERATION_ARTIFACT' | translate }}</label>
+ <div class="i-sdc-form-item i-sdc-form-file-upload">
+ <span
+ class="i-sdc-form-file-name"
+ data-tests-id="artifactFilename">
+ {{ operation.artifactFileName }}
+ </span>
+ <div
+ *ngIf="operation.artifactFileName"
+ class="i-sdc-form-file-upload-x-btn"
+ data-tests-id="clearArtifact"
+ (click)="onChangeArtifactFile({ target: {} })"></div>
+ <label
+ class="i-sdc-form-file-upload-label"
+ [ngClass]="{'disabled': readonly}">
+ <input
+ type="file"
+ base-sixty-four-input
+ maxsize="10240"
+ data-tests-id="artifactUpload"
+ (change)="onChangeArtifactFile($event)"
+ (click)="$event.target.value = ''"
+ />
+ <div class="file-upload-browse-btn">Browse</div>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-item sdc-input" *ngIf="isUsingExistingWF()">
+ <label class="sdc-input__label required">{{ 'OPERATION_WORKFLOW' | translate }}
+ <span class="archive-warning" *ngIf="workflowIsOnline && archivedWorkflowId === operation.workflowId">({{ 'OPERATION_WORKFLOW_ARCHIVED' | translate }})</span>
+ <span class="no-workflow-warning" *ngIf="!workflowIsOnline">{{ 'OPERATION_NO_WORKFLOW_CONNECTION' | translate }}</span>
+ <span class="no-workflow-warning" *ngIf="workflowIsOnline && !workflows.length">{{ 'OPERATION_NO_WORKFLOW_ERROR' | translate }}</span>
+ </label>
+ <sdc-dropdown
+ placeHolder="Select..."
+ testId="associated-workflow"
+ [selectedOption]="getSelectedDropdown(workflows, operation.workflowId)"
+ [options]="workflows"
+ (changed)="onSelectWorkflow($event)"
+ [disabled]="readonly || !workflows.length || !workflowIsOnline">
+ </sdc-dropdown>
+ </div>
+
+ <div class="form-item sdc-input" *ngIf="isUsingExistingWF()">
+ <sdc-dropdown
+ *ngIf="workflowIsOnline && workflows.length"
+ label="{{ 'OPERATION_WORKFLOW_VERSION' | translate }}"
+ testId="associated-workflow-version"
+ [selectedOption]="getSelectedDropdown(workflowVersions, operation.workflowVersionId)"
+ [options]="workflowVersions"
+ (changed)="changeWorkflowVersion($event)"
+ [disabled]="!operation.workflowId || archivedWorkflowId === operation.workflowId || readonly">
+ </sdc-dropdown>
+ </div>
+ </div>
+
+ <div class="separator-buttons">
+ <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true">
+ <tab tabTitle="Inputs"></tab>
+ <tab tabTitle="Outputs"></tab>
+ </tabs>
+ <a
+ class="add-param-link add-btn"
+ *ngIf="!isUsingExistingWF() && !readonly"
+ data-tests-id="addInputParameter"
+ [ngClass]="{'disabled':!canAdd()}"
+ (click)="addParam()">{{ currentTab === TYPE_INPUT ? 'Add Input' : 'Add Output' }}</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" *ngIf="currentTab == TYPE_INPUT">
+ {{ 'OPERATION_PARAM_PROPERTY' | translate }}
+ <span
+ *ngIf="!isUsingExistingWF()"
+ class="sprite-new info-icon"
+ tooltip="{{propertyTooltipText}}"
+ tooltipDelay="0">
+ </span>
+ </span>
+ <span class="cell header-cell field-mandatory" *ngIf="!isUsingExistingWF()">{{ 'OPERATION_PARAM_MANDATORY' | translate }}</span>
+ <span class="cell header-cell remove" *ngIf="!isUsingExistingWF() && !readonly">●●●</span>
+ </div>
+
+ <div class="empty-msg data-row" *ngIf="tableParameters.length === 0">
+ <div>{{ 'EMPTY_PARAM_TABLE_HEADER' | translate }}</div>
+ <div *ngIf="isUsingExistingWF() && !operation.workflowVersionId">
+ <div *ngIf="workflows.length">
+ <span class="bold-message">{{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_1' | translate }}</span>
+ <span>{{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_2' | translate }}</span>
+ </div>
+ <div *ngIf="!workflows.length">
+ Only <span class="bold-message">certified</span> workflow versions can be assigned to an operation
+ </div>
+ </div>
+ </div>
+
+ <param-row
+ *ngFor="let param of tableParameters"
+ class="data-row"
+ [isInputParam]="currentTab === TYPE_INPUT"
+ [isAssociateWorkflow]="isUsingExistingWF()"
+ [param]="param"
+ [inputProps]="inputProperties"
+ [capabilitiesProps]="componentCapabilities"
+ [operationOutputs]="operationOutputs"
+ [onRemoveParam]="onRemoveParam"
+ [readonly]="readonly"
+ [validityChanged]="validityChanged">
+ </param-row>
+ </div>
+
+ </form>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less
new file mode 100644
index 0000000000..0afaa47ca3
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less
@@ -0,0 +1,200 @@
+/*
+* ============LICENSE_START=======================================================
+* SDC
+* ================================================================================
+* Copyright (C) 2022 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.
+*
+* SPDX-License-Identifier: Apache-2.0
+* ============LICENSE_END=========================================================
+*/
+@import '../../../../../assets/styles/variables.less';
+@import '../../../../../assets/styles/override.less';
+
+.operation-creator-interface-definition {
+ 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;
+ }
+ }
+
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts
new file mode 100644
index 0000000000..1897e3190d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts
@@ -0,0 +1,582 @@
+/*
+* ============LICENSE_START=======================================================
+* SDC
+* ================================================================================
+* Copyright (C) 2022 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.
+*
+* SPDX-License-Identifier: Apache-2.0
+* ============LICENSE_END=========================================================
+*/
+import * as _ from "lodash";
+import {Component, ViewChild} from '@angular/core';
+
+import {TranslateService} from "app/ng2/shared/translator/translate.service";
+import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service';
+import {
+ Capability,
+ InputBEModel,
+ InterfaceModel,
+ OperationModel,
+ OperationParameter,
+ WORKFLOW_ASSOCIATION_OPTIONS
+} from 'app/models';
+
+import {Tabs} from "app/ng2/components/ui/tabs/tabs.component";
+import {
+ DropdownValue
+} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+import {IDropDownOption} from 'onap-ui-angular';
+import {DropDownComponent} from "onap-ui-angular/dist/components";
+import {DROPDOWN_OPTION_TYPE} from "app/utils";
+import {Subscription} from "rxjs";
+
+export 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;
+ }
+}
+
+export interface OperationCreatorInput {
+ allWorkflows: Array<any>,
+ inputOperation: OperationModel,
+ interfaces: Array<InterfaceModel>,
+ inputProperties: Array<InputBEModel>,
+ enableWorkflowAssociation: boolean,
+ readonly: boolean,
+ interfaceTypes: { [interfaceType: string]: Array<string> },
+ validityChangedCallback: Function,
+ workflowIsOnline: boolean,
+ capabilities: Array<Capability>
+}
+
+@Component({
+ selector: 'operation-creator-interface-definition',
+ templateUrl: './operation-creator-interface-definition.component.html',
+ styleUrls: ['./operation-creator-interface-definition.component.less'],
+ providers: [TranslateService]
+})
+
+export class OperationCreatorInterfaceDefinitionComponent implements OperationCreatorInput {
+
+ input: OperationCreatorInput;
+ inputOperation: OperationModel;
+ interfaces: Array<InterfaceModel>;
+ operation: OperationModel;
+ interfaceNames: Array<TypedDropDownOption> = [];
+ interfaceTypes: { [interfaceType: string]: Array<string> };
+ operationNames: Array<TypedDropDownOption> = [];
+ validityChangedCallback: Function;
+ capabilities: Array<Capability>;
+
+ allWorkflows: Array<any>;
+ workflows: Array<DropdownValue> = [];
+ workflowVersions: Array<DropdownValue> = [];
+ inputProperties: Array<InputBEModel> = [];
+ archivedWorkflowId: string = '&';
+
+ inputParameters: Array<OperationParameter> = [];
+ noAssignInputParameters: Array<OperationParameter> = [];
+ assignInputParameters: { [key: string]: { [key: string]: Array<OperationParameter>; }; } = {};
+
+ outputParameters: Array<OperationParameter> = [];
+ noAssignOutputParameters: Array<OperationParameter> = [];
+ assignOutputParameters: { [key: string]: { [key: string]: Array<OperationParameter>; }; } = {};
+ componentCapabilities: Array<Capability> = [];
+
+ tableParameters: Array<OperationParameter> = [];
+ operationOutputs: Array<OperationModel> = [];
+
+ associationOptions: Array<DropdownValue> = [];
+ workflowAssociationType: string;
+
+ enableWorkflowAssociation: boolean;
+ workflowIsOnline: boolean;
+ isEditMode: boolean = false;
+ isLoading: boolean = false;
+ readonly: boolean;
+
+ propertyTooltipText: String;
+
+ TYPE_INPUT = 'Inputs';
+ TYPE_OUTPUT = 'Outputs';
+
+ INTERFACE_OTHER_HEADER = 'Local Interfaces';
+ INTERFACE_OTHER = 'Local';
+
+ @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
+ @ViewChild('operationNamesDropdown') operationNamesDropdown: DropDownComponent;
+ @ViewChild('workflowAssignmentDropdown') workflowAssignmentDropdown: DropDownComponent;
+ currentTab: String;
+
+ constructor(private workflowServiceNg2: WorkflowServiceNg2, private translateService: TranslateService) {
+ this.translateService.languageChangedObservable.subscribe(lang => {
+ this.propertyTooltipText = this.translateService.translate("OPERATION_PROPERTY_TOOLTIP_TEXT");
+
+ this.associationOptions = [
+ new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")),
+ new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")),
+ ];
+
+ this.workflowAssociationType = this.operation.workflowAssociationType;
+ });
+
+ this.currentTab = this.TYPE_INPUT;
+ }
+
+ createInterfaceDropdown(type: string) {
+ let label = type;
+ const lastDot = label.lastIndexOf('.');
+ if (lastDot > -1) {
+ label = label.substr(lastDot + 1);
+ }
+ return new TypedDropDownOption(type, label);
+ }
+
+ ngOnInit() {
+ this.interfaceNames = _.map(
+ _.keys(this.interfaceTypes),
+ type => this.createInterfaceDropdown(type)
+ );
+ this.interfaceNames.unshift(new TypedDropDownOption('Existing Interfaces', 'Existing Interfaces', DROPDOWN_OPTION_TYPE.HEADER));
+ this.interfaceNames = this.interfaceNames.concat([
+ new TypedDropDownOption(' ', ' ', DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE),
+ new TypedDropDownOption(this.INTERFACE_OTHER_HEADER, this.INTERFACE_OTHER_HEADER, DROPDOWN_OPTION_TYPE.HEADER),
+ new TypedDropDownOption(this.INTERFACE_OTHER)
+ ]);
+ const inputOperation = this.inputOperation;
+ this.operation = new OperationModel(inputOperation || {});
+
+ this.operationOutputs = _.reduce(
+ this.interfaces,
+ (acc: Array<OperationModel>, interf) => [
+ ...acc,
+ ..._.filter(
+ interf.operations,
+ op => op.uniqueId !== this.operation.uniqueId
+ ),
+ ],
+ []);
+
+ if (this.enableWorkflowAssociation) {
+ if (this.workflowIsOnline) {
+ this.workflows = _.map(
+ _.filter(
+ this.allWorkflows,
+ (workflow: any) => {
+ if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ACTIVE) {
+ return true;
+ }
+ if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ARCHIVED &&
+ workflow.id === this.operation.workflowId) {
+ this.archivedWorkflowId = workflow.id;
+ return true;
+ }
+ return false;
+ }
+ ),
+ (workflow: any) => new DropdownValue(workflow.id, workflow.name)
+ );
+ } else {
+ this.workflows = [];
+ }
+ }
+ this.reconstructOperation();
+ this.filterCapabilities();
+ this.validityChanged();
+ this.updateTable();
+ }
+
+ ngAfterViewInit() {
+ if (this.workflowAssignmentDropdown) {
+ this.workflowAssignmentDropdown.allOptions = this.associationOptions && this.associationOptions.length ?
+ this.associationOptions :
+ [
+ new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")),
+ new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")),
+ ];
+ }
+ }
+
+ reconstructOperation = () => {
+
+ const buildAndUpdate = () => {
+ this.buildParams();
+ this.updateTable();
+ };
+
+ const inputOperation = this.inputOperation;
+ if (inputOperation) {
+ this.onSelectInterface(new DropDownOption(this.operation.interfaceType));
+
+ if (this.enableWorkflowAssociation && inputOperation.workflowVersionId && this.isUsingExistingWF(inputOperation)) {
+ this.assignInputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []};
+ this.assignOutputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []};
+ this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+ this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+
+ const sub = this.onSelectWorkflow(new DropDownOption(inputOperation.workflowId), inputOperation.workflowVersionId);
+ if (sub) {
+ sub.add(() => {
+ buildAndUpdate();
+ this.operation.workflowVersionId = '-1';
+ setTimeout(() => this.operation.workflowVersionId = this.inputOperation.workflowVersionId);
+ });
+ } else {
+ buildAndUpdate();
+ }
+ } else {
+ this.inputParameters = this.noAssignInputParameters;
+ this.outputParameters = this.noAssignOutputParameters;
+ buildAndUpdate();
+ }
+
+ if (inputOperation.uniqueId) {
+ this.isEditMode = true;
+ }
+ }
+
+ }
+
+ filterCapabilities() {
+ this.componentCapabilities = _.filter(this.capabilities, (cap: Capability) => cap.properties);
+ }
+
+ buildParams = () => {
+
+ if (this.inputOperation.outputs) {
+ this.currentTab = this.TYPE_OUTPUT;
+ this.updateTable();
+ _.forEach(
+ [...this.inputOperation.outputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)),
+ (output: OperationParameter) => {
+ this.addParam({...output, required: Boolean(output.required)});
+ }
+ );
+ }
+
+ this.currentTab = this.TYPE_INPUT;
+ this.updateTable();
+ if (this.inputOperation.inputs) {
+ _.forEach(
+ [...this.inputOperation.inputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)),
+ (input: OperationParameter) => {
+ this.addParam({...input, required: Boolean(input.required)});
+ }
+ );
+ }
+
+ }
+
+ isInterfaceOther(): boolean {
+ return this.operation.interfaceType === this.INTERFACE_OTHER;
+ }
+
+ onSelectInterface(interf: IDropDownOption) {
+ if (interf && this.operation.interfaceType !== interf.value) {
+ this.operation.name = null;
+ }
+ this.operation.interfaceType = interf && interf.value;
+ this.operationNames = !this.operation.interfaceType ? [] : (
+ _.map(
+ this.interfaceTypes[this.operation.interfaceType],
+ name => {
+ const curInterf = _.find(
+ this.interfaces,
+ interf => interf.type === this.operation.interfaceType
+ );
+ const existingOp = _.find(
+ curInterf && curInterf.operations || [],
+ op => op.name === name
+ );
+ const ddType = (existingOp && existingOp.uniqueId !== this.operation.uniqueId) ? DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE : DROPDOWN_OPTION_TYPE.SIMPLE;
+ return new TypedDropDownOption(name, name, ddType);
+ }
+ )
+ );
+ if (this.operationNamesDropdown) {
+ this.operationNamesDropdown.allOptions = <IDropDownOption[]>this.operationNames;
+ }
+ this.validityChanged();
+ }
+
+ onSelectOperationName(name: IDropDownOption) {
+ if (name) {
+ this.operation.name = name.value;
+ }
+ this.validityChanged();
+ }
+
+ onChangeName() {
+ this.validityChanged();
+ }
+
+ get descriptionValue() {
+ return this.operation.description;
+ }
+
+ set descriptionValue(v) {
+ this.operation.description = v || null;
+ this.validityChanged();
+ }
+
+ onSelectWorkflow(workflowId: DropDownOption, selectedVersionId?: string): Subscription {
+
+ if (_.isUndefined(workflowId) || !this.workflowIsOnline) {
+ return;
+ }
+
+ if (this.operation.workflowId === workflowId.value && !selectedVersionId) {
+ return;
+ }
+
+ this.operation.workflowId = workflowId.value;
+ if (!this.assignInputParameters[this.operation.workflowId]) {
+ this.assignInputParameters[this.operation.workflowId] = {};
+ this.assignOutputParameters[this.operation.workflowId] = {};
+ }
+ this.operation.workflowName = workflowId.label;
+ if (!this.assignInputParameters[this.operation.workflowName]) {
+ this.assignInputParameters[this.operation.workflowName] = {};
+ this.assignOutputParameters[this.operation.workflowName] = {};
+ }
+
+ this.isLoading = true;
+ this.validityChanged();
+ return this.workflowServiceNg2.getWorkflowVersions(this.operation.workflowId).subscribe((versions: Array<any>) => {
+ this.isLoading = false;
+
+ this.workflowVersions = _.map(
+ _.filter(
+ versions, version => version.state === this.workflowServiceNg2.VERSION_STATE_CERTIFIED
+ ).sort((a, b) => a.name.localeCompare(b.name)),
+ (version: any) => {
+ if (!this.assignInputParameters[this.operation.workflowId][version.id] && version.id !== selectedVersionId) {
+ this.assignInputParameters[this.operation.workflowId][version.id] = _.map(version.inputs, (input: any) => {
+ return new OperationParameter({
+ ...input,
+ type: input.type.toLowerCase(),
+ required: Boolean(input.mandatory)
+ });
+ })
+ .sort((a, b) => a.name.localeCompare(b.name));
+
+ this.assignOutputParameters[this.operation.workflowId][version.id] = _.map(version.outputs, (output: any) => {
+ return new OperationParameter({
+ ...output,
+ type: output.type.toLowerCase(),
+ required: Boolean(output.mandatory)
+ });
+ })
+ .sort((a, b) => a.name.localeCompare(b.name));
+ }
+ return new DropdownValue(version.id, `V ${version.name}`);
+ }
+ );
+ if (!selectedVersionId && this.workflowVersions.length) {
+ this.operation.workflowVersionId = _.last(this.workflowVersions).value;
+ this.operation.workflowVersion = _.last(this.workflowVersions).label;
+ }
+
+ this.changeWorkflowVersion(new DropDownOption(this.operation.workflowVersionId));
+ this.validityChanged();
+ });
+
+ }
+
+ changeWorkflowVersion(versionId: DropDownOption) {
+
+ if (_.isUndefined(versionId) || !this.workflowIsOnline) {
+ return;
+ }
+
+ this.operation.workflowVersionId = versionId.value;
+ this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+ this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+ this.updateTable();
+ this.validityChanged();
+
+ }
+
+ toggleAssociateWorkflow(type: DropDownOption) {
+
+ if (_.isUndefined(type)) {
+ return;
+ }
+
+ this.operation.workflowAssociationType = type.value;
+ this.workflowAssociationType = this.operation.workflowAssociationType;
+
+ if (!this.isUsingExistingWF()) {
+ this.inputParameters = this.noAssignInputParameters;
+ this.outputParameters = this.noAssignOutputParameters;
+ } else {
+ if (!this.operation.workflowId || !this.operation.workflowVersionId) {
+ this.inputParameters = [];
+ this.outputParameters = [];
+ } else {
+ this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+ this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
+ }
+ }
+
+ this.updateTable();
+ this.validityChanged();
+
+ }
+
+ onChangeArtifactFile(e: any) {
+ const file = e.target.files && e.target.files[0];
+ this.operation.artifactFileName = file && file.name;
+
+ if (!this.operation.artifactFileName) {
+ this.operation.artifactData = null;
+ this.validityChanged();
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ this.isLoading = false;
+ const result = <String>reader.result;
+ this.operation.artifactData = result.substring(result.indexOf(',') + 1);
+ this.validityChanged();
+ }
+
+ this.isLoading = true;
+ reader.readAsDataURL(file);
+ }
+
+ tabChanged = (event) => {
+
+ this.currentTab = event.title;
+ this.updateTable();
+
+ }
+
+ updateTable() {
+
+ switch (this.currentTab) {
+ case this.TYPE_INPUT:
+ this.tableParameters = this.inputParameters;
+ break;
+ case this.TYPE_OUTPUT:
+ this.tableParameters = this.outputParameters;
+ break;
+ }
+
+ }
+
+ addParam(param?: OperationParameter): void {
+ this.tableParameters.push(new OperationParameter(param || {required: false}));
+ this.validityChanged();
+ }
+
+ canAdd = (): boolean => {
+
+ let valid = true;
+ if (this.currentTab === this.TYPE_INPUT) {
+ _.forEach(this.inputParameters, param => {
+ if (!param.name || !param.inputId) {
+ valid = false;
+ }
+ });
+ } else {
+ _.forEach(this.outputParameters, param => {
+ if (!param.name || !param.type) {
+ valid = false;
+ }
+ });
+ }
+
+ return valid;
+
+ }
+
+ isParamsValid = (): boolean => {
+
+ let valid = true;
+ _.forEach(this.inputParameters, param => {
+ if (!param.name || !param.inputId) {
+ valid = false;
+ }
+ });
+ _.forEach(this.outputParameters, param => {
+ if (!param.name || !param.type) {
+ valid = false;
+ }
+ });
+
+ return valid;
+
+ }
+
+ onRemoveParam = (param: OperationParameter): void => {
+ let index = _.indexOf(this.tableParameters, param);
+ this.tableParameters.splice(index, 1);
+ this.validityChanged();
+ }
+
+ createParamLists = () => {
+ this.operation.createInputsList(this.inputParameters);
+ this.operation.createOutputsList(this.outputParameters);
+ }
+
+ isUsingExistingWF = (operation?: OperationModel): boolean => {
+ operation = operation || this.operation;
+ return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING;
+ }
+
+ isUsingExternalWF = (operation?: OperationModel): boolean => {
+ operation = operation || this.operation;
+ return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL;
+ }
+
+ shouldCreateWF = (operation?: OperationModel): boolean => {
+ operation = operation || this.operation;
+ return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW;
+ }
+
+ checkFormValidForSubmit = (): boolean => {
+ return this.operation.name &&
+ (!this.isUsingExistingWF() || this.operation.workflowVersionId) &&
+ this.isParamsValid();
+ }
+
+ validityChanged = () => {
+ let validState = this.checkFormValidForSubmit();
+ this.validityChangedCallback(validState);
+ }
+
+ getSelectedDropdown(options: DropdownValue[], selectedValue: string): DropdownValue {
+ const selectedDropdown = _.find(options, (option) => option.value === selectedValue);
+ return selectedDropdown || this.toDropDownOption(null);
+ }
+
+ toDropDownOption(val: string) {
+ return {value: val, label: val};
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts
new file mode 100644
index 0000000000..7f75240735
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts
@@ -0,0 +1,52 @@
+/*
+* ============LICENSE_START=======================================================
+* SDC
+* ================================================================================
+* Copyright (C) 2022 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.
+*
+* SPDX-License-Identifier: Apache-2.0
+* ============LICENSE_END=========================================================
+*/
+import {NgModule} from "@angular/core";
+import {ParamRowComponent} from "./param-row/param-row.component";
+import {OperationCreatorInterfaceDefinitionComponent} from "./operation-creator-interface-definition.component";
+import {UiElementsModule} from "../../../components/ui/ui-elements.module";
+import {TranslateModule} from "../../../shared/translator/translate.module";
+import {SdcUiComponentsModule} from "onap-ui-angular";
+import {CommonModule} from "@angular/common";
+import {FormsModule} from "@angular/forms";
+import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module";
+
+@NgModule({
+ declarations: [
+ OperationCreatorInterfaceDefinitionComponent,
+ ParamRowComponent
+ ],
+ imports: [
+ CommonModule,
+ SdcUiComponentsModule,
+ FormsModule,
+ FormElementsModule,
+ TranslateModule,
+ UiElementsModule
+ ],
+ exports: [],
+ entryComponents: [
+ OperationCreatorInterfaceDefinitionComponent
+ ],
+ providers: []
+})
+
+export class OperationCreatorInterfaceDefinitionModule {
+}
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html
new file mode 100644
index 0000000000..1ed0375a16
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html
@@ -0,0 +1,100 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2022 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="cell field-name">
+ <ui-element-input
+ *ngIf="!isAssociateWorkflow"
+ [testId]="'param-name-' + (param.name || 'unnamed')"
+ [(value)]="param.name"
+ (valueChange)="onChangeName()"
+ [readonly]="readonly">
+ </ui-element-input>
+ <span *ngIf="isAssociateWorkflow">{{param.name}}</span>
+</div>
+
+<div class="cell field-type">
+ <ui-element-dropdown
+ *ngIf="!isAssociateWorkflow"
+ [testId]="'param-type-' + (param.name || 'unnamed')"
+ [values]="propTypeEnum"
+ [(value)]="param.type"
+ (valueChange)="onChangeType()"
+ [readonly]="readonly">
+ </ui-element-dropdown>
+ <span *ngIf="isAssociateWorkflow">{{param.type}}</span>
+</div>
+
+<div class="cell field-property" *ngIf="isInputParam">
+ <select
+ *ngIf="filteredInputProps.length || operationOutputCats.length || !isAssociateWorkflow"
+ [(ngModel)]="param.inputId"
+ (change)="onChangeProperty($event)"
+ [ngClass]="{'disabled': readonly}"
+ [attr.data-tests-id]="'value-param-property-' + (param.name || 'unnamed')">
+ <option
+ *ngFor="let prop of filteredInputProps"
+ [ngValue]="prop.value">
+ {{prop.label}}
+ </option>
+ <optgroup
+ *ngFor="let operation of operationOutputCats"
+ label="{{operation.operationName}}">
+ <option
+ *ngFor="let output of operation.outputs"
+ [ngValue]="output.value">
+ {{output.label}}
+ </option>
+ </optgroup>
+ <optgroup
+ *ngFor="let cap of filteredCapabilitiesProps"
+ label="{{cap.capabilityName}}">
+ <option
+ *ngFor="let prop of cap.properties"
+ [ngValue]="prop.value">
+ {{prop.label}}
+ </option>
+ </optgroup>
+ </select>
+ <span
+ *ngIf="!filteredInputProps.length && !operationOutputCats.length && isAssociateWorkflow"
+ class="no-properties-error">
+ {{ 'PARAM_NONE_OF_TYPE' | translate }}
+ </span>
+</div>
+
+<div class="cell field-mandatory" *ngIf="!isAssociateWorkflow">
+ <checkbox
+ *ngIf="!isAssociateWorkflow"
+ [attr.data-tests-id]="'param-mandatory-' + (param.name || 'unnamed')"
+ [(checked)]="param.required"
+ [ngClass]="{'disabled':readonly}">
+ </checkbox>
+</div>
+
+<div class="cell remove" *ngIf="!isAssociateWorkflow && !readonly">
+ <svg-icon
+ name="trash-o"
+ mode="info"
+ size="small"
+ [attr.data-tests-id]="'param-remove-' + (param.name || 'unnamed')"
+ (click)="onRemoveParam(param)"
+ [clickable]="true">
+ </svg-icon>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less
new file mode 100644
index 0000000000..d616bad1f9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less
@@ -0,0 +1,73 @@
+/*
+* ============LICENSE_START=======================================================
+* SDC
+* ================================================================================
+* Copyright (C) 2022 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.
+*
+* 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;
+ border: none;
+ padding-left: 10px;
+ }
+
+ 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/interface-definition/operation-creator/param-row/param-row.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts
new file mode 100644
index 0000000000..43760ba117
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts
@@ -0,0 +1,257 @@
+/*
+* ============LICENSE_START=======================================================
+* SDC
+* ================================================================================
+* Copyright (C) 2022 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.
+*
+* SPDX-License-Identifier: Apache-2.0
+* ============LICENSE_END=========================================================
+*/
+import {Component, Input} from '@angular/core';
+import {DataTypeService} from "app/ng2/services/data-type.service";
+import {
+ Capability,
+ DataTypeModel,
+ InputBEModel,
+ OperationModel,
+ OperationParameter
+} from 'app/models';
+import {
+ DropdownValue
+} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+import {WorkspaceService} from "../../../workspace/workspace.service";
+
+class DropdownValueType extends DropdownValue {
+ type: string;
+
+ constructor(value: string, label: string, type?: string) {
+ super(value, label);
+ if (type) {
+ this.type = type;
+ }
+ }
+}
+
+@Component({
+ selector: 'param-row',
+ templateUrl: './param-row.component.html',
+ styleUrls: ['./param-row.component.less']
+})
+
+export class ParamRowComponent {
+ @Input() param: OperationParameter;
+ @Input() inputProps: Array<InputBEModel>;
+ @Input() operationOutputs: Array<OperationModel>;
+ @Input() capabilitiesProps: Array<Capability>;
+ @Input() onRemoveParam: Function;
+ @Input() isAssociateWorkflow: boolean;
+ @Input() readonly: boolean;
+ @Input() isInputParam: boolean;
+ @Input() validityChanged: Function;
+
+ propTypeEnum: Array<string> = [];
+ operationOutputCats: Array<{ operationName: string, outputs: Array<DropdownValueType> }> = [];
+ filteredInputProps: Array<DropdownValue> = [];
+ filteredCapabilitiesProps: Array<{ capabilityName: string, properties: Array<DropdownValueType> }> = [];
+
+ constructor(private dataTypeService: DataTypeService, protected workspaceService: WorkspaceService) {
+ }
+
+ ngOnInit() {
+ if (this.isInputParam) {
+ this.propTypeEnum = _.uniq(
+ _.map(
+ _.concat(
+ this.getPrimitiveSubtypes(),
+ _.reduce(
+ this.operationOutputs,
+ (acc, op) => [...acc, ...op.outputs.listToscaDataDefinition],
+ []),
+ _.reduce(
+ this.capabilitiesProps,
+ (acc, capab) => [...acc, ...capab.properties],
+ [])
+ ),
+ prop => prop.type
+ )
+ );
+ } else {
+ const dataTypes: Array<DataTypeModel> = _.toArray(this.dataTypeService.getDataTypeByModel(this.workspaceService.metadata.model));
+ this.propTypeEnum = _.concat(
+ _.map(
+ _.filter(
+ dataTypes,
+ type => this.isTypePrimitive(type.name)
+ ),
+ type => type.name
+ ).sort(),
+ _.map(
+ _.filter(
+ dataTypes,
+ type => !this.isTypePrimitive(type.name)
+ ),
+ type => type.name
+ ).sort()
+ );
+ }
+
+ this.onChangeType();
+ this.validityChanged();
+ }
+
+ onChangeName() {
+ this.validityChanged();
+ }
+
+ onChangeType() {
+ if (!this.isInputParam) {
+ this.validityChanged();
+ return;
+ }
+
+ this.filteredInputProps = _.map(
+ _.filter(
+ this.getPrimitiveSubtypes(),
+ prop => !this.param.type || prop.type === this.param.type
+ ),
+ prop => new DropdownValue(prop.uniqueId, prop.name)
+ );
+ this.filteredInputProps.unshift(new DropdownValue("", ""));
+
+ this.operationOutputCats = _.filter(
+ _.map(
+ this.operationOutputs,
+ op => {
+ return {
+ operationName: `${op.displayType()}.${op.name}`,
+ outputs: _.map(
+ _.filter(op.outputs.listToscaDataDefinition, output => !this.param.type || output.type === this.param.type),
+ output => new DropdownValueType(
+ `${op.interfaceType}.${op.name}.${output.name}`,
+ output.name,
+ output.type
+ )
+ )
+ };
+ }
+ ),
+ category => category.outputs.length > 0
+ );
+
+ this.filteredCapabilitiesProps = _.filter(
+ _.map(
+ this.capabilitiesProps,
+ cap => {
+ return {
+ capabilityName: cap.name,
+ properties: _.map(
+ _.filter(cap.properties, prop => !this.param.type || prop.type === this.param.type),
+ prop => new DropdownValueType(
+ prop.uniqueId,
+ prop.name,
+ prop.type
+ )
+ )
+ };
+ }
+ ),
+ capability => capability.properties.length > 0
+ );
+
+ if (this.param.inputId) {
+ const selProp = this.getSelectedProp();
+ if (selProp && selProp.type === this.param.type) {
+ this.param.inputId = '-1';
+ setTimeout(() => this.param.inputId = selProp.uniqueId || selProp.value);
+ } else {
+ this.param.inputId = null;
+ }
+ }
+
+ this.validityChanged();
+ }
+
+ onChangeProperty() {
+ const newProp = this.getSelectedProp();
+
+ if (!this.param.type) {
+ this.param.type = newProp.type;
+ this.onChangeType();
+ }
+
+ if (!this.param.name) {
+ this.param.name = newProp.name || newProp.label;
+ }
+
+ this.validityChanged();
+ }
+
+ getPrimitiveSubtypes(): Array<InputBEModel> {
+ const flattenedProps: Array<any> = [];
+ const dataTypes = this.dataTypeService.getDataTypeByModel(this.workspaceService.metadata.model);
+
+ _.forEach(this.inputProps, prop => {
+ const type: DataTypeModel = _.find(
+ _.toArray(dataTypes),
+ (type: DataTypeModel) => type.name === prop.type
+ );
+ flattenedProps.push(prop);
+ if (!type) {
+ console.error('Could not find prop in dataTypes: ', prop);
+ } else {
+ if (type.properties) {
+ _.forEach(type.properties, subType => {
+ if (this.isTypePrimitive(subType.type)) {
+ flattenedProps.push({
+ type: subType.type,
+ name: `${prop.name}.${subType.name}`,
+ uniqueId: `${prop.uniqueId}.${subType.name}`
+ });
+ }
+ });
+ }
+ }
+ });
+
+ return flattenedProps;
+ }
+
+ getSelectedProp() {
+ return _.find(
+ this.getPrimitiveSubtypes(),
+ prop => this.param.inputId === prop.uniqueId
+ ) || _.find(
+ _.reduce(
+ this.operationOutputCats,
+ (acc, cat) => [...acc, ...cat.outputs],
+ []),
+ (out: DropdownValueType) => this.param.inputId === out.value
+ ) || _.find(
+ _.reduce(
+ this.filteredCapabilitiesProps,
+ (acc, cap) => [...acc, ...cap.properties],
+ []),
+ (prop: DropdownValueType) => this.param.inputId === prop.value
+ );
+ }
+
+ isTypePrimitive(type: string): boolean {
+ return (
+ type === 'string' ||
+ type === 'integer' ||
+ type === 'float' ||
+ type === 'boolean'
+ );
+ }
+}