diff options
Diffstat (limited to 'catalog-ui')
16 files changed, 719 insertions, 7 deletions
diff --git a/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.html b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.html new file mode 100644 index 0000000000..f781e6c89b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.html @@ -0,0 +1,50 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2020 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="substitution-filter"> + + <div *ngIf="compositeService.isService()" + class="i-sdc-designer-sidebar-section-content-item-rules-section"> + + <div class="i-sdc-designer-sidebar-section-content-item-rule" [ngClass]="{'hand': !readonly}" + *ngFor="let constraint of constraintObjects; let i = index"> + <div class="rule-details" [ngClass]="{'readonly': readonly}"> + <div class="rule-desc" (click)="!readonly && onSelectFilter(i)" tooltips + tooltip="{{constraint.servicePropertyName + ' ' + getSymbol(constraint.constraintOperator) + ' ' + + (constraint.sourceName ? constraint.sourceName + ':' : '') + constraint.value}}"> + {{constraint.servicePropertyName + ' ' + getSymbol(constraint.constraintOperator) + ' ' + + (constraint.sourceName ? constraint.sourceName + ':' : '') + constraint.value}} + </div> + <span *ngIf="!readonly" class="sprite-new delete-btn delete-icon" + (click)="openDeleteModal(i)" data-tests-id="delete-input-button"></span> + </div> + </div> + + <div *ngIf="!isSubstitutionFilterSet()" class="w-sdc-designer-sidebar-section-footer"> + <button + class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" + data-tests-id="add-rule-button" + (click)="onAddSubstitutionFilter()" + [disabled]="readonly"> + {{'ADD_SUBSTITUTION_FILTER' | translate}} + </button> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.less b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.less new file mode 100644 index 0000000000..2d9b2646af --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.less @@ -0,0 +1,105 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2020 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/variables-old.less'; +@import './../../../../../assets/styles/mixins_old.less'; +@import './../../../../../assets/styles/mixins.less'; + +.substitution-filter { + + /deep/ .checkbox-label-mark-as-dependent { + padding: 7px 18px; + position: relative; + height: 61px; + color: @main_color_a; + box-shadow: 0 2px 7px @main_color_o; + border-bottom: 1px solid @main_color_o; + .checkbox-label { + margin-top: 14px; + .checkbox-label-content { + font-size: 14px; + } + } + .checkbox-container input[type=checkbox].checkbox-hidden[disabled] ~ .checkbox-label-content { + opacity: 0.5; + } + .delete-btn { + background-position: -137px -415px; + width: 24px; + height: 24px + } + + loader { + top: 20px; + } + } + + + .i-sdc-designer-sidebar-section-content-item-rules-section { + .i-sdc-designer-sidebar-section-content-item-rule { + border-bottom: 1px solid @main_color_o; + padding: 5px 10px 5px 18px; + position: relative; + height: 61px; + display: flex; + align-items: center; + justify-content: space-between; + + .rule-details { + // .s_1; + display: flex; + flex: 1; + align-items: center; + justify-content: space-between; + margin-left: 5px; + width: 180px; + .delete-icon { + visibility: hidden; + } + &:not(.readonly):hover { + .a_1; + } + &:hover .delete-icon{ + visibility: visible; + } + &.readonly { + opacity: 0.5; + } + } + .rule-desc { + .sdc-ellipsis; + width: 220px; + position: relative; + padding-right: 1em; + } + + } + } + .w-sdc-designer-sidebar-section-footer { + margin-top: 10px; + text-align: center; + width: 100%; + } + .w-sdc-designer-sidebar-section-footer-action { + width: 180px; + margin-top: 10px; + } +} diff --git a/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.ts b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.ts new file mode 100644 index 0000000000..a0fa2bb20b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.ts @@ -0,0 +1,296 @@ +/* +* ============LICENSE_START======================================================= +* Copyright (C) 2020 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, EventEmitter, Input, Output} from '@angular/core'; +import { + ButtonModel, + ComponentInstance, + InputBEModel, + ModalModel, + PropertyBEModel, +} from 'app/models'; +import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; +import {ServiceDependenciesEditorComponent} from 'app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component'; +import {ModalService} from 'app/ng2/services/modal.service'; +import {ComponentGenericResponse} from 'app/ng2/services/responses/component-generic-response'; +import {TranslateService} from 'app/ng2/shared/translator/translate.service'; +import {ComponentMetadata} from '../../../../models/component-metadata'; +import {ServiceInstanceObject} from '../../../../models/service-instance-properties-and-interfaces'; +import {TopologyTemplateService} from '../../../services/component-services/topology-template.service'; + +export class ConstraintObject { + servicePropertyName: string; + constraintOperator: string; + sourceType: string; + sourceName: string; + value: string; + + constructor(input?: any) { + if (input) { + this.servicePropertyName = input.servicePropertyName; + this.constraintOperator = input.constraintOperator; + this.sourceType = input.sourceType; + this.sourceName = input.sourceName; + this.value = input.value; + } + } +} + +export class ConstraintObjectUI extends ConstraintObject { + isValidValue: boolean; + + constructor(input?: any) { + super(input); + if (input) { + this.isValidValue = input.isValidValue ? input.isValidValue : input.value !== ''; + } + } + + public updateValidity(isValidValue: boolean) { + this.isValidValue = isValidValue; + } + + public isValidRule(isStatic) { + const isValidValue = isStatic ? this.isValidValue : true; + return this.servicePropertyName != null && this.servicePropertyName !== '' + && this.value != null && this.value !== '' && isValidValue; + } +} + +export const OPERATOR_TYPES = { + EQUAL: 'equal', + GREATER_THAN: 'greater_than', + LESS_THAN: 'less_than' +}; + +class I18nTexts { + static addSubstitutionFilterTxt: string; + static updateSubstitutionFilterTxt: string; + static deleteSubstitutionFilterTxt: string; + static deleteSubstitutionFilterMsg: string; + static modalCancel: string; + static modalCreate: string; + static modalSave: string; + static modalDelete: string; + + public static translateTexts(translateService) { + I18nTexts.modalCancel = translateService.translate('MODAL_CANCEL'); + I18nTexts.modalCreate = translateService.translate('MODAL_CREATE'); + I18nTexts.modalSave = translateService.translate('MODAL_SAVE'); + I18nTexts.modalDelete = translateService.translate('MODAL_DELETE'); + + I18nTexts.addSubstitutionFilterTxt = translateService.translate('ADD_SUBSTITUTION_FILTER'); + I18nTexts.updateSubstitutionFilterTxt = translateService.translate('UPDATE_SUBSTITUTION_FILTER'); + I18nTexts.deleteSubstitutionFilterTxt = translateService.translate('DELETE_SUBSTITUTION_FILTER'); + I18nTexts.deleteSubstitutionFilterMsg = translateService.translate('DELETE_SUBSTITUTION_FILTER_MSG'); + } +} + +@Component({ + selector: 'substitution-filter', + templateUrl: './substitution-filter.component.html', + styleUrls: ['substitution-filter.component.less'], + providers: [ModalService, TranslateService] +}) + +export class SubstitutionFilterComponent { + modalInstance: ComponentRef<ModalComponent>; + isLoading: boolean; + parentServiceInputs: InputBEModel[] = []; + operatorTypes: any[]; + constraintObjects: ConstraintObject[] = []; + + @Input() readonly: boolean; + @Input() compositeService: ComponentMetadata; + @Input() currentServiceInstance: ComponentInstance; + @Input() selectedInstanceSiblings: ServiceInstanceObject[]; + @Input() selectedInstanceConstraints: ConstraintObject[] = []; + @Input() selectedInstanceProperties: PropertyBEModel[] = []; + @Output() updateConstraintListEvent: EventEmitter<ConstraintObject[]> = new EventEmitter<ConstraintObject[]>(); + @Output() loadConstraintListEvent: EventEmitter<any> = new EventEmitter(); + @Output() hasSubstitutionFilter = new EventEmitter<boolean>(); + + constructor(private topologyTemplateService: TopologyTemplateService, private modalServiceNg2: ModalService, private translateService: TranslateService) { + } + + ngOnInit() { + this.isLoading = false; + this.operatorTypes = [ + {label: '>', value: OPERATOR_TYPES.GREATER_THAN}, + {label: '<', value: OPERATOR_TYPES.LESS_THAN}, + {label: '=', value: OPERATOR_TYPES.EQUAL} + ]; + this.topologyTemplateService.getComponentInputsWithProperties(this.compositeService.componentType, this.compositeService.uniqueId).subscribe((result: ComponentGenericResponse) => { + this.parentServiceInputs = result.inputs; + }); + this.loadAllInstances(); + this.translateService.languageChangedObservable.subscribe((lang) => { + I18nTexts.translateTexts(this.translateService); + }); + } + + ngOnChanges(changes) { + if (changes.currentServiceInstance) { + this.currentServiceInstance = changes.currentServiceInstance.currentValue; + } + if (changes.selectedInstanceConstraints && changes.selectedInstanceConstraints.currentValue !== changes.selectedInstanceConstraints.previousValue) { + this.selectedInstanceConstraints = changes.selectedInstanceConstraints.currentValue; + this.loadAllInstances(); + } + } + + public loadAllInstances = (): void => { + this.topologyTemplateService.getComponentCompositionData(this.compositeService.uniqueId, this.compositeService.componentType).subscribe((response) => { + response.componentInstances.forEach(componentInstance => this.getSubstitutionFilter(componentInstance)) + }) + } + + onAddSubstitutionFilter() { + const cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.modalServiceNg2.closeCurrentModal); + const saveButton: ButtonModel = new ButtonModel(I18nTexts.modalCreate, 'blue', this.createSubstitutionFilter, this.getDisabled); + const modalModel: ModalModel = new ModalModel('l', I18nTexts.addSubstitutionFilterTxt, '', [saveButton, cancelButton], 'standard'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + this.modalServiceNg2.addDynamicContentToModal( + this.modalInstance, + ServiceDependenciesEditorComponent, + { + currentServiceName: this.currentServiceInstance.name, + operatorTypes: this.operatorTypes, + compositeServiceName: this.compositeService.name, + parentServiceInputs: this.parentServiceInputs, + selectedInstanceProperties: this.selectedInstanceProperties, + selectedInstanceSiblings: this.selectedInstanceSiblings + } + ); + this.modalInstance.instance.open(); + } + + createSubstitutionFilter = (): void => { + const newSubstitutionFilter: ConstraintObject = new ConstraintObject(this.modalInstance.instance.dynamicContent.instance.currentRule); + this.isLoading = true; + this.topologyTemplateService.createSubstitutionFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, + newSubstitutionFilter, + this.compositeService.componentType + ).subscribe((response) => { + this.updateConstraintListEvent.emit(response.properties); + this.isLoading = false; + }, () => { + console.error("Failed to Create Substitution Filter on the component with id: ", this.compositeService.uniqueId); + this.isLoading = false; + }); + this.modalServiceNg2.closeCurrentModal(); + } + + onSelectFilter(index: number) { + const cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.modalServiceNg2.closeCurrentModal); + const updateButton: ButtonModel = new ButtonModel(I18nTexts.modalSave, 'blue', () => this.updateSubstitutionFilter(), this.getDisabled); + const modalModel: ModalModel = new ModalModel('l', I18nTexts.updateSubstitutionFilterTxt, '', [updateButton, cancelButton], 'standard'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + this.modalServiceNg2.addDynamicContentToModal( + this.modalInstance, + ServiceDependenciesEditorComponent, + { + serviceRuleIndex: index, + serviceRules: _.map(this.constraintObjects, (constraint) => new ConstraintObjectUI(constraint)), + currentServiceName: this.currentServiceInstance.name, + operatorTypes: this.operatorTypes, + compositeServiceName: this.compositeService.name, + parentServiceInputs: this.parentServiceInputs, + selectedInstanceProperties: this.selectedInstanceProperties, + selectedInstanceSiblings: this.selectedInstanceSiblings + } + ); + this.modalInstance.instance.open(); + } + + updateSubstitutionFilter = (): void => { + const constraintToUpdate: ConstraintObject = this.modalInstance.instance.dynamicContent.instance.serviceRulesList.map((rule) => new ConstraintObject(rule)); + this.isLoading = true; + this.topologyTemplateService.updateSubstitutionFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, + constraintToUpdate, + this.compositeService.componentType + ).subscribe((response) => { + this.hasSubstitutionFilter.emit(this.isSubstitutionFilterSet()); + this.updateConstraintListEvent.emit(response.properties); + this.isLoading = false; + }, () => { + console.error("Failed to Update Substitution Filter on the component with id: ", this.compositeService.uniqueId); + this.isLoading = false; + }); + this.modalServiceNg2.closeCurrentModal(); + } + + onDeleteSubstitutionFilter = (index: number) => { + this.isLoading = true; + this.topologyTemplateService.deleteSubstitutionFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, + index, + this.compositeService.componentType + ).subscribe((response) => { + console.log("on Delete - Response Properties: ", response.properties); + this.updateConstraintListEvent.emit(response.properties); + this.isLoading = false; + }, () => { + console.error("Failed to Delete Substitution Filter on the component with id: ", this.compositeService.uniqueId); + this.isLoading = false; + }); + this.constraintObjects = []; + this.modalServiceNg2.closeCurrentModal(); + } + + getDisabled = (): boolean => { + return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); + } + + getSymbol(constraintOperator) { + switch (constraintOperator) { + case OPERATOR_TYPES.LESS_THAN: + return '<'; + case OPERATOR_TYPES.EQUAL: + return '='; + case OPERATOR_TYPES.GREATER_THAN: + return '>'; + } + } + + openDeleteModal = (index: number) => { + this.modalServiceNg2.createActionModal(I18nTexts.deleteSubstitutionFilterTxt, I18nTexts.deleteSubstitutionFilterMsg, + I18nTexts.modalDelete, () => this.onDeleteSubstitutionFilter(index), I18nTexts.modalCancel).instance.open(); + } + + private getSubstitutionFilter = (componentInstance: ComponentInstance): void => { + this.topologyTemplateService.getSubstitutionFilterConstraints(this.compositeService.componentType, this.compositeService.uniqueId).subscribe((response) => { + const substitutionFilter: ConstraintObject[] = response.substitutionFilterForTopologyTemplate[componentInstance.uniqueId].properties; + if (substitutionFilter) { + this.currentServiceInstance = componentInstance; + this.constraintObjects = substitutionFilter; + } + }); + } + + private isSubstitutionFilterSet = (): boolean => { + return Array.isArray(this.constraintObjects) && this.constraintObjects.length > 0; + } + +} diff --git a/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.module.ts b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.module.ts new file mode 100644 index 0000000000..9ea7c0c5a0 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.module.ts @@ -0,0 +1,44 @@ +/* +* ============LICENSE_START======================================================= +* Copyright (C) 2020 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 { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { SubstitutionFilterComponent } from "./substitution-filter.component"; + +@NgModule({ + declarations: [ + SubstitutionFilterComponent + ], + imports: [ + CommonModule, + UiElementsModule, + TranslateModule + ], + exports: [ + SubstitutionFilterComponent + ], + entryComponents: [ + SubstitutionFilterComponent + ], + providers: [] +}) +export class SubstitutionFilterModule { +} 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 69ca3faaf5..5467ecedbc 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 @@ -47,7 +47,7 @@ import { ServiceGenericResponse } from 'app/ng2/services/responses/service-gener import { WorkspaceState } from 'app/ng2/store/states/workspace.state'; import { EventListenerService } from 'app/services'; import { ComponentInstanceFactory, EVENTS, SdcElementType } from 'app/utils'; -import { ComponentType, GRAPH_EVENTS, GraphColors, DEPENDENCY_EVENTS } from 'app/utils/constants'; +import { ComponentType, GRAPH_EVENTS, GraphColors, DEPENDENCY_EVENTS , SUBSTITUTION_FILTER_EVENTS} from 'app/utils/constants'; import * as _ from 'lodash'; import { DndDropEvent } from 'ngx-drag-drop/ngx-drag-drop'; import { SdcUiServices } from 'onap-ui-angular'; @@ -143,6 +143,7 @@ export class CompositionGraphComponent implements AfterViewInit { }); this.eventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); this.eventListenerService.unRegisterObserver(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE); + this.eventListenerService.unRegisterObserver(SUBSTITUTION_FILTER_EVENTS.ON_SUBSTITUTION_FILTER_CHANGE); } public isViewOnly = (): boolean => { 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 25a0c728a8..305086e8f3 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 @@ -157,7 +157,7 @@ describe('composition-panel component', () => { fixture.componentInstance.ngOnInit(); // Expect that - expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs.length).toBe(6); expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); expect (fixture.componentInstance.tabs[1]).toEqual(tabs.properties); expect (fixture.componentInstance.tabs[2]).toEqual(tabs.reqAndCapabilities); 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 348dc9f8c1..bf8cc27bfb 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 @@ -38,6 +38,7 @@ import { OnSidebarOpenOrCloseAction } from '../common/store/graph.actions'; 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"; const tabs = { infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'}, @@ -53,7 +54,8 @@ const tabs = { inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'}, 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'} + 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'} }; @Component({ @@ -144,6 +146,7 @@ export class CompositionPanelComponent { if (component.isService() && this.selectedComponentIsServiceProxyInstance()) { this.tabs.push(tabs.consumption); this.tabs.push(tabs.dependencies); + this.tabs.push(tabs.substitutionFilter) } else if (component.isResource() && this.selectedComponentIsVfcInstance()) { this.tabs.push(tabs.dependencies); } 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 0fd1e51fa5..a89db21b04 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 @@ -47,6 +47,8 @@ import { ServiceConsumptionTabComponent } from "./panel-tabs/service-consumption import { ServiceDependenciesTabComponent } from "./panel-tabs/service-dependencies-tab/service-dependencies-tab.component"; import { ServiceDependenciesModule } from "../../../components/logic/service-dependencies/service-dependencies.module"; 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"; @@ -63,6 +65,7 @@ import { ServiceConsumptionModule } from "../../../components/logic/service-cons ReqAndCapabilitiesTabComponent, ServiceConsumptionTabComponent, ServiceDependenciesTabComponent, + SubstitutionFilterTabComponent, RequirementListComponent, EnvParamsComponent ], @@ -77,7 +80,8 @@ import { ServiceConsumptionModule } from "../../../components/logic/service-cons TranslateModule, NgxDatatableModule, ServiceDependenciesModule, - ServiceConsumptionModule + ServiceConsumptionModule, + SubstitutionFilterModule // EnvParamsModule ], entryComponents: [ @@ -91,6 +95,7 @@ import { ServiceConsumptionModule } from "../../../components/logic/service-cons ReqAndCapabilitiesTabComponent, ServiceConsumptionTabComponent, ServiceDependenciesTabComponent, + SubstitutionFilterTabComponent, RequirementListComponent, PanelTabComponent, EnvParamsComponent diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.html new file mode 100644 index 0000000000..d0656959f8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.html @@ -0,0 +1,38 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2020 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========================================================= +--> + +<ng2-expand-collapse state="0"> + <header sdc-tooltip tooltip-text="{{input.title}}">{{input.title}}</header> + <content> + <div *ngIf="isComponentInstanceSelected"> + <substitution-filter + [compositeService]="metaData" + [currentServiceInstance]="component" + [selectedInstanceProperties]="selectedInstanceProperties" + [selectedInstanceSiblings]="selectedInstanceSiblings" + [selectedInstanceConstraints]="selectedInstanceConstraints" + [readonly]="isViewOnly" + (hasSubstitutionFilter)="notifyDependencyEventsObserver($event)" + (updateConstraintListEvent)="updateSelectedInstanceConstraints($event)" + (loadConstraintListEvent)="loadConstraints()"> + </substitution-filter> + </div> + </content> +</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.less new file mode 100644 index 0000000000..3edfbd951b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.less @@ -0,0 +1,23 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2020 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========================================================= + */ + +:host /deep/ .expand-collapse-content { + padding: 0 0 10px; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.ts new file mode 100644 index 0000000000..20868e388b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/substitution-filter-tab/substitution-filter-tab.component.ts @@ -0,0 +1,114 @@ +/* +* ============LICENSE_START======================================================= +* Copyright (C) 2020 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 { Store } from '@ngxs/store'; +import { + CapabilitiesGroup, + Capability, + Component as TopologyTemplate, + ComponentInstance, + FullComponentInstance, + InputBEModel, + InputsGroup, + InterfaceModel, + PropertiesGroup, + PropertyBEModel, +} from 'app/models'; +import { SUBSTITUTION_FILTER_EVENTS } from 'app/utils/constants'; +import { ComponentMetadata } from '../../../../../../models/component-metadata'; +import { ServiceInstanceObject } from '../../../../../../models/service-instance-properties-and-interfaces'; +import { EventListenerService } from '../../../../../../services/event-listener-service'; +import { ConstraintObject } from '../../../../../components/logic/service-dependencies/service-dependencies.component'; +import { TopologyTemplateService } from '../../../../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../../../../services/responses/component-generic-response'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { SelectedComponentType } from '../../../common/store/graph.actions'; +import { CompositionService } from '../../../composition.service'; + +@Component({ + selector: 'substitution-filter-tab', + templateUrl: 'substitution-filter-tab.component.html', + styleUrls: ['substitution-filter-tab.component.less'] +}) +export class SubstitutionFilterTabComponent { + isComponentInstanceSelected: boolean; + + selectedInstanceSiblings: ServiceInstanceObject[]; + componentInstancesConstraints: any[]; + selectedInstanceConstraints: ConstraintObject[]; + selectedInstanceProperties: PropertyBEModel[]; + componentInstanceProperties: PropertiesGroup; + metaData: ComponentMetadata; + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: FullComponentInstance | TopologyTemplate; + @Input() input: any; + + constructor(private store: Store, + private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private eventListenerService: EventListenerService) { + } + + ngOnInit() { + this.metaData = this.workspaceService.metadata; + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + this.initInstancesWithProperties(); + this.loadConstraints(); + this.initInstancesWithProperties(); + } + + public loadConstraints = (): void => { + this.topologyTemplateService.getSubstitutionFilterConstraints(this.metaData.componentType, this.metaData.uniqueId).subscribe((response) => { + this.componentInstancesConstraints = response.substitutionFilterForTopologyTemplate; + }); + } + + public notifyDependencyEventsObserver = (isChecked: boolean): void => { + this.eventListenerService.notifyObservers(SUBSTITUTION_FILTER_EVENTS.ON_SUBSTITUTION_FILTER_CHANGE, isChecked); + } + + public updateSelectedInstanceConstraints = (constraintsList:Array<ConstraintObject>):void => { + this.componentInstancesConstraints[this.component.uniqueId].properties = constraintsList; + this.selectedInstanceConstraints = this.componentInstancesConstraints[this.component.uniqueId].properties; + } + + private initInstancesWithProperties = (): void => { + this.topologyTemplateService.getComponentInstanceProperties(this.metaData.componentType, this.metaData.uniqueId).subscribe((genericResponse: ComponentGenericResponse) => { + this.componentInstanceProperties = genericResponse.componentInstancesProperties; + this.updateInstanceAttributes(); + }); + } + + + private updateInstanceAttributes = (): void => { + if (this.isComponentInstanceSelected && this.componentInstanceProperties) { + const instancesMappedList = this.compositionService.componentInstances.map((coInstance) => new ServiceInstanceObject({ + id: coInstance.uniqueId, + name: coInstance.name, + properties: this.componentInstanceProperties[coInstance.uniqueId] || [] + })); + this.selectedInstanceProperties = this.componentInstanceProperties[this.component.uniqueId]; + this.selectedInstanceSiblings = instancesMappedList.filter((coInstance) => coInstance.id !== this.component.uniqueId); + } + } +} diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts index 708742ae0c..003c6dc515 100644 --- a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts +++ b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts @@ -136,9 +136,11 @@ export class ServiceDependenciesEditorComponent { const selectedSourceType: UIDropDownSourceTypesElement = this.sourceTypes.find( (t) => t.value === this.currentRule.sourceName && t.type === this.currentRule.sourceType ); - this.listOfSourceOptions = selectedSourceType.options || []; - this.assignedValueLabel = selectedSourceType.assignedLabel || this.SOURCE_TYPES.STATIC.label; - this.filterOptionsByType(); + if(selectedSourceType) { + this.listOfSourceOptions = selectedSourceType.options || []; + this.assignedValueLabel = selectedSourceType.assignedLabel || this.SOURCE_TYPES.STATIC.label; + this.filterOptionsByType(); + } } } diff --git a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts index 095c8d6e76..cf30ea8a75 100644 --- a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts @@ -380,6 +380,10 @@ export class TopologyTemplateService { return this.getComponentDataByFieldsName(componentType, componentId, [SERVICE_FIELDS.NODE_FILTER]); } + getSubstitutionFilterConstraints(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [SERVICE_FIELDS.SUBSTITUTION_FILTER]); + } + getComponentInstanceProperties(componentType: string, componentId: string): Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES]); } @@ -396,6 +400,18 @@ export class TopologyTemplateService { return this.http.delete<any>(this.baseUrl + this.getServerTypeUrl(componentType) + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/nodeFilter/' + constraintIndex) } + createSubstitutionFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraint: ConstraintObject, componentType: string): Observable<any> { + return this.http.post<any>(this.baseUrl + this.getServerTypeUrl(componentType) + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/substitutionFilter', constraint); + } + + updateSubstitutionFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraint: ConstraintObject, componentType: string): Observable<any> { + return this.http.put<any>(this.baseUrl + this.getServerTypeUrl(componentType) + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/substitutionFilter', constraint); + } + + deleteSubstitutionFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraintIndex: number, componentType: string): Observable<any>{ + return this.http.delete<any>(this.baseUrl + this.getServerTypeUrl(componentType) + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/substitutionFilter/' + constraintIndex) + } + deletePolicy(component: Component, policy: PolicyInstance): Observable<PolicyInstance> { return this.http.put<PolicyInstance>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policy.uniqueId + '/undeclare', policy) } diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts index 301b3a4c9b..2c502d9fd5 100644 --- a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts +++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts @@ -57,6 +57,7 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR public additionalInformation:any; public derivedList:Array<any>; public nodeFilterData: Array<any>; + public substitutionFilterForTopologyTemplate: Array<any>; deserialize (response): ComponentGenericResponse { @@ -122,6 +123,9 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR if(response.nodeFilterData) { this.nodeFilterData = response.nodeFilterData; } + if(response.substitutionFilterForTopologyTemplate) { + this.substitutionFilterForTopologyTemplate = response.substitutionFilterForTopologyTemplate; + } return this; } } diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts index f2efc42e44..a4bceb5ded 100644 --- a/catalog-ui/src/app/utils/constants.ts +++ b/catalog-ui/src/app/utils/constants.ts @@ -366,6 +366,10 @@ export class DEPENDENCY_EVENTS { static ON_DEPENDENCY_CHANGE = 'onDependencyStatusChange'; } +export class SUBSTITUTION_FILTER_EVENTS { + static ON_SUBSTITUTION_FILTER_CHANGE = 'onSubstitutionFilterChange'; +} + export class COMPONENT_FIELDS { static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties"; @@ -396,6 +400,7 @@ export class COMPONENT_FIELDS { export class SERVICE_FIELDS { static FORWARDING_PATHS = "forwardingPaths"; static NODE_FILTER = "nodeFilter"; + static SUBSTITUTION_FILTER = "substitutionFilter"; } export class API_QUERY_PARAMS { diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json index adf5ebedcf..cdd580cec6 100644 --- a/catalog-ui/src/assets/languages/en_US.json +++ b/catalog-ui/src/assets/languages/en_US.json @@ -510,6 +510,12 @@ "DIRECTIVES_AND_NODE_FILTER_UPDATE_TITLE": "Update Directives", "DIRECTIVES_AND_NODE_FILTER_UPDATE_TEXT": "Changing \"Directive Option\" will remove directives value and erase all the node filter. Are you sure you want to update directives?", + "============= SUBSTITUTION FILTER MANAGE TAB ======" : "", + "ADD_SUBSTITUTION_FILTER": "Add Substitution Filter", + "UPDATE_SUBSTITUTION_FILTER": "Update Substitution Filter", + "DELETE_SUBSTITUTION_FILTER": "Delete Substitution Filter", + "DELETE_SUBSTITUTION_FILTER_MSG": "Are you sure you want to delete this Substitution Filter?", + "============= COMPOSITION DETAILS TAB ======" : "", "DETAILS_TAB_CHANGE_VERSION_MODAL_TITLE": "Change Version", "DETAILS_TAB_CHANGE_VERSION_MODAL_MSG": "Are you sure you want to change the version?\nIt will affect Service-Consumption and Service-Dependencies data", |