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