diff options
author | aribeiro <anderson.ribeiro@est.tech> | 2020-11-19 13:28:43 +0000 |
---|---|---|
committer | Christophe Closset <christophe.closset@intl.att.com> | 2021-01-29 08:22:04 +0000 |
commit | 5c1f5756bcb5856e2d8b35e3c6ac206f891f8695 (patch) | |
tree | 29a7c4424b3ced8800e5bcacc629c8fff8dd8753 /catalog-ui/src/app/ng2/pages/composition | |
parent | 3c957597725f306b4ca06cebfa54fbf0f2622938 (diff) |
Add support for updating interface operations
Allows to update interface operations on a component instance.
Issue-ID: SDC-3446
Signed-off-by: aribeiro <anderson.ribeiro@est.tech>
Signed-off-by: andre.schmid <andre.schmid@est.tech>
Change-Id: I6a2c44997c04d9d9ea298e3d0bc971da7b137799
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/composition')
15 files changed, 1209 insertions, 12 deletions
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts index 1b1363e576..d7b997d86a 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts @@ -66,7 +66,7 @@ export class ZoneInstanceComponent implements OnInit { } private setMode = (mode:ZoneInstanceMode, event?:any, afterSaveCallback?:Function):void => { - + if(event){ //prevent event from handle and then repeat event from zone instance event.stopPropagation(); } @@ -125,4 +125,4 @@ export class ZoneInstanceComponent implements OnInit { event.stopPropagation(); }; -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html index 5a0ca3e43f..9f6a8bcbf9 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html @@ -54,4 +54,4 @@ (assignmentSaveComplete)="zoneAssignmentSaveComplete($event)"> </zone-instance> </zone-container> -</div>
\ No newline at end of file +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html new file mode 100644 index 0000000000..7567b90365 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html @@ -0,0 +1,80 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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="interface-operations"> + <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader> + <div class="operation-list"> + <div *ngIf="!isListEmpty()"> + <div class="expand-collapse"> + <a class="link" + [ngClass]="{'disabled': isAllExpanded()}" + (click)="collapseAll(false)">{{ 'INTERFACE_EXPAND_ALL' | translate }} + </a> | + <a class="link" + [ngClass]="{'disabled': isAllCollapsed()}" + (click)="collapseAll()"> + {{ 'INTERFACE_COLLAPSE_ALL' | translate }} + </a> + </div> + + <div class="interface-row" *ngFor="let interface1 of interfaces"> + <div class="interface-accordion" (click)="interface1.toggleCollapse()"> + <span + class="chevron-container" + [ngClass]="{'isCollapsed': interface1.isCollapsed}"> + <svg-icon + name="caret1-down-o" + mode="primary" + size="small"> + </svg-icon> + </span> + <span class="interface-name">{{interface1.displayType()}}</span> + </div> + + <div class="generic-table" *ngIf="!interface1.isCollapsed"> + <div class="header-row table-row"> + <span + class="cell header-cell field-name header-name"> + {{ 'INTERFACE_HEADER_NAME' | translate }} + </span> + <span class="cell header-cell field-description header-description"> + {{ 'INTERFACE_HEADER_DESCRIPTION' | translate }} + </span> + </div> + + <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'}} + </span> + </span> + </div> + </div> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less new file mode 100644 index 0000000000..1ebfb1fe82 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less @@ -0,0 +1,216 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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========================================================= + */ + +@import './../../../../../assets/styles/override.less'; +@import './../../../../../assets/styles/variables.less'; + +.interface-operations { + font-size: 14px; + + .top-add-btn { + position: relative; + top: -31px; + text-transform: uppercase; + font-size: 14px; + font-family: @font-opensans-medium; + } + + .link { + color: @sdcui_color_blue; + text-decoration: underline; + font-family: @font-opensans-regular; + + &:not(.disabled) { + &:not(.empty-list-add-btn) { + &:hover { + color: @sdcui_color_dark-blue; + cursor: pointer; + } + } + } + } + + .operation-list { + border-top: 1px solid @main_color_o; + padding-top: 5px; + + .empty-list-container { + width: 100%; + display: flex; + justify-content: center; + + .empty-list-add-btn { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid @main_color_o; + margin-top: 50px; + + height: 229px; + width: 480px; + + &.disabled { + pointer-events: none; + } + + &:hover { + &:not(.disabled) { + border: 1px solid @sdcui_color_blue; + cursor: pointer; + } + } + + .button-text { + margin-top: 9px; + font-family: @font-opensans-medium; + font-size: 16px; + text-transform: uppercase; + color: @sdcui_color_blue; + } + } + } + + .expand-collapse { + margin-top: 4px; + margin-bottom: 18px; + color: @sdcui_color_light-gray; + } + + .interface-row { + width: 100%; + margin-top: 13px; + border-bottom: 1px solid @main_color_o; + padding-left: 4px; + min-height: 37px; + + + .interface-accordion { + cursor: pointer; + + .chevron-container { + position: relative; + margin-right: 5px; + + &.isCollapsed { + right: -6px; + top: 0; + * { + transform: rotate(270deg); + } + } + &:not(.isCollapsed) { + top: 6px; + } + * { + &:hover { + cursor: pointer; + } + } + } + .interface-name { + font-size: 18px; + font-family: @font-opensans-bold; + margin-bottom: 15px; + } + } + + .generic-table { + margin-bottom: 24px; + margin-top: 10px; + margin-left: 22px; + font-size: 14px; + + .header-row, .data-row { + .cell { + &.field-description { + flex: 2.5; + } + + &.field-actions { + flex-basis: 72px; + display: flex; + justify-content: center; + align-items: center; + } + } + } + + .header-row { + .cell { + background: @sdcui_color_silver; + + &.field-actions { + font-size: 10px; + } + } + } + + .data-row { + cursor: pointer; + + &:hover { + background: @sdcui_color_light-silver; + + .cell { + &.field-name { + color: @sdcui_color_dark-blue; + } + } + } + + &:not(:hover) { + .field-actions { + visibility: hidden; + } + } + + .cell { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + &.field-description { + &:not(.collapsed) { + white-space: normal; + } + &.collapsed { + text-overflow: clip; + } + .more-or-less { + margin-left: 5px; + } + } + + &.field-actions { + .delete-action { + position: relative; + top: 2px; + } + } + } + + } + } + + } + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts new file mode 100644 index 0000000000..304fbcec3e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts @@ -0,0 +1,247 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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, ComponentRef, Input} from '@angular/core'; +import {TopologyTemplateService} from '../../../services/component-services/topology-template.service'; +import {TranslateService} from "../../../shared/translator/translate.service"; +import {ModalService } from 'app/ng2/services/modal.service'; +import { ModalComponent } from 'app/ng2/components/ui/modal/modal.component'; +import { + Component as TopologyTemplate +} from "../../../../models/components/component"; +import {PluginsService} from "app/ng2/services/plugins.service"; +import {SelectedComponentType} from "../common/store/graph.actions"; + +import {WorkspaceService} from "../../workspace/workspace.service"; +import { + ComponentInstanceInterfaceModel, + InterfaceOperationModel +} from "../../../../models/interfaceOperation"; +import { + InterfaceOperationHandlerComponent +} from "./operation-creator/interface-operation-handler.component"; + +import { + ButtonModel, + ComponentMetadata, + InterfaceModel, + InputBEModel, + ModalModel, + ComponentInstance +} from 'app/models'; + +export class UIInterfaceOperationModel extends InterfaceOperationModel { + isCollapsed: boolean = true; + isEllipsis: boolean; + MAX_LENGTH = 75; + _description: string; + + constructor(operation: InterfaceOperationModel) { + super(operation); + + if (!operation.description) { + this.description = ''; + } + + if (this.description.length > this.MAX_LENGTH) { + this.isEllipsis = true; + } else { + this.isEllipsis = false; + } + } + + getDescriptionEllipsis(): string { + if (this.isCollapsed && this.description.length > this.MAX_LENGTH) { + return this.description.substr(0, this.MAX_LENGTH - 3) + '...'; + } + return this.description; + } + + toggleCollapsed(e) { + e.stopPropagation(); + this.isCollapsed = !this.isCollapsed; + } +} + +class ModalTranslation { + EDIT_TITLE: string; + CANCEL_BUTTON: string; + SAVE_BUTTON: string; + + constructor(private TranslateService: TranslateService) { + this.TranslateService.languageChangedObservable.subscribe(lang => { + this.EDIT_TITLE = this.TranslateService.translate('INTERFACE_EDIT_TITLE'); + this.CANCEL_BUTTON = this.TranslateService.translate("INTERFACE_CANCEL_BUTTON"); + this.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON"); + }); + } +} + +export class UIInterfaceModel extends ComponentInstanceInterfaceModel { + isCollapsed: boolean = false; + + constructor(interf?: any) { + super(interf); + this.operations = _.map( + this.operations, + (operation) => new UIInterfaceOperationModel(operation) + ); + } + + toggleCollapse() { + this.isCollapsed = !this.isCollapsed; + } +} + +@Component({ + selector: 'app-interface-operations', + templateUrl: './interface-operations.component.html', + styleUrls: ['./interface-operations.component.less'], + providers: [ModalService, TranslateService] +}) +export class InterfaceOperationsComponent { + interfaces: UIInterfaceModel[]; + selectedOperation: InterfaceOperationModel; + inputs: Array<InputBEModel>; + isLoading: boolean; + interfaceTypes: { [interfaceType: string]: string[] }; + topologyTemplate: TopologyTemplate; + componentMetaData: ComponentMetadata; + componentInstanceSelected: ComponentInstance; + modalInstance: ComponentRef<ModalComponent>; + modalTranslation: ModalTranslation; + componentInstancesInterfaces: Map<string, InterfaceModel[]>; + + @Input() component: ComponentInstance; + @Input() readonly: boolean; + @Input() enableMenuItems: Function; + @Input() disableMenuItems: Function; + @Input() componentType: SelectedComponentType; + + + constructor( + private TranslateService: TranslateService, + private PluginsService: PluginsService, + private topologyTemplateService: TopologyTemplateService, + private modalServiceNg2: ModalService, + private workspaceService: WorkspaceService, + ) { + this.modalTranslation = new ModalTranslation(TranslateService); + } + + ngOnInit(): void { + this.componentMetaData = this.workspaceService.metadata; + this.loadComponentInstances(); + } + + private loadComponentInstances() { + this.isLoading = true; + this.topologyTemplateService.getComponentInstances(this.componentMetaData.componentType, this.componentMetaData.uniqueId) + .subscribe((response) => { + this.componentInstanceSelected = response.componentInstances.find(ci => ci.uniqueId === this.component.uniqueId); + this.initComponentInstanceInterfaceOperations(); + this.isLoading = false; + }); + } + + private initComponentInstanceInterfaceOperations() { + this.initInterfaces(this.componentInstanceSelected.interfaces); + this.sortInterfaces(); + } + + private initInterfaces(interfaces: InterfaceModel[]): void { + this.interfaces = _.map(interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel)); + } + + private sortInterfaces(): void { + this.interfaces = _.filter(this.interfaces, (interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces + this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically + _.forEach(this.interfaces, (interf) => { + interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically + }); + } + + collapseAll(value: boolean = true): void { + _.forEach(this.interfaces, (interf) => { + interf.isCollapsed = value; + }); + } + + isAllCollapsed(): boolean { + return _.every(this.interfaces, (interf) => interf.isCollapsed); + } + + isAllExpanded(): boolean { + return _.every(this.interfaces, (interf) => !interf.isCollapsed); + } + + isListEmpty(): boolean { + return _.filter( + this.interfaces, + (interf) => interf.operations && interf.operations.length > 0 + ).length === 0; + } + + private enableOrDisableSaveButton = (): boolean => { + return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); + } + + onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) { + const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal); + const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () => + this.updateInterfaceOperation(), this.enableOrDisableSaveButton); + const modalModel: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', [saveButton, cancelButton], 'custom'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + + this.modalServiceNg2.addDynamicContentToModal( + this.modalInstance, + InterfaceOperationHandlerComponent, + { + selectedInterface: interfaceModel, + selectedInterfaceOperation: operation, + validityChangedCallback: this.enableOrDisableSaveButton + } + ); + this.modalInstance.instance.open(); + } + + private cancelAndCloseModal = () => { + this.loadComponentInstances(); + return this.modalServiceNg2.closeCurrentModal(); + } + + private updateInterfaceOperation() { + this.isLoading = true; + let operationUpdated = this.modalInstance.instance.dynamicContent.instance.operationToUpdate; + this.topologyTemplateService.updateComponentInstanceInterfaceOperation( + this.componentMetaData.uniqueId, + this.componentMetaData.componentType, + this.componentInstanceSelected.uniqueId, + operationUpdated) + .subscribe((updatedComponentInstance: ComponentInstance) => { + this.componentInstanceSelected = new ComponentInstance(updatedComponentInstance); + this.initComponentInstanceInterfaceOperations(); + }); + this.modalServiceNg2.closeCurrentModal(); + this.isLoading = false; + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html new file mode 100644 index 0000000000..80aceeadda --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html @@ -0,0 +1,44 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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-input-name"> + <sdc-input + [(value)]="input.name" + (valueChange)="onChange()"> + </sdc-input> +</div> + +<div class="cell field-input-value"> + <sdc-input + [(value)]="input.toscaDefaultValue" + (valueChange)="onChange()"> + </sdc-input> + +</div> + +<div class="cell remove" *ngIf="!readonly"> + <svg-icon + name="trash-o" + mode="info" + size="small" + (click)="onRemoveInput(input)" + [clickable]="true"> + </svg-icon> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less new file mode 100644 index 0000000000..12eacc6473 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less @@ -0,0 +1,72 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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========================================================= + */ + +@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/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts new file mode 100644 index 0000000000..48bb804173 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts @@ -0,0 +1,48 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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 {InputOperationParameter} from "../../../../../../models/interfaceOperation"; + +@Component({ + selector: 'input-param-row', + templateUrl: './input-param-row.component.html', + styleUrls: ['./input-param-row.component.less'] +}) + +export class InputParamRowComponent { + @Input() input: InputOperationParameter; + @Input() onRemoveInput: Function; + @Input() readonly: boolean; + @Input() validityChanged: Function; + + constructor() { + } + + ngOnInit() { + this.validityChanged(); + } + + onChange() { + this.validityChanged(); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html new file mode 100644 index 0000000000..cd2d6063c1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html @@ -0,0 +1,86 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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-handler"> + <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader> + + <form class="w-sdc-form"> + + <div class="side-by-side"> + <div class="form-item"> + <sdc-input + label="{{ 'OPERATION_INTERFACE_TYPE' | translate }}" + [(value)]="interfaceType" + [disabled]="true"> + </sdc-input> + </div> + + <div class="form-item"> + <sdc-input + label="{{ 'OPERATION_NAME' | translate }}" + [(value)]="operationToUpdate.name" + [disabled]="true"> + </sdc-input> + </div> + </div> + + <div class="i-sdc-form-item"> + <sdc-input + label="{{'OPERATION_DESCRIPTION' | translate}}" + [(value)]="operationToUpdate.description" + (valueChange)="onDescriptionChange($event)"> + </sdc-input> + </div> + + <div class="i-sdc-form-item"> + <sdc-input + label="{{'IMPLEMENTATION_NAME' | translate}}" + [(value)]="operationToUpdate.implementation.artifactName"> + </sdc-input> + </div> + + <div class="separator-buttons"> + <tab tabTitle="Inputs"></tab> + <a class="add-param-link add-btn" + [ngClass]="{'disabled': readonly}" + (click)="onAddInput()">{{'OPERATION_ADD_INPUT' | translate}} + </a> + </div> + + <div class="generic-table"> + <div class="header-row table-row"> + <span class="cell header-cell field-input-name">{{ 'OPERATION_PARAM_NAME' | translate }}</span> + <span class="cell header-cell field-input-value">{{ 'OPERATION_INPUT_VALUE' | translate }}</span> + <span class="cell header-cell remove">●●●</span> + </div> + <div class="empty-msg data-row" *ngIf="!inputs.length"> + <div>{{ 'OPERATION_INPUT_EMPTY' | translate }}</div> + </div> + <input-param-row + *ngFor="let inputParameter of inputs" + class="data-row" + [input]="inputParameter" + [onRemoveInput]="onRemoveInput" + [validityChanged]="validityChanged"> + </input-param-row> + </div> + + </form> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less new file mode 100644 index 0000000000..8bbed9de02 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less @@ -0,0 +1,200 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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========================================================= + */ + +@import '../../../../../../assets/styles/variables.less'; +@import '../../../../../../assets/styles/override.less'; + +.operation-handler { + 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-input-name, &.field-input-value{ + 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/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts new file mode 100644 index 0000000000..1618af4aea --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts @@ -0,0 +1,130 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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} from '@angular/core'; +import {UIInterfaceModel} from "../interface-operations.component"; +import { + InputOperationParameter, + InterfaceOperationModel, + IOperationParamsList +} from "../../../../../models/interfaceOperation"; +import {TranslateService} from "../../../../shared/translator/translate.service"; + +@Component({ + selector: 'operation-handler', + templateUrl: './interface-operation-handler.component.html', + styleUrls: ['./interface-operation-handler.component.less'], + providers: [TranslateService] +}) + +export class InterfaceOperationHandlerComponent { + + input: { + selectedInterface: UIInterfaceModel; + selectedInterfaceOperation: InterfaceOperationModel; + validityChangedCallback: Function; + }; + + interfaceType: string; + interfaceOperationName: string; + operationToUpdate: InterfaceOperationModel; + inputs: Array<InputOperationParameter> = []; + isLoading: boolean = false; + readonly: boolean; + + ngOnInit() { + this.interfaceType = this.input.selectedInterface.displayType(); + this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation); + this.operationToUpdate.interfaceId = this.input.selectedInterface.uniqueId; + this.operationToUpdate.interfaceType = this.input.selectedInterface.type; + if (!this.operationToUpdate.inputs) { + this.operationToUpdate.inputs = new class implements IOperationParamsList { + listToscaDataDefinition: Array<InputOperationParameter> = []; + } + } + this.inputs = this.operationToUpdate.inputs.listToscaDataDefinition; + this.removeImplementationQuote(); + this.validityChanged(); + } + + onAddInput(inputOperationParameter?: InputOperationParameter): void { + let newInput = new InputOperationParameter(inputOperationParameter) + newInput.type = "string"; + newInput.inputId = this.generateUniqueId(); + this.inputs.push(newInput); + this.validityChanged(); + } + + onRemoveInput = (inputParam: InputOperationParameter): void => { + let index = this.inputs.indexOf(inputParam); + this.inputs.splice(index, 1); + this.validityChanged(); + } + + private generateUniqueId = (): string => { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < 36; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.input.validityChangedCallback(validState); + if (validState) { + this.readonly = false; + } + } + + onDescriptionChange= (value: any): void => { + this.operationToUpdate.description = value; + } + + private checkFormValidForSubmit = (): boolean => { + return this.operationToUpdate.name && this.isParamsValid(); + } + + private isParamsValid = (): boolean => { + const isInputValid = (input) => input.name && input.inputId; + const isValid = this.inputs.every(isInputValid); + if (!isValid) { + this.readonly = true; + } + return isValid; + } + + private removeImplementationQuote(): void { + if (!this.operationToUpdate.implementation + || !this.operationToUpdate.implementation.artifactName) { + return; + } + + let implementation = this.operationToUpdate.implementation.artifactName.trim(); + + if (implementation.startsWith("'") && implementation.endsWith("'")) { + this.operationToUpdate.implementation.artifactName = implementation.slice(1, -1); + } + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts new file mode 100644 index 0000000000..deba50aa7d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts @@ -0,0 +1,55 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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 {CommonModule} from "@angular/common"; + +import {FormsModule} from "@angular/forms"; +import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; +import {TranslateModule} from "app/ng2/shared/translator/translate.module"; + +import {SdcUiComponentsModule} from 'onap-ui-angular'; +import {UiElementsModule} from '../../../../components/ui/ui-elements.module'; +import {InputParamRowComponent} from './input-param-row/input-param-row.component'; +import {InterfaceOperationHandlerComponent} from "./interface-operation-handler.component"; + +@NgModule({ + declarations: [ + InterfaceOperationHandlerComponent, + InputParamRowComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + FormsModule, + FormElementsModule, + TranslateModule, + UiElementsModule + ], + exports: [], + entryComponents: [ + InterfaceOperationHandlerComponent + ], + providers: [] +}) + +export class InterfaceOperationHandlerModule { +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts index 1761bfdd03..6d96764b1c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts @@ -8,7 +8,7 @@ import { Service } from '../../../../models/components/service'; import { Resource } from '../../../../models/components/resource'; import { GroupInstance } from '../../../../models/graph/zones/group-instance'; import { PolicyInstance } from '../../../../models/graph/zones/policy-instance'; -import { ArtifactGroupType, ResourceType } from '../../../../utils/constants'; +import { ArtifactGroupType } from '../../../../utils/constants'; import { WorkspaceState } from '../../../store/states/workspace.state'; import { CompositionPanelComponent } from './composition-panel.component'; import { ArtifactsTabComponent } from './panel-tabs/artifacts-tab/artifacts-tab.component'; @@ -19,6 +19,7 @@ import { PolicyTargetsTabComponent } from './panel-tabs/policy-targets-tab/polic import { PropertiesTabComponent } from './panel-tabs/properties-tab/properties-tab.component'; import { ReqAndCapabilitiesTabComponent } from './panel-tabs/req-capabilities-tab/req-capabilities-tab.component'; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; describe('composition-panel component', () => { @@ -61,7 +62,13 @@ describe('composition-panel component', () => { }, inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'}, settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, - + interfaceOperations: { + titleIcon: 'composition-o', + component: InterfaceOperationsComponent, + input: {title: 'Interface Operations'}, + isActive: false, + tooltipText: 'Interface Operations' + } }; beforeEach( @@ -157,17 +164,17 @@ describe('composition-panel component', () => { fixture.componentInstance.store.select = jest.fn(() => Observable.of(selectedComponent)); fixture.componentInstance.selectedComponentIsServiceProxyInstance = jest.fn(() => true); - // const pnfMock = Mock.of<Service>({ isResource : () => false }); fixture.componentInstance.topologyTemplate = selectedComponent; // Call ngOnInit fixture.componentInstance.ngOnInit(); // Expect that - expect (fixture.componentInstance.tabs.length).toBe(6); + expect (fixture.componentInstance.tabs.length).toBe(7); expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); expect (fixture.componentInstance.tabs[1]).toEqual(tabs.properties); expect (fixture.componentInstance.tabs[2]).toEqual(tabs.reqAndCapabilities); + expect (fixture.componentInstance.tabs[6]).toEqual(tabs.interfaceOperations); }); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts index 2fce002844..2ef4e7c9a9 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts @@ -39,6 +39,7 @@ import { CompositionStateModel, GraphState } from '../common/store/graph.state'; import { ServiceConsumptionTabComponent } from './panel-tabs/service-consumption-tab/service-consumption-tab.component'; import { ServiceDependenciesTabComponent } from './panel-tabs/service-dependencies-tab/service-dependencies-tab.component'; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; const tabs = { infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'}, @@ -55,7 +56,8 @@ const tabs = { settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, consumption: {titleIcon: 'api-o', component: ServiceConsumptionTabComponent, input: {title: 'OPERATION CONSUMPTION'}, isActive: false, tooltipText: 'Service Consumption'}, dependencies: {titleIcon: 'archive', component: ServiceDependenciesTabComponent, input: {title: 'DIRECTIVES AND NODE FILTER'}, isActive: false, tooltipText: 'Service Dependencies'}, - substitutionFilter: {titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'}, isActive: false, tooltipText: 'Substitution Filter'} + substitutionFilter: {titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'}, isActive: false, tooltipText: 'Substitution Filter'}, + interfaceOperations: {titleIcon: 'composition-o', component: InterfaceOperationsComponent, input: {title: 'Interface Operations'}, isActive: false, tooltipText: 'Interface Operations'} }; @Component({ @@ -86,6 +88,12 @@ export class CompositionPanelComponent { }); } + + onRightClick(selectedComponent: any) { + console.info("onRightClick", selectedComponent) + return false; + } + ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); @@ -151,8 +159,10 @@ export class CompositionPanelComponent { if (component.isService() && (this.selectedComponentIsServiceProxyInstance() || this.selectedComponentIsServiceSubstitutionInstance())) { this.tabs.push(tabs.consumption); this.tabs.push(tabs.dependencies); + this.tabs.push(tabs.interfaceOperations); } else if (component.isResource() && this.isComponentInstanceSelected()) { this.tabs.push(tabs.dependencies); + this.tabs.push(tabs.interfaceOperations); } } diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts index a89db21b04..595ee21089 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts @@ -49,7 +49,7 @@ import { ServiceDependenciesModule } from "../../../components/logic/service-dep import { ServiceConsumptionModule } from "../../../components/logic/service-consumption/service-consumption.module"; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; import {SubstitutionFilterModule} from "../../../components/logic/substitution-filter/substitution-filter.module"; - +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; @NgModule({ @@ -67,7 +67,8 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f ServiceDependenciesTabComponent, SubstitutionFilterTabComponent, RequirementListComponent, - EnvParamsComponent + EnvParamsComponent, + InterfaceOperationsComponent, ], imports: [ GlobalPipesModule, @@ -81,7 +82,7 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f NgxDatatableModule, ServiceDependenciesModule, ServiceConsumptionModule, - SubstitutionFilterModule + SubstitutionFilterModule, // EnvParamsModule ], entryComponents: [ @@ -98,7 +99,8 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f SubstitutionFilterTabComponent, RequirementListComponent, PanelTabComponent, - EnvParamsComponent + EnvParamsComponent, + InterfaceOperationsComponent ], exports: [ CompositionPanelComponent |