From 4192e3caac2662624a7368252a3bc5619539caa7 Mon Sep 17 00:00:00 2001 From: ojasdubey Date: Mon, 18 Mar 2019 14:15:03 +0530 Subject: Service Consumption FE Service consumption feature frontend implementation Change-Id: I68e1b507b1d4379b271fe97428ff8ae86dc11b4c Issue-ID: SDC-1990 Signed-off-by: ojasdubey --- catalog-ui/src/app/app.ts | 8 + .../composition-graph.directive.ts | 1 + catalog-ui/src/app/models.ts | 2 +- catalog-ui/src/app/models/inputs.ts | 11 + .../service-instance-properties-and-interfaces.ts | 35 +++ catalog-ui/src/app/modules/directive-module.ts | 7 + catalog-ui/src/app/modules/view-model-module.ts | 6 +- catalog-ui/src/app/ng2/app.module.ts | 4 + .../dynamic-property.component.html | 8 +- .../properties-table/property-table.module.ts | 2 +- .../service-consumption.component.html | 26 ++ .../service-consumption.component.less | 67 +++++ .../service-consumption.component.ts | 228 ++++++++++++++++ .../service-consumption.module.ts | 39 +++ .../service-consumption-editor.component.html | 98 +++++++ .../service-consumption-editor.component.less | 143 ++++++++++ .../service-consumption-editor.component.ts | 294 +++++++++++++++++++++ .../service-consumption-editor.module.ts | 28 ++ .../component-services/component.service.ts | 7 +- .../services/component-services/service.service.ts | 24 +- .../responses/component-generic-response.ts | 16 +- catalog-ui/src/app/utils/common-utils.ts | 6 +- catalog-ui/src/app/utils/constants.ts | 2 + .../tabs/composition/composition-view.html | 8 + .../workspace/tabs/composition/composition.less | 7 +- .../service-consumption-view-model.ts | 79 ++++++ .../service-consumption-view.html | 22 ++ 27 files changed, 1157 insertions(+), 21 deletions(-) create mode 100644 catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts create mode 100644 catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html create mode 100644 catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less create mode 100644 catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts create mode 100644 catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts create mode 100644 catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html create mode 100644 catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less create mode 100644 catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts create mode 100644 catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts create mode 100644 catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts create mode 100644 catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html (limited to 'catalog-ui/src') diff --git a/catalog-ui/src/app/app.ts b/catalog-ui/src/app/app.ts index ebcfdd2785..a147f8cab4 100644 --- a/catalog-ui/src/app/app.ts +++ b/catalog-ui/src/app/app.ts @@ -529,6 +529,14 @@ ng1appModule.config([ controller: viewModelsModuleName + '.ResourceArtifactsViewModel' } ); + $stateProvider.state( + 'workspace.composition.consumption', { + url: 'consumption', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html', + controller: viewModelsModuleName + '.ServiceConsumptionViewModel' + } + ); $stateProvider.state( 'workspace.composition.dependencies', { url: 'dependencies', diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts index 502188b3c2..e6c2fb395b 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts @@ -87,6 +87,7 @@ export interface ICompositionGraphScope extends ng.IScope { zones: Array; zoneMinimizeToggle(zoneType: ZoneInstanceType): void; zoneInstanceTagged(taggedInstance: ZoneInstance): void; + zoneBackgroundClicked() :void; zoneInstanceModeChanged(newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType); unsetActiveZoneInstance(): void; clickOutsideZoneInstance(): void; diff --git a/catalog-ui/src/app/models.ts b/catalog-ui/src/app/models.ts index 5c79155260..ad1da27c62 100644 --- a/catalog-ui/src/app/models.ts +++ b/catalog-ui/src/app/models.ts @@ -114,7 +114,7 @@ export * from './models/wizard-step'; export * from './models/radio-button'; export * from './models/filter-properties-assignment-data'; export * from './models/properties-inputs/input-be-model'; -export * from './models/service-instance-properties'; +export * from './models/service-instance-properties-and-interfaces'; export * from './models/relationship-types'; export * from './models/tosca-presentation'; export * from './models/node-types'; diff --git a/catalog-ui/src/app/models/inputs.ts b/catalog-ui/src/app/models/inputs.ts index cbed226324..e5b2274835 100644 --- a/catalog-ui/src/app/models/inputs.ts +++ b/catalog-ui/src/app/models/inputs.ts @@ -26,6 +26,17 @@ import {PropertyModel} from "./properties"; import {InputPropertyBase} from "./input-property-base"; import {SchemaPropertyGroupModel} from "./aschema-property"; +export class InputsGroup { + constructor(inputsObj?:InputsGroup) { + _.forEach(inputsObj, (inputs:Array, instance) => { + this[instance] = []; + _.forEach(inputs, (input:InputModel):void => { + this[instance].push(new InputModel(input)); + }); + }); + } +} + export interface IInputModel extends InputPropertyBase { //server data definition:boolean; diff --git a/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts b/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts new file mode 100644 index 0000000000..168b0af215 --- /dev/null +++ b/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts @@ -0,0 +1,35 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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. + */ + +import {PropertyModel, InputModel, InterfaceModel} from 'app/models'; + +export class ServiceInstanceObject { + id: string; + name: string; + properties: Array = []; + inputs: Array = []; + interfaces: Array = []; + + constructor(input?:any) { + if(input) { + this.id = input.id; + this.name = input.name; + this.properties = input.properties; + this.inputs = input.inputs; + this.interfaces = _.map(input.interfaces, interf => new InterfaceModel(interf)); + } + } +} diff --git a/catalog-ui/src/app/modules/directive-module.ts b/catalog-ui/src/app/modules/directive-module.ts index c541d58208..96bee45c59 100644 --- a/catalog-ui/src/app/modules/directive-module.ts +++ b/catalog-ui/src/app/modules/directive-module.ts @@ -183,6 +183,7 @@ import { SearchWithAutoCompleteComponent } from "../ng2/components/ui/search-wit import { PalettePopupPanelComponent } from "../ng2/components/ui/palette-popup-panel/palette-popup-panel.component"; import { ServicePathComponent } from '../ng2/components/logic/service-path/service-path.component'; import { ServicePathSelectorComponent } from '../ng2/components/logic/service-path-selector/service-path-selector.component'; +import { ServiceConsumptionComponent } from '../ng2/components/logic/service-consumption/service-consumption.component'; import { ServiceDependenciesComponent } from '../ng2/components/logic/service-dependencies/service-dependencies.component'; import { MultilineEllipsisComponent } from "../ng2/shared/multiline-ellipsis/multiline-ellipsis.component"; import { InterfaceOperationComponent } from '../ng2/pages/interface-operation/interface-operation.page.component'; @@ -246,6 +247,12 @@ directiveModule.directive('ng2ServicePathSelector', downgradeComponent({ outputs: [] }) as angular.IDirectiveFactory); +directiveModule.directive('ng2ServiceConsumption', downgradeComponent({ + component: ServiceConsumptionComponent, + inputs: ['parentService', 'selectedService', 'selectedServiceInstanceId', 'instancesMappedList','parentServiceInputs', 'readonly'], + outputs: [] +}) as angular.IDirectiveFactory); + directiveModule.directive('ng2ServiceDependencies', downgradeComponent({ component: ServiceDependenciesComponent, inputs: ['compositeService', 'currentServiceInstance', 'selectedInstanceProperties', 'selectedInstanceSiblings', 'selectedInstanceConstraints', 'readonly'], diff --git a/catalog-ui/src/app/modules/view-model-module.ts b/catalog-ui/src/app/modules/view-model-module.ts index a390ab4fb4..8c8f239c4e 100644 --- a/catalog-ui/src/app/modules/view-model-module.ts +++ b/catalog-ui/src/app/modules/view-model-module.ts @@ -24,6 +24,7 @@ import {WorkspaceViewModel} from "../view-models/workspace/workspace-view-model" import {CompositionViewModel} from "../view-models/workspace/tabs/composition/composition-view-model"; import {DetailsViewModel} from "../view-models/workspace/tabs/composition/tabs/details/details-view-model"; import {ResourceArtifactsViewModel} from "../view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model"; +import {ServiceConsumptionViewModel} from "../view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model"; import {ServiceDependenciesViewModel} from "../view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view-model"; import {PropertyFormBaseView} from "../view-models/forms/property-forms/base-property-form/property-form-base-model"; import {PropertyFormViewModel} from "../view-models/forms/property-forms/component-property-form/property-form-view-model"; @@ -83,6 +84,7 @@ viewModelModule .controller(moduleName + '.DetailsViewModel', DetailsViewModel) .controller(moduleName + '.ResourceArtifactsViewModel', ResourceArtifactsViewModel) + .controller(moduleName + '.ServiceConsumptionViewModel', ServiceConsumptionViewModel) .controller(moduleName + '.ServiceDependenciesViewModel', ServiceDependenciesViewModel) .controller(moduleName + '.PropertyFormBaseView', PropertyFormBaseView) .controller(moduleName + '.PropertyFormViewModel', PropertyFormViewModel) @@ -140,5 +142,5 @@ viewModelModule // //TABS .controller(moduleName + '.HierarchyViewModel', HierarchyViewModel); - // NG2 - //.controller(moduleName + '.NG2Example', downgradeComponent({component: NG2Example2Component}) ); +// NG2 +//.controller(moduleName + '.NG2Example', downgradeComponent({component: NG2Example2Component}) ); diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts index 0ff9378949..22c6624348 100644 --- a/catalog-ui/src/app/ng2/app.module.ts +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -61,6 +61,8 @@ import { ServicePathCreatorModule } from './pages/service-path-creator/service-p import { ServicePathsListModule } from './pages/service-paths-list/service-paths-list.module'; import { ServicePathModule } from 'app/ng2/components/logic/service-path/service-path.module'; import { ServicePathSelectorModule } from 'app/ng2/components/logic/service-path-selector/service-path-selector.module'; +import { ServiceConsumptionModule } from 'app/ng2/components/logic/service-consumption/service-consumption.module'; +import { ServiceConsumptionCreatorModule } from './pages/service-consumption-editor/service-consumption-editor.module'; import {ServiceDependenciesModule} from 'app/ng2/components/logic/service-dependencies/service-dependencies.module'; import {ServiceDependenciesEditorModule} from './pages/service-dependencies-editor/service-dependencies-editor.module'; import { CompositionPanelModule } from 'app/ng2/pages/composition/panel/panel.module'; @@ -116,6 +118,8 @@ export function configServiceFactory(config: ConfigService) { ServicePathsListModule, ServicePathModule, ServicePathSelectorModule, + ServiceConsumptionModule, + ServiceConsumptionCreatorModule, ServiceDependenciesModule, ServiceDependenciesEditorModule, RequirementsEditorModule, diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html index d4e7b02930..3f87b070e8 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html @@ -12,8 +12,8 @@ * See the License for the specific language governing permissions and * limitations under the License. --> - - + +
{{property.name}}
-
{{property.name}}
+
+
{{property.name}}
+
+ +
{{'CONSUMPTION_NO_OPERATIONS_TO_SHOW' | translate}}
+ +
+
+ +
{{interface.displayName}}
+
+
+
+
+ {{serviceOperation.operation.name}} +
+
+
+
+
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less new file mode 100644 index 0000000000..5830c06972 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less @@ -0,0 +1,67 @@ +@import './../../../../../assets/styles/variables.less'; +@import './../../../../../assets/styles/variables-old.less'; +@import './../../../../../assets/styles/mixins_old.less'; +@import './../../../../../assets/styles/sprite.less'; + +.service-consumption { + min-height: 100px; + .no-operations-text { + text-align: center; + padding: 30px; + opacity: 0.7; + font-size: 14px; + } + .interface-operations-group { + &:first-of-type { + margin-top: 1px; + } + .interface-title { + display: flex; + .title-text { + font-size: 14px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 90%; + padding-left: 4px; + } + &.expanded { + background-color: @tlv_color_v; + border-left: 4px solid @main_color_a; + box-shadow: 0 0px 3px -1px rgba(0, 0, 0, 0.3); + margin-bottom: 2px; + padding-left: 4px !important; + .expand-collapse-icon { + .expand-collapse-minus-icon; + } + } + .expand-collapse-icon { + margin: 0 6px; + display: inline-block; + margin-top: 4px; + } + } + .operation-data { + border-bottom: 1px solid #c8cdd1; + padding: 5px 10px 5px 18px; + min-height: 61px; + display: flex; + align-items: center; + } + + .operation-name { + .s_1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 220px; + display: inline-block; + &:not(.readonly):hover { + .a_7; + } + &.readonly { + opacity: 0.5; + } + } + } +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts new file mode 100644 index 0000000000..b0f6dfb649 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts @@ -0,0 +1,228 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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. + */ + +import {Component, Input, ComponentRef} from '@angular/core'; +import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; +import {ComponentInstanceServiceNg2} from 'app/ng2/services/component-instance-services/component-instance.service'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {ModalService} from 'app/ng2/services/modal.service'; +import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; +import { + ModalModel, + ButtonModel, + OperationModel, + Service, + ServiceInstanceObject, + PropertyFEModel, + PropertyBEModel, + InputBEModel, + InterfaceModel +} from 'app/models'; +import {ServiceConsumptionCreatorComponent} from 'app/ng2/pages/service-consumption-editor/service-consumption-editor.component'; + + +export class ConsumptionInput extends PropertyFEModel{ + inputId: string; + type: string; + source: string; + value: any; + + constructor(input?: any) { + super(input); + if (input) { + this.inputId = input.inputId; + this.type = input.type; + this.source = input.source; + this.value = input.value || ""; + } + } +} + +export class ConsumptionInputDetails extends ConsumptionInput { + name: string; + expanded: boolean; + assignValueLabel: string; + associatedProps: Array; + associatedInterfaces: Array; + origVal: string; + isValid: boolean; + + constructor(input: any) { + super(input); + if (input) { + this.name = input.name; + this.expanded = input.expanded; + this.assignValueLabel = input.assignValueLabel; + this.associatedProps = input.associatedProps; + this.associatedInterfaces = input.associatedInterfaces; + this.origVal = input.value || ""; + this.isValid = input.isValid; + } + } + + public updateValidity(isValid: boolean) { + this.isValid = isValid; + } +} + +export class ServiceOperation { + operation: OperationModel; + consumptionInputs: Array; + + constructor(input?: any) { + if (input) { + this.operation = new OperationModel(input.operation || {}); + this.consumptionInputs = input.consumptionInputs || []; + } + } +} + +export class InterfaceWithServiceOperation { + interfaceId: string; + displayName: string; + operationsList: Array; + isExpanded: boolean; + + constructor(input?: InterfaceModel) { + if (input) { + this.interfaceId = input.uniqueId; + this.displayName = input.displayType(); + this.operationsList = _.map(input.operations, operation => new ServiceOperation({operation: operation})); + this.isExpanded = true; + } + } +} + + + +@Component({ + selector: 'service-consumption', + templateUrl: './service-consumption.component.html', + styleUrls: ['service-consumption.component.less'], + providers: [ModalService] +}) + +export class ServiceConsumptionComponent { + + modalInstance: ComponentRef; + isLoading: boolean = false; + interfacesList: Array; + operationsGroup: Array; + @Input() parentServiceInputs: Array = []; + @Input() parentService: Service; + @Input() selectedService: Service; + @Input() selectedServiceInstanceId: string; + @Input() instancesMappedList: Array; + @Input() readonly: boolean; + + selectedInstanceSiblings: Array; + selectedInstancePropertiesList: Array = []; + + constructor(private ModalServiceNg2: ModalService, private serviceServiceNg2: ServiceServiceNg2, private componentServiceNg2: ComponentServiceNg2, private componentInstanceServiceNg2:ComponentInstanceServiceNg2) {} + + ngOnInit() { + this.updateSelectedInstancePropertiesAndSiblings(); + } + + ngOnChanges(changes) { + if(changes.selectedServiceInstanceId && changes.selectedServiceInstanceId.currentValue !== changes.selectedServiceInstanceId.previousValue) { + this.selectedServiceInstanceId = changes.selectedServiceInstanceId.currentValue; + if(changes.selectedService && changes.selectedService.currentValue !== changes.selectedService.previousValue) { + this.selectedService = changes.selectedService.currentValue; + } + this.updateSelectedInstancePropertiesAndSiblings(); + } + if(changes.instancesMappedList && !_.isEqual(changes.instancesMappedList.currentValue, changes.instancesMappedList.previousValue)) { + this.updateSelectedInstancePropertiesAndSiblings(); + } + } + + updateSelectedInstancePropertiesAndSiblings() { + this.interfacesList = []; + let selectedInstanceMetadata: ServiceInstanceObject = _.find(this.instancesMappedList, coInstance => coInstance.id === this.selectedServiceInstanceId); + if (selectedInstanceMetadata) { + _.forEach(selectedInstanceMetadata.interfaces, (interfaceData:InterfaceModel) => { + this.interfacesList.push(new InterfaceWithServiceOperation(interfaceData)); + }); + } + this.interfacesList.sort((interf1:InterfaceWithServiceOperation, interf2:InterfaceWithServiceOperation) => interf1.displayName.localeCompare(interf2.displayName)); + + this.selectedInstancePropertiesList = selectedInstanceMetadata && selectedInstanceMetadata.properties; + this.selectedInstanceSiblings = _.filter(this.instancesMappedList, coInstance => coInstance.id !== this.selectedServiceInstanceId); + } + + expandCollapseInterfaceGroup(currInterface) { + currInterface.isExpanded = !currInterface.isExpanded; + } + + onSelectOperation(event, currInterface:InterfaceWithServiceOperation, opIndex: number) { + event.stopPropagation(); + if(!this.readonly) { + this.operationsGroup = currInterface.operationsList; + let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.ModalServiceNg2.closeCurrentModal); + let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createOrUpdateOperationInput, this.getDisabled); + let modalModel: ModalModel = new ModalModel('l', 'Modify Operation Consumption', '', [saveButton, cancelButton], 'standard'); + this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); + this.ModalServiceNg2.addDynamicContentToModal( + this.modalInstance, + ServiceConsumptionCreatorComponent, + { + interfaceId: currInterface.interfaceId, + serviceOperationIndex: opIndex, + serviceOperations: this.operationsGroup, + parentService: this.parentService, + selectedService: this.selectedService, + parentServiceInputs: this.parentServiceInputs, + selectedServiceProperties: this.selectedInstancePropertiesList, + selectedServiceInstanceId: this.selectedServiceInstanceId, + selectedInstanceSiblings: this.selectedInstanceSiblings + } + ); + this.modalInstance.instance.open(); + } + } + + createOrUpdateOperationInput = ():void => { + this.isLoading = true; + let consumptionInputsList:Array<{[id: string]: Array}> = _.map(this.operationsGroup, (serviceOp) => { + let consumptionInputsArr: Array = []; + if(serviceOp.consumptionInputs) { + consumptionInputsArr = _.map(serviceOp.consumptionInputs, (input: ConsumptionInputDetails) => { + return { + inputId: input.inputId, + type: input.isSimpleType ? input.type : 'json', + source: input.source, + value: input.value + }; + }); + } + return { + [serviceOp.operation.uniqueId]: consumptionInputsArr + }; + }); + this.serviceServiceNg2.createOrUpdateServiceConsumptionInputs(this.parentService,this.selectedServiceInstanceId, consumptionInputsList).subscribe(() => { + this.isLoading = false; + }, err=> { + this.isLoading = false; + }); + this.ModalServiceNg2.closeCurrentModal(); + }; + + getDisabled = ():boolean => { + return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); + }; + +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts new file mode 100644 index 0000000000..8593bef3eb --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts @@ -0,0 +1,39 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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. + */ + +import { NgModule } from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {ServiceConsumptionComponent} from "./service-consumption.component"; +import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; + +@NgModule({ + declarations: [ + ServiceConsumptionComponent + ], + imports: [ + CommonModule, + UiElementsModule, + TranslateModule + ], + exports: [], + entryComponents: [ + ServiceConsumptionComponent + ], + providers: [] +}) +export class ServiceConsumptionModule { +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html new file mode 100644 index 0000000000..5d42fa7694 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html @@ -0,0 +1,98 @@ + +
+
+ +
+
{{serviceOperation.operation.name}}
+ +
+ + +
+
+
+
{{'CONSUMPTION_EXPAND_ALL' | translate}}
+
+
{{'CONSUMPTION_COLLAPSE_ALL' | translate}}
+
+ + + + +
+
{{'CONSUMPTION_NO_INPUTS_TO_SHOW' | translate}}
+
+
+
+
{{consumptionInput.name}}
+
+ | {{ 'CONSUMPTION_TYPE' | translate}}: + {{consumptionInput.type}} +
+
+ +
+
+ + + +
+ +
+ + + + +
+
+
+
+ +
+ + +
+
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less new file mode 100644 index 0000000000..83481c1d74 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less @@ -0,0 +1,143 @@ +@import './../../../../assets/styles/variables.less'; +@import './../../../../assets/styles/variables-old.less'; +@import './../../../../assets/styles/sprite.less'; +@import './../../../../assets/styles/mixins_old.less'; + +.service-consumption-editor { + .sdc-modal-top-bar { + display: flex; + justify-content: space-between; + .operation-name { + text-transform: capitalize; + font-family: @font-opensans-bold; + font-size: 18px; + } + } + .expand-collapse-all { + display: flex; + .expand-all, .collapse-all { + color: @main_color_a; + border-bottom: solid 1px @main_color_a; + font-size: 12px; + font-family: @font-opensans-regular; + cursor: pointer; + width: max-content; + line-height: 24px; + } + .separator-line { + border-left: 1px solid @main_color_o; + margin: 0 7px; + } + } + + .i-sdc-form-content-operation-inputs-content { + margin-top: 6px; + padding-top: 10px; + height: 390px; + overflow: auto; + &.no-inputs { + border: 1px solid @main_color_o; + } + .no-inputs-text { + text-align: center; + padding: 30px; + opacity: 0.7; + } + .i-sdc-form-content-operation-input-box { + border: solid 1px @main_color_o; + &:not(:last-of-type) { + margin-bottom: 17px; + } + + .i-sdc-form-content-operation-input-box-title { + .hand; + display: flex; + align-items: center; + padding-left: 10px; + height: 38px; + font-size: 14px; + .expand-collapse-icon { + .sprite-new; + .expand-collapse-plus-icon; + vertical-align: middle; + &.expanded { + .expand-collapse-minus-icon; + } + } + .operation-input-name { + margin-left: 10px; + margin-right: 7px; + font-family: @font-opensans-bold + } + .operation-input-type { + .type-text { + font-family: @font-opensans-bold; + } + .type-val { + font-family: @font-opensans-regular; + } + } + } + + .operation-input-section-row { + &.with-top-border, .separator { + border-top: 1px solid @main_color_o; + } + background-color: @tlv_color_t; + display: flex; + padding: 19px 20px 12px 19px; + .operation-input-section-col { + flex: 45%; + &:not(:last-of-type) { + margin-right: 38px; + } + .i-sdc-form-label { + font-size: 12px; + font-family: @font-opensans-bold; + } + + &.assigned-value { + display: flex; + flex-direction: column; + .i-sdc-form-label { + margin-bottom: 0; + } + } + + /deep/ ui-element-dropdown select, select, + /deep/ dynamic-element input { + height: 30px; + } + + select { + margin-top: 2px; + border: solid 1px @main_color_o; + } + } + } + + //for complex types + /deep/ .operation-input-complex-type-section { + background-color: @tlv_color_t; + padding: 0 20px 12px 19px; + .separator { + border-top: 1px solid @main_color_o; + padding-bottom: 19px; + } + .static-values-title-for-complex-type { + font-size: 14px; + font-family: @font-opensans-bold; + margin-bottom: 10px; + } + .flat-children-container { + border: solid 1px @main_color_o; + } + .dynamic-property { + .dynamic-property-row { + background-color: @tlv_color_t; + } + } + } + } + } +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts new file mode 100644 index 0000000000..25f412671f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts @@ -0,0 +1,294 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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. + */ + +import * as _ from "lodash"; +import { Component } from '@angular/core'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {Service, ServiceInstanceObject, InstanceFePropertiesMap, InstanceBePropertiesMap, PropertyBEModel, InputBEModel, OperationModel, InterfaceModel} from 'app/models'; +import {ConsumptionInput, ConsumptionInputDetails, ServiceOperation} from 'app/ng2/components/logic/service-consumption/service-consumption.component'; +import {PropertiesUtils} from "app/ng2/pages/properties-assignment/services/properties.utils"; +import { PROPERTY_DATA } from 'app/utils'; + + +@Component({ + selector: 'service-consumption-editor', + templateUrl: './service-consumption-editor.component.html', + styleUrls:['./service-consumption-editor.component.less'], + providers: [] +}) + +export class ServiceConsumptionCreatorComponent { + + input: { + interfaceId: string; + serviceOperationIndex: number, + serviceOperations: Array, + parentService: Service, + selectedService: Service, + parentServiceInputs: Array, + selectedServiceProperties: Array, + selectedServiceInstanceId: String, + selectedInstanceSiblings: Array + }; + sourceTypes: Array = []; + serviceOperationsList: Array; + serviceOperation: ServiceOperation; + currentIndex: number; + isLoading: boolean = false; + parentService: Service; + selectedService: Service; + selectedServiceInstanceId: String; + parentServiceInputs: Array; + selectedServiceProperties: Array; + changedData: Array = []; + inputFePropertiesMap: any = []; + + SOURCE_TYPES = { + STATIC: 'Static', + SELF: 'Self', + SERVICE_PROPERTY_LABEL: 'Service Property', + SERVICE_INPUT_LABEL: 'Service Input' + }; + + constructor(private serviceServiceNg2: ServiceServiceNg2, private propertiesUtils:PropertiesUtils) {} + + ngOnInit() { + this.serviceOperationsList = this.input.serviceOperations; + this.currentIndex = this.input.serviceOperationIndex; + this.serviceOperation = this.serviceOperationsList[this.currentIndex]; + this.parentService = this.input.parentService; + this.selectedService = this.input.selectedService; + this.selectedServiceInstanceId = this.input.selectedServiceInstanceId; + this.parentServiceInputs = this.input.parentServiceInputs || []; + this.selectedServiceProperties = this.input.selectedServiceProperties || []; + this.initSourceTypes(); + this.initConsumptionInputs(); + } + + initSourceTypes() { + this.sourceTypes = [ + { label: this.SOURCE_TYPES.STATIC, value: this.SOURCE_TYPES.STATIC, options: [], interfaces: []}, + { label: this.SOURCE_TYPES.SELF + ' (' + this.selectedService.name + ')', + value: this.selectedServiceInstanceId, + options: this.selectedServiceProperties, + interfaces: this.selectedService.interfaces + }, + { label: this.parentService.name, + value: this.parentService.uniqueId, + options: this.parentServiceInputs, + interfaces: [] + } + ]; + _.forEach(this.input.selectedInstanceSiblings, sib => + this.sourceTypes.push({ + label: sib.name, + value: sib.id, + options: _.unionBy(sib.inputs, sib.properties, 'uniqueId'), + interfaces: sib.interfaces + }) + ); + } + + onExpandCollapse(consumptionInput: ConsumptionInputDetails) { + consumptionInput.expanded = !consumptionInput.expanded; + } + + onExpandAll() { + _.forEach(this.serviceOperation.consumptionInputs, coInput => { + coInput.expanded = true; + }) + } + onCollapseAll() { + _.forEach(this.serviceOperation.consumptionInputs, coInput => { + coInput.expanded = false; + }) + } + + isAllInputExpanded() { + return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === true); + } + isAllInputCollapsed() { + return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === false); + } + + onChangePage(newIndex) { + if (newIndex >= 0 && newIndex < this.serviceOperationsList.length) { + this.currentIndex = newIndex; + this.serviceOperation = this.serviceOperationsList[newIndex]; + if(!this.serviceOperation.consumptionInputs || this.serviceOperation.consumptionInputs.length === 0) { + this.initConsumptionInputs(); + } + this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs); + } + } + + private initConsumptionInputs() { + this.isLoading = true; + this.serviceServiceNg2.getServiceConsumptionInputs(this.parentService, this.selectedServiceInstanceId, this.input.interfaceId, this.serviceOperation.operation).subscribe((result: Array) => { + this.isLoading = false; + this.serviceOperation.consumptionInputs = this.analyzeCurrentConsumptionInputs(result); + this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs); + }, err=> { + this.isLoading = false; + }); + } + + private analyzeCurrentConsumptionInputs(result: Array): Array { + let inputsResult: Array = []; + let currentOp = this.serviceOperation.operation; + if(currentOp) { + inputsResult = _.map(result, input => { + let sourceVal = input.source || this.SOURCE_TYPES.STATIC; + let consumptionInputDetails: ConsumptionInputDetails = _.cloneDeep(input); + consumptionInputDetails.source = sourceVal; + consumptionInputDetails.isValid = true; + consumptionInputDetails.expanded = false; + let filteredListsObj = this.getFilteredProps(sourceVal, input.type); + consumptionInputDetails.assignValueLabel = this.getAssignValueLabel(sourceVal); + consumptionInputDetails.associatedProps = filteredListsObj.associatedPropsList; + consumptionInputDetails.associatedInterfaces = filteredListsObj.associatedInterfacesList; + return new ConsumptionInputDetails(consumptionInputDetails); + }); + } + return inputsResult; + } + + private onSourceChanged(consumptionInput: ConsumptionInputDetails): void { + consumptionInput.assignValueLabel = this.getAssignValueLabel(consumptionInput.source); + let filteredListsObj = this.getFilteredProps(consumptionInput.source, consumptionInput.type); + consumptionInput.associatedProps = filteredListsObj.associatedPropsList; + consumptionInput.associatedInterfaces = filteredListsObj.associatedInterfacesList; + if(consumptionInput.source === this.SOURCE_TYPES.STATIC) { + if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(consumptionInput.type) !== -1) { + consumptionInput.value = consumptionInput.defaultValue || ""; + } + else { + consumptionInput.value = null; + Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(consumptionInput)); + } + } + } + + private getFilteredProps(sourceVal, inputType) { + let currentSourceObj = this.sourceTypes.find(s => s.value === sourceVal); + let associatedInterfacesList = [], associatedPropsList = []; + if(currentSourceObj) { + if (currentSourceObj.interfaces) { + associatedInterfacesList = this.getFilteredInterfaceOutputs(currentSourceObj, inputType); + } + associatedPropsList = currentSourceObj.options.reduce((result, prop) => { + if (prop.type === inputType) { + result.push(prop.name); + } + return result; + }, []); + } + return { + associatedPropsList: associatedPropsList, + associatedInterfacesList: associatedInterfacesList + } + } + + private getFilteredInterfaceOutputs(currentSourceObj, inputType) { + let currentServiceOperationId = this.serviceOperation.operation.uniqueId; + let filteredInterfacesList = []; + Object.keys(currentSourceObj.interfaces).map(interfId => { + let interfaceObj: InterfaceModel = new InterfaceModel(currentSourceObj.interfaces[interfId]); + Object.keys(interfaceObj.operations).map(opId => { + if(currentServiceOperationId !== opId) { + let operationObj: OperationModel = interfaceObj.operations[opId]; + let filteredOutputsList = _.filter(operationObj.outputs.listToscaDataDefinition, output => output.type === inputType); + if (filteredOutputsList.length) { + filteredInterfacesList.push({ + name: `${interfaceObj.type}.${operationObj.name}`, + label: `${interfaceObj.displayType()}.${operationObj.name}`, + outputs: filteredOutputsList + }); + } + } + }); + }); + return filteredInterfacesList; + } + + getAssignValueLabel(selectedSource: string): string { + if(selectedSource === this.SOURCE_TYPES.STATIC || selectedSource === "") { + return this.SOURCE_TYPES.STATIC; + } + else { + if(selectedSource === this.parentService.uniqueId) { //parent is the source + return this.SOURCE_TYPES.SERVICE_INPUT_LABEL; + } + return this.SOURCE_TYPES.SERVICE_PROPERTY_LABEL; + } + } + + + private isValidInputsValues(): boolean { + return this.changedData.length > 0 && this.changedData.every((changedItem) => changedItem.isValid); + } + + private isMandatoryFieldsValid(): boolean { + const invalid: Array = this.serviceOperation.consumptionInputs.filter(item => + item.required && (item.value === null || typeof item.value === 'undefined' || item.value === '')); + if (invalid.length > 0) { + return false; + } + return true; + } + + checkFormValidForSubmit(): boolean { + return this.isValidInputsValues() && this.isMandatoryFieldsValid(); + } + + checkFormValidForNavigation(): boolean { + return this.isMandatoryFieldsValid() && (this.changedData.length === 0 || this.isValidInputsValues()); + } + + onChange(value: any, isValid: boolean, consumptionInput: ConsumptionInputDetails) { + consumptionInput.updateValidity(isValid); + const dataChangedIndex = this.changedData.findIndex((changedItem) => changedItem.inputId === consumptionInput.inputId); + if (value !== consumptionInput.origVal) { + if (dataChangedIndex === -1) { + this.changedData.push(consumptionInput); + } + } else { + if (dataChangedIndex !== -1) { + this.changedData.splice(dataChangedIndex, 1); + } + } + } + + private getComplexPropertiesForCurrentInputsOfOperation(opInputs: Array) { + _.forEach(opInputs, input => { + if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(input.type) === -1 && input.source === this.SOURCE_TYPES.STATIC) { + Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(input)); + } + }); + } + + private processPropertiesOfComplexTypeInput(input: ConsumptionInput): InstanceFePropertiesMap { + let inputBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + inputBePropertiesMap[input.name] = [input]; + let originTypeIsVF = false; + return this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(inputBePropertiesMap, originTypeIsVF); //create flattened children and init values + } + + onComplexPropertyChanged(property, consumptionInput) { + consumptionInput.value = JSON.stringify(property.valueObj); + this.onChange(property.valueObj, property.valueObjIsValid , consumptionInput); + } +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts new file mode 100644 index 0000000000..e37cd76716 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {ServiceConsumptionCreatorComponent} from "./service-consumption-editor.component"; +import {FormsModule} from "@angular/forms"; +import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; +import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import {PropertyTableModule} from 'app/ng2/components/logic/properties-table/property-table.module'; +import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; + +@NgModule({ + declarations: [ + ServiceConsumptionCreatorComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + UiElementsModule, + PropertyTableModule, + TranslateModule + ], + exports: [], + entryComponents: [ + ServiceConsumptionCreatorComponent + ], + providers: [] +}) +export class ServiceConsumptionCreatorModule { +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts index 46dfe01992..a25fb75a41 100644 --- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts @@ -25,10 +25,9 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; import {Response, URLSearchParams} from '@angular/http'; import { Component, ComponentInstance, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, - PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement -} from "app/models"; -import {downgradeInjectable} from '@angular/upgrade/static'; + PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement} from "app/models"; import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils"; +import {downgradeInjectable} from '@angular/upgrade/static'; import {ComponentGenericResponse} from "../responses/component-generic-response"; import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map"; import {API_QUERY_PARAMS} from "app/utils"; @@ -282,7 +281,7 @@ export class ComponentServiceNg2 { }) } - restoreComponent(componentType:string, componentId:string){ + restoreComponent(componentType:string, componentId:string){ return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {}) } diff --git a/catalog-ui/src/app/ng2/services/component-services/service.service.ts b/catalog-ui/src/app/ng2/services/component-services/service.service.ts index 15e624b81f..406ac77ec4 100644 --- a/catalog-ui/src/app/ng2/services/component-services/service.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/service.service.ts @@ -23,8 +23,7 @@ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; import { Response, URLSearchParams } from '@angular/http'; -import {Service} from "app/models"; -import { downgradeInjectable } from '@angular/upgrade/static'; +import {Service, OperationModel} from "app/models"; import { HttpService } from '../http.service'; import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config"; @@ -37,6 +36,7 @@ import {COMPONENT_FIELDS, SERVICE_FIELDS} from "app/utils/constants"; import {ComponentServiceNg2} from "./component.service"; import {ServiceGenericResponse} from "app/ng2/services/responses/service-generic-response"; import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; +import {ConsumptionInput} from 'app/ng2/components/logic/service-consumption/service-consumption.component'; @Injectable() @@ -110,6 +110,26 @@ export class ServiceServiceNg2 extends ComponentServiceNg2 { }); } + getServiceConsumptionData(service: Service):Observable { + return this.getComponentDataByFieldsName(service.componentType, service.uniqueId, [ + COMPONENT_FIELDS.COMPONENT_INSTANCES_INTERFACES, + COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, + COMPONENT_FIELDS.COMPONENT_INSTANCES_INPUTS, + COMPONENT_FIELDS.COMPONENT_INPUTS + ]); + } + + getServiceConsumptionInputs(service: Service, serviceInstanceId: String, interfaceId: string, operation: OperationModel): Observable { + return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId + '/interfaces/' + interfaceId + '/operations/' + operation.uniqueId + '/inputs') + .map(res => { + return res.json(); + }); + } + + createOrUpdateServiceConsumptionInputs(service: Service, serviceInstanceId: String, consumptionInputsList: Array<{[id: string]: Array}>): Observable { + return this.http.post(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId, consumptionInputsList); + } + checkComponentInstanceVersionChange(service: Service, newVersionId: string):Observable> { let instanceId = service.selectedInstance.uniqueId; let queries = {componentInstanceId: instanceId, newComponentInstanceId: newVersionId}; 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 37ccf381c9..d297ea0874 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 @@ -22,11 +22,10 @@ * Created by ob0695 on 4/18/2017. */ -import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel, - InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup,InputFEModel} from "app/models"; +import { ArtifactGroupModel, PropertyModel, PropertiesGroup, InputsGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel, + InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup, InterfaceModel} from "app/models"; import {CommonUtils} from "app/utils"; import {Serializable} from "../utils/serializable"; -import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model"; import { PolicyInstance } from "app/models/graph/zones/policy-instance"; import { GroupInstance } from "../../../models/graph/zones/group-instance"; @@ -37,9 +36,11 @@ export class ComponentGenericResponse implements Serializable; public componentInstances:Array; + public componentInstancesInterfaces:Map>; public inputs:Array; public capabilities:CapabilitiesGroup; public requirements:RequirementsGroup; @@ -59,6 +60,9 @@ export class ComponentGenericResponse implements Serializable
+