Service Consumption FE 63/82563/3
authorojasdubey <ojas.dubey@amdocs.com>
Mon, 18 Mar 2019 08:45:03 +0000 (14:15 +0530)
committerAvi Gaffa <avi.gaffa@amdocs.com>
Mon, 18 Mar 2019 11:38:42 +0000 (11:38 +0000)
Service consumption feature
frontend implementation

Change-Id: I68e1b507b1d4379b271fe97428ff8ae86dc11b4c
Issue-ID: SDC-1990
Signed-off-by: ojasdubey <ojas.dubey@amdocs.com>
27 files changed:
catalog-ui/src/app/app.ts
catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
catalog-ui/src/app/models.ts
catalog-ui/src/app/models/inputs.ts
catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts [new file with mode: 0644]
catalog-ui/src/app/modules/directive-module.ts
catalog-ui/src/app/modules/view-model-module.ts
catalog-ui/src/app/ng2/app.module.ts
catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html
catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts
catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/services/component-services/component.service.ts
catalog-ui/src/app/ng2/services/component-services/service.service.ts
catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
catalog-ui/src/app/utils/common-utils.ts
catalog-ui/src/app/utils/constants.ts
catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html
catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts [new file with mode: 0644]
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html [new file with mode: 0644]

index ebcfdd2..a147f8c 100644 (file)
@@ -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',
index 502188b..e6c2fb3 100644 (file)
@@ -87,6 +87,7 @@ export interface ICompositionGraphScope extends ng.IScope {
     zones: Array<Zone>;
     zoneMinimizeToggle(zoneType: ZoneInstanceType): void;
     zoneInstanceTagged(taggedInstance: ZoneInstance): void;
+    zoneBackgroundClicked() :void;
     zoneInstanceModeChanged(newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType);
     unsetActiveZoneInstance(): void;
     clickOutsideZoneInstance(): void;
index 5c79155..ad1da27 100644 (file)
@@ -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';
index cbed226..e5b2274 100644 (file)
@@ -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<InputModel>, 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 (file)
index 0000000..168b0af
--- /dev/null
@@ -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<PropertyModel> = [];
+    inputs: Array<InputModel> = [];
+    interfaces: Array<InterfaceModel> = [];
+
+    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));
+        }
+    }
+}
index c541d58..96bee45 100644 (file)
@@ -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'],
index a390ab4..8c8f239 100644 (file)
@@ -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}) );
index 0ff9378..22c6624 100644 (file)
@@ -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,
index d4e7b02..3f87b07 100644 (file)
@@ -12,8 +12,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  -->
+
+
 <div *ngIf="!property.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn]
     [ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.propertiesName, 'readonly':  property.isDisabled ||property.isDeclared}"
     [class.with-top-border]="property.isChildOfListOrMap"
@@ -24,7 +24,9 @@
             <checkbox *ngIf="hasDeclareOption" [(checked)]="property.isSelected" [disabled]="property.isDisabled ||property.isDeclared || readonly" (checkedChange)="checkProperty.emit(property.propertiesName)" ></checkbox>
             <div class="inner-cell-div" tooltip="{{property.name}}"><span>{{property.name}}</span></div>
         </div>
-        <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">{{property.name}}</div> <!-- simple children of complex type within map or list -->
+        <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">
+            <div class="inner-cell-div" tooltip="{{property.name}}"><span>{{property.name}}</span></div>
+        </div> <!-- simple children of complex type within map or list -->
         <div class="table-cell map-entry" *ngIf="property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP"><!-- map left cell -->
             <!--<input [value]="property.mapKey" [placeholder]="property.name" (input)="mapKeyChanged.emit($event.target.value)" [readonly]="readonly" type="text" [ngClass]="{'disabled':readonly, 'error':property.mapKeyError}" required/>-->
             <dynamic-element #mapKeyInput
index 91baaf1..cb8c9a6 100644 (file)
@@ -24,7 +24,7 @@ import {MultilineEllipsisModule} from "../../../shared/multiline-ellipsis/multil
         DynamicPropertyComponent,
         PropertiesTableComponent
     ],
-    exports: [PropertiesTableComponent],
+    exports: [PropertiesTableComponent, DynamicPropertyComponent],
     providers: [FilterChildPropertiesPipe, PropertiesService]
 })
 export class PropertyTableModule {
diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html
new file mode 100644 (file)
index 0000000..9c52afb
--- /dev/null
@@ -0,0 +1,26 @@
+<div class='service-consumption'>
+    <loader [display]="isLoading" [size]="'medium'" [relative]="true"></loader>
+    <div class="no-operations-text" data-tests-id="no-operations" *ngIf="!isLoading && !interfacesList.length">{{'CONSUMPTION_NO_OPERATIONS_TO_SHOW' | translate}}</div>
+
+    <div class="interface-operations-group"
+         *ngFor="let interface of interfacesList; let interfaceIndex = index"
+         (click)="expandCollapseInterfaceGroup(interface)">
+        <div class="interface-title first-level hand" [attr.data-tests-id]="interface.displayName" [ngClass]="{'expanded': interface.isExpanded}">
+            <span class="sprite-new expand-collapse-plus-icon expand-collapse-icon"></span>
+            <div class="title-text" tooltips tooltip="{{interface.displayName}}">{{interface.displayName}}</div>
+        </div>
+        <div class="i-sdc-designer-sidebar-section-content-item operations-group" *ngIf="interface.isExpanded">
+            <div class="operation-data"
+             [ngClass]="{'hand': !readonly}"
+             *ngFor="let serviceOperation of interface.operationsList; let opIndex = index"
+             (click)="onSelectOperation($event, interface, opIndex)">
+                <div class="operation-name"
+                     [attr.data-tests-id]="interface.displayName + '.' + serviceOperation.operation.name"
+                     [ngClass]="{'readonly': readonly}"
+                     tooltips tooltip="{{serviceOperation.operation.name}}">
+                    {{serviceOperation.operation.name}}
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ 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 (file)
index 0000000..5830c06
--- /dev/null
@@ -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 (file)
index 0000000..b0f6dfb
--- /dev/null
@@ -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<string>;
+    associatedInterfaces: Array<any>;
+    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<ConsumptionInputDetails>;
+
+    constructor(input?: any) {
+        if (input) {
+            this.operation = new OperationModel(input.operation || {});
+            this.consumptionInputs = input.consumptionInputs || [];
+        }
+    }
+}
+
+export class InterfaceWithServiceOperation {
+    interfaceId: string;
+    displayName: string;
+    operationsList: Array<ServiceOperation>;
+    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<ModalComponent>;
+    isLoading: boolean = false;
+    interfacesList: Array<InterfaceWithServiceOperation>;
+    operationsGroup: Array<ServiceOperation>;
+    @Input() parentServiceInputs: Array<InputBEModel> = [];
+    @Input() parentService: Service;
+    @Input() selectedService: Service;
+    @Input() selectedServiceInstanceId: string;
+    @Input() instancesMappedList: Array<ServiceInstanceObject>;
+    @Input() readonly: boolean;
+
+    selectedInstanceSiblings: Array<ServiceInstanceObject>;
+    selectedInstancePropertiesList: Array<PropertyBEModel> = [];
+
+    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<ConsumptionInput>}> = _.map(this.operationsGroup, (serviceOp) => {
+            let consumptionInputsArr: Array<any> = [];
+            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 (file)
index 0000000..8593bef
--- /dev/null
@@ -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 (file)
index 0000000..5d42fa7
--- /dev/null
@@ -0,0 +1,98 @@
+
+<div class="service-consumption-editor">
+    <form class="w-sdc-form">
+
+        <div class="sdc-modal-top-bar">
+            <div class="operation-name">{{serviceOperation.operation.name}}</div>
+
+            <div class="sdc-modal-top-bar-buttons">
+                <span (click)="onChangePage(currentIndex - 1)" [ngClass]="{'disabled' : currentIndex === 0 || !checkFormValidForNavigation()}" class="sprite-new left-arrow" data-tests-id="get-prev" tooltip="Previous"></span>
+                <span (click)="onChangePage(currentIndex + 1)" [ngClass]="{'disabled' : currentIndex === serviceOperationsList.length - 1 || !checkFormValidForNavigation()}" class="sprite-new right-arrow" data-tests-id="get-next" tooltip="Next"></span>
+            </div>
+        </div>
+        <div class="expand-collapse-all" *ngIf="serviceOperation.consumptionInputs.length">
+            <div class="expand-all" (click)="onExpandAll()" [ngClass]="{'disabled': isAllInputExpanded()}"> {{'CONSUMPTION_EXPAND_ALL' | translate}}</div>
+            <div class="separator-line"></div>
+            <div class="collapse-all" (click)="onCollapseAll()" [ngClass]="{'disabled': isAllInputCollapsed()}"> {{'CONSUMPTION_COLLAPSE_ALL' | translate}}</div>
+        </div>
+
+
+        <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+
+        <div class="i-sdc-form-content-operation-inputs-content" [ngClass]="{'no-inputs': !serviceOperation.consumptionInputs.length}">
+            <div class="no-inputs-text" *ngIf="!serviceOperation.consumptionInputs.length">{{'CONSUMPTION_NO_INPUTS_TO_SHOW' | translate}}</div>
+            <div class="i-sdc-form-content-operation-input-box" *ngFor="let consumptionInput of serviceOperation.consumptionInputs">
+                <div class="i-sdc-form-content-operation-input-box-title" (click)="onExpandCollapse(consumptionInput)">
+                    <div class="expand-collapse-icon" [ngClass]="{'expanded': consumptionInput.expanded}"></div>
+                    <div class="operation-input-name">{{consumptionInput.name}}</div>
+                    <div class="operation-input-type">
+                        <span class="type-text"> | {{ 'CONSUMPTION_TYPE' | translate}}:</span>
+                        <span class="type-val"> {{consumptionInput.type}} </span>
+                    </div>
+                </div>
+
+                <div class="operation-input-section-row with-top-border" *ngIf="consumptionInput.expanded">
+                    <div class="i-sdc-form-item operation-input-section-col">
+                        <label class="i-sdc-form-label" [ngClass]="{'required': consumptionInput.required}" >{{ 'CONSUMPTION_SOURCE' | translate}}</label>
+                        <ui-element-dropdown
+                                class="i-sdc-form-select"
+                                data-tests-id="sourceType"
+                                [values]="sourceTypes"
+                                [(value)]="consumptionInput.source"
+                                (change)="onSourceChanged(consumptionInput)">
+                        </ui-element-dropdown>
+                    </div>
+
+                    <div class="operation-input-section-col assigned-value">
+                        <label class="i-sdc-form-label" *ngIf="consumptionInput.source !== SOURCE_TYPES.STATIC || consumptionInput.isSimpleType">
+                            {{consumptionInput.assignValueLabel}}
+                        </label>
+                        <dynamic-element
+                                class="dynamic-input-field"
+                                *ngIf="consumptionInput.isSimpleType && (consumptionInput.source === SOURCE_TYPES.STATIC ||  consumptionInput.source === '')"
+                                data-tests-id="inputValue"
+                                [(value)]="consumptionInput.value"
+                                (elementChanged)="onChange($event.value, $event.isValid, consumptionInput)"
+                                [type]="consumptionInput.type">
+                        </dynamic-element>
+                        <select
+                            class="i-sdc-form-select"
+                            *ngIf="consumptionInput.source !== SOURCE_TYPES.STATIC"
+                            [attr.data-tests-id]="inputValue"
+                            (change)="onChange(value, true, consumptionInput)"
+                            [(ngModel)]="consumptionInput.value"
+                            [ngModelOptions]="{standalone: true}">
+                            <option
+                                *ngFor="let propName of consumptionInput.associatedProps"
+                                [ngValue]="propName">
+                                {{propName}}
+                            </option>
+                            <optgroup
+                                *ngFor="let interfaceOperation of consumptionInput.associatedInterfaces"
+                                label="{{interfaceOperation.label}}">
+                                <option
+                                    *ngFor="let output of interfaceOperation.outputs"
+                                    [ngValue]="interfaceOperation.name + '.' + output.name">
+                                    {{output.name}}
+                                </option>
+                            </optgroup>
+                        </select>
+                    </div>
+                </div>
+                <div class="operation-input-complex-type-section" *ngIf="consumptionInput.expanded && consumptionInput.source === SOURCE_TYPES.STATIC && !consumptionInput.isSimpleType">
+                    <div class="separator"></div>
+                    <label class="static-values-title-for-complex-type">{{ 'CONSUMPTION_STATIC_VALUES' | translate}}</label>
+                    <div class="dynamic-property" *ngFor="let property of inputFePropertiesMap[consumptionInput.name]">
+                        <dynamic-property
+                            [property]="property"
+                            [readonly]="false"
+                            [expandedChildId]="property.expandedChildPropertyId"
+                            (propertyChanged)="onComplexPropertyChanged(property, consumptionInput)"
+                            (expandChild)="property.updateExpandedChildPropertyId($event)">
+                        </dynamic-property>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
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 (file)
index 0000000..83481c1
--- /dev/null
@@ -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 (file)
index 0000000..25f4126
--- /dev/null
@@ -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<ServiceOperation>,
+        parentService: Service,
+        selectedService: Service,
+        parentServiceInputs: Array<InputBEModel>,
+        selectedServiceProperties: Array<PropertyBEModel>,
+        selectedServiceInstanceId: String,
+        selectedInstanceSiblings: Array<ServiceInstanceObject>
+    };
+    sourceTypes: Array<any> = [];
+    serviceOperationsList: Array<ServiceOperation>;
+    serviceOperation: ServiceOperation;
+    currentIndex: number;
+    isLoading: boolean = false;
+    parentService: Service;
+    selectedService: Service;
+    selectedServiceInstanceId: String;
+    parentServiceInputs: Array<InputBEModel>;
+    selectedServiceProperties: Array<PropertyBEModel>;
+    changedData: Array<ConsumptionInputDetails> = [];
+    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<ConsumptionInput>) => {
+            this.isLoading = false;
+            this.serviceOperation.consumptionInputs = this.analyzeCurrentConsumptionInputs(result);
+            this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs);
+        }, err=> {
+            this.isLoading = false;
+        });
+    }
+
+    private analyzeCurrentConsumptionInputs(result: Array<any>): Array<ConsumptionInputDetails> {
+        let inputsResult: Array<ConsumptionInputDetails> = [];
+        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<ConsumptionInputDetails> = 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<ConsumptionInput>) {
+        _.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 (file)
index 0000000..e37cd76
--- /dev/null
@@ -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
index 46dfe01..a25fb75 100644 (file)
@@ -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', {})
     }
 
index 15e624b..406ac77 100644 (file)
@@ -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<ComponentGenericResponse> {
+        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<any> {
+        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<ConsumptionInput>}>): Observable<any> {
+        return this.http.post(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId, consumptionInputsList);
+    }
+
     checkComponentInstanceVersionChange(service: Service, newVersionId: string):Observable<Array<string>> {
         let instanceId = service.selectedInstance.uniqueId;
         let queries = {componentInstanceId: instanceId, newComponentInstanceId: newVersionId};
index 37ccf38..d297ea0 100644 (file)
  * 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<ComponentGenericR
     public artifacts:ArtifactGroupModel;
     public toscaArtifacts:ArtifactGroupModel;
     public componentInstancesProperties:PropertiesGroup;
+    public componentInstancesInputs:InputsGroup;
     public componentInstancesAttributes:AttributesGroup;
     public componentInstancesRelations:Array<RelationshipModel>;
     public componentInstances:Array<ComponentInstance>;
+    public componentInstancesInterfaces:Map<string,Array<InterfaceModel>>;
     public inputs:Array<InputBEModel>;
     public capabilities:CapabilitiesGroup;
     public requirements:RequirementsGroup;
@@ -59,6 +60,9 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
         if(response.componentInstancesProperties) {
             this.componentInstancesProperties = new PropertiesGroup(response.componentInstancesProperties);
         }
+        if(response.componentInstancesInputs) {
+            this.componentInstancesInputs = response.componentInstancesInputs;
+        }
         if(response.componentInstancesAttributes) {
             this.componentInstancesAttributes = new AttributesGroup(response.componentInstancesAttributes);
         }
@@ -96,6 +100,12 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
             this.interfaces = CommonUtils.initInterfaces(response.interfaces);
             this.interfaceOperations = CommonUtils.initInterfaceOperations(response.interfaces);
         }
+        if(response.componentInstancesInterfaces) {
+            this.componentInstancesInterfaces = new Map();
+            for (let resourceId in response.componentInstancesInterfaces) {
+                this.componentInstancesInterfaces[resourceId] = CommonUtils.initInterfaces(response.componentInstancesInterfaces[resourceId]);
+            }
+        }
         if(response.metadata) {
             this.metadata = new ComponentMetadata().deserialize(response.metadata);
         }
index c5259f0..99f7c49 100644 (file)
@@ -19,9 +19,9 @@
  */
 
 import * as _ from "lodash";
-import { ComponentInstanceFactory } from "./component-instance-factory";
-import { Module, AttributeModel, ResourceInstance, PropertyModel, InputFEModel, InterfaceModel, OperationModel } from "../models";
-import { InputBEModel, PropertyBEModel, RelationshipModel } from "app/models";
+import {Module, AttributeModel, ResourceInstance, PropertyModel, InterfaceModel, OperationModel} from "../models";
+import {ComponentInstanceFactory} from "./component-instance-factory";
+import {InputBEModel, PropertyBEModel, RelationshipModel} from "app/models";
 import { PolicyInstance } from "app/models/graph/zones/policy-instance";
 import { GroupInstance } from "../models/graph/zones/group-instance";
 
index 104b5dc..d96bf6f 100644 (file)
@@ -336,6 +336,7 @@ export class DEPENDENCY_EVENTS {
 
 export class COMPONENT_FIELDS {
     static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties";
+    static COMPONENT_INSTANCES_INPUTS = "componentInstancesInputs";
     static COMPONENT_INSTANCES_ATTRIBUTES = "componentInstancesAttributes";
     static COMPONENT_ATTRIBUTES = "attributes";
     static COMPONENT_INSTANCES = "componentInstances";
@@ -351,6 +352,7 @@ export class COMPONENT_FIELDS {
     static COMPONENT_POLICIES = "policies";
     static COMPONENT_GROUPS = "groups";
     static COMPONENT_INTERFACE_OPERATIONS = "interfaces";
+    static COMPONENT_INSTANCES_INTERFACES = "componentInstancesInterfaces";
     static COMPONENT_NON_EXCLUDED_GROUPS = "nonExcludedGroups";
     static COMPONENT_NON_EXCLUDED_POLICIES = "nonExcludedPolicies";
 }
index 5559e53..c2d6007 100644 (file)
                         data-ng-class="{'disabled': disabledTabs}">
                     <div class="i-sdc-designer-sidebar-tab-icon sprite-new api"></div>
                 </button>
+                <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+                        data-ui-sref="workspace.composition.consumption"
+                        tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Operation Consumption"
+                        data-tests-id="service-consumption-tab"
+                        data-ng-if="(isComponentInstanceSelected() && currentComponent.selectedInstance.isServiceProxy())"
+                        data-ng-class="{'disabled': disabledTabs}">
+                    <div class="i-sdc-designer-sidebar-tab-icon sprite-new import-icon"></div>
+                </button>
                 <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
                         data-ui-sref="workspace.composition.dependencies"
                         tooltips tooltip-class="tooltip-custom tab-tooltip " tooltip-content="Service Dependencies"
index b9bb66c..f37a492 100644 (file)
 
     .i-sdc-designer-sidebar-tab-icon {
         margin-top: 5px ;
+        &.import-icon {
+            transform: rotate(270deg);
+        }
         //  opacity: .4;
     }
 
         .g_1;
         line-height: 18px;
     }
-    
+
     .service-path-buttons {
        margin-top: 12px;
        position: absolute;
                     border-radius: 2px;
                     border:solid 1px #fff;
                 }
-            } 
+            }
         }
 
         .zoom-icons {
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts
new file mode 100644 (file)
index 0000000..20e99b0
--- /dev/null
@@ -0,0 +1,79 @@
+/*!
+* 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 {ICompositionViewModelScope} from "../../composition-view-model";
+import {Service, PropertiesGroup, InputsGroup, ServiceInstanceObject, InterfaceModel, InputBEModel} from 'app/models';
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+
+interface IServiceConsumptionViewModelScope extends ICompositionViewModelScope {
+    service: Service;
+    instancesMappedList: Array<ServiceInstanceObject>;
+    componentInstancesProperties: PropertiesGroup;
+    componentInstancesInputs: InputsGroup;
+    componentInstancesInterfaces: Map<string, Array<InterfaceModel>>;
+    componentInputs: Array<InputBEModel>;
+}
+
+
+export class ServiceConsumptionViewModel {
+
+    static '$inject' = [
+        '$scope',
+        'ServiceServiceNg2'
+    ];
+
+    constructor(private $scope:IServiceConsumptionViewModelScope, private ServiceServiceNg2:ServiceServiceNg2) {
+        this.$scope.service = <Service>this.$scope.currentComponent;
+        this.initInstances();
+        this.initScope();
+    }
+
+    private initInstances = ():void => {
+        this.ServiceServiceNg2.getServiceConsumptionData(this.$scope.service).subscribe((genericResponse:ComponentGenericResponse) => {
+            this.$scope.componentInstancesProperties = genericResponse.componentInstancesProperties;
+            this.$scope.componentInstancesInputs = genericResponse.componentInstancesInputs;
+            this.$scope.componentInstancesInterfaces = genericResponse.componentInstancesInterfaces;
+            this.$scope.componentInputs = genericResponse.inputs;
+            this.updateInstanceAttributes();
+        });
+    }
+
+    private updateInstanceAttributes = ():void => {
+        if (this.$scope.isComponentInstanceSelected() && this.$scope.componentInstancesProperties) {
+            this.$scope.instancesMappedList = this.$scope.service.componentInstances.map(coInstance => new ServiceInstanceObject({
+                id: coInstance.uniqueId,
+                name: coInstance.name,
+                properties: this.$scope.componentInstancesProperties[coInstance.uniqueId] || [],
+                inputs: this.$scope.componentInstancesInputs[coInstance.uniqueId] || [],
+                interfaces: this.$scope.componentInstancesInterfaces[coInstance.uniqueId] || []
+            }));
+        }
+    }
+
+    private initScope = ():void => {
+        this.$scope.$watch('currentComponent.selectedInstance', ():void => {
+            this.updateInstanceAttributes();
+        });
+
+        this.$scope.registerCreateInstanceEvent(() => {
+            this.initInstances();
+        });
+
+        this.$scope.$on('$destroy', this.$scope.unregisterCreateInstanceEvent);
+    }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html
new file mode 100644 (file)
index 0000000..835ded3
--- /dev/null
@@ -0,0 +1,22 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content service-consumption">
+    <div class="w-sdc-designer-sidebar-section">
+        <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content"
+                         class="w-sdc-designer-sidebar-section-title">
+            <span class="w-sdc-designer-sidebar-section-title-text" tooltips tooltip-content="Operation Consumption">Operation Consumption</span>
+            <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+        </expand-collapse>
+
+        <div class="w-sdc-designer-sidebar-section-content">
+            <div class="i-sdc-designer-sidebar-section-content-item">
+                <ng2-service-consumption
+                        [parent-service]="service"
+                        [selected-service]="selectedComponent"
+                        [selected-service-instance-id]="currentComponent.selectedInstance.uniqueId"
+                        [instances-mapped-list]="instancesMappedList"
+                        [parent-service-inputs]="componentInputs"
+                        [readonly]="isViewMode() || !isDesigner()">
+                </ng2-service-consumption>
+            </div>
+        </div>
+    </div>
+</perfect-scrollbar>
\ No newline at end of file