Add 'Service Dependencies' tab in composition page 55/78455/1
authormiriame <miriam.eini@amdocs.com>
Wed, 13 Feb 2019 13:17:26 +0000 (15:17 +0200)
committermiriame <miriam.eini@amdocs.com>
Thu, 14 Feb 2019 08:45:13 +0000 (10:45 +0200)
 Issue-ID: SDC-1987

Change-Id: Ib5b688c12234c81fe6f89b2b5d37dd16a75b0db9
Signed-off-by: miriame <miriam.eini@amdocs.com>
37 files changed:
catalog-ui/src/app/app.ts
catalog-ui/src/app/directives/graphs-v2/common/style/component-instances-nodes-style.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/componentsInstances/componentInstance.ts
catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts
catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-service-proxy.ts
catalog-ui/src/app/models/properties-inputs/property-be-model.ts
catalog-ui/src/app/models/service-instance-properties.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/service-dependencies/service-dependencies.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.component.ts
catalog-ui/src/app/ng2/components/ui/form-components/checkbox/checkbox.component.less
catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-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/responses/component-generic-response.ts
catalog-ui/src/app/utils/constants.ts
catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts
catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view-model.ts [new file with mode: 0644]
catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view.html [new file with mode: 0644]
catalog-ui/src/assets/languages/en_US.json
catalog-ui/src/assets/styles/images/service-proxy-icons/dependent.png [new file with mode: 0644]
catalog-ui/src/assets/styles/images/service-proxy-icons/uncertified_dependent.png [new file with mode: 0644]
catalog-ui/src/assets/styles/mixins.less
catalog-ui/src/assets/styles/mixins_old.less
catalog-ui/src/assets/styles/sprite.less

index 2f7b781..8fa7f1e 100644 (file)
@@ -517,6 +517,14 @@ ng1appModule.config([
                 controller: viewModelsModuleName + '.ResourceArtifactsViewModel'
             }
         );
+        $stateProvider.state(
+            'workspace.composition.dependencies', {
+                url: 'dependencies',
+                parent: 'workspace.composition',
+                templateUrl: './view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view.html',
+                controller: viewModelsModuleName + '.ServiceDependenciesViewModel'
+            }
+        );
 
         $stateProvider.state(
             States.WORKSPACE_INTERFACE_OPERATION, {
index 194845c..dd50524 100644 (file)
@@ -237,7 +237,27 @@ export class ComponentInstanceNodesStyle {
                 css: {
                     'shape': 'rectangle',
                     'background-image': (ele:Cy.Collection) => {
-                        return ele.data().setUncertifiedImageBgStyle(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE);//Change name to setUncertifiedImageBgStyle??
+                        return ele.data().initUncertifiedImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE);
+                    },
+                    "border-width": 0
+                }
+            },
+            {
+                selector: '.dependent',
+                css: {
+                    'shape': 'rectangle',
+                    'background-image': (ele:Cy.Collection) => {
+                        return ele.data().initDependentImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE)
+                    },
+                    "border-width": 0
+                }
+            },
+            {
+                selector: '.dependent.not-certified',
+                css: {
+                    'shape': 'rectangle',
+                    'background-image': (ele:Cy.Collection) => {
+                        return ele.data().initUncertifiedDependentImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE)
                     },
                     "border-width": 0
                 }
index 804e772..502188b 100644 (file)
@@ -35,7 +35,7 @@ import {
     NodesFactory,
     Point
 } from "app/models";
-import { ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors } from "app/utils";
+import { ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors, DEPENDENCY_EVENTS } from "app/utils";
 import { EventListenerService, LoaderService } from "app/services";
 import { CompositionGraphLinkUtils } from "./utils/composition-graph-links-utils";
 import { CompositionGraphGeneralUtils } from "./utils/composition-graph-general-utils";
@@ -185,6 +185,7 @@ export class CompositionGraph implements ng.IDirective {
             });
             this.eventListenerService.unRegisterObserver(EVENTS.SHOW_LOADER_EVENT + 'composition-graph');
             this.eventListenerService.unRegisterObserver(EVENTS.HIDE_LOADER_EVENT + 'composition-graph');
+            this.eventListenerService.unRegisterObserver(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE);
         });
 
     };
@@ -383,6 +384,15 @@ export class CompositionGraph implements ng.IDirective {
             this.loadGraphData(scope);
         });
 
+        this.eventListenerService.registerObserverCallback(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE, (ischecked: boolean) => {
+            if (ischecked) {
+                this._cy.$('node:selected').addClass('dependent');
+            } else {
+                // due to defect in cytoscape, just changing the class does not replace the icon, and i need to revert to original icon with no markings.
+                this._cy.$('node:selected').removeClass('dependent');
+                this._cy.$('node:selected').style({'background-image': this._cy.$('node:selected').data('originalImg')});
+            }
+        });
 
         scope.zoom = (zoomIn: boolean): void => {
             let currentZoom: number = this._cy.zoom();
index e5cc483..3a31caf 100644 (file)
@@ -114,4 +114,5 @@ 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'
 
index fcc3298..f95d655 100644 (file)
@@ -61,6 +61,11 @@ export class ComponentInstance {
     public groupInstances:Array<Module>;
     public invariantName:string;
     public originArchived:boolean;
+    public directives: Array<string>;
+
+    DIRECTIVES_TYPES = {
+        SELECTABLE: 'selectable'
+    };
 
     constructor(componentInstance?:ComponentInstance) {
 
@@ -92,6 +97,7 @@ export class ComponentInstance {
             this.sourceModelUid = componentInstance.sourceModelUid;
             this.sourceModelUuid = componentInstance.sourceModelUuid;
             this.originArchived = componentInstance.originArchived;
+            this.directives = componentInstance.directives;
         }
     }
 
@@ -177,4 +183,19 @@ export class ComponentInstance {
     public get iconClass() {
         return this.iconSprite + ' ' + this.icon;
     }
+
+    public isDependent = () : boolean => {
+        return this.directives && this.directives.indexOf(this.DIRECTIVES_TYPES.SELECTABLE) !== -1;
+    }
+
+    public markAsDependent = () : void => {
+        this.directives.push(this.DIRECTIVES_TYPES.SELECTABLE);
+    }
+
+    public unmarkAsDependent = () : void => {
+        let index = this.directives.indexOf(this.DIRECTIVES_TYPES.SELECTABLE);
+        if(index >= 0) {
+            this.directives.splice(index, 1);
+        }
+    }
 }
index a241423..51a0c7b 100644 (file)
@@ -48,33 +48,31 @@ export abstract class CompositionCiNodeBase extends CommonCINodeBase implements
         this.isUcpePart = false;
         this.isInsideGroup = false;
     }
-    
-    
-    public setUncertifiedImageBgStyle(node:Cy.Collection, nodeMinSize:number):string {
 
-        let uncertifiedIconWidth:number = GraphUIObjects.HANDLE_SIZE;
+
+    protected enhanceImage(node:Cy.Collection, nodeMinSize:number, imgUrl: string):string {
+        let infoIconWidth:number = GraphUIObjects.HANDLE_SIZE;
         let nodeWidth:number = node.data('imgWidth') || node.width();
-        let uncertifiedCanvasWidth: number = nodeWidth;
+        let infoCanvasWidth: number = nodeWidth;
 
-        if (nodeWidth < nodeMinSize) { //uncertified icon will overlap too much of the node, need to expand canvas.
-            uncertifiedCanvasWidth = nodeWidth + uncertifiedIconWidth/2; //expand canvas so that only half of the icon overlaps with the node
+        if (nodeWidth < nodeMinSize) { //info icon will overlap too much of the node, need to expand canvas.
+            infoCanvasWidth = nodeWidth + infoIconWidth/2; //expand canvas so that only half of the icon overlaps with the node
         }
 
-        const x = uncertifiedCanvasWidth - nodeWidth, y = x, width = nodeWidth, height = width;
+        const x = infoCanvasWidth - nodeWidth, y = x, width = nodeWidth, height = width;
 
         const canvasImages:ICanvasImage[] = [
             { src: this.imagesPath + this.componentInstance.icon + '.png', x, y, width, height},
-            { src: this.imagesPath + 'uncertified.png', x: 0, y: 0, width: uncertifiedIconWidth, height: uncertifiedIconWidth}
+            { src: imgUrl, x: 0, y: 0, width: infoIconWidth, height: infoIconWidth}
         ];
 
-
        //Create the image and update the node background styles
-        this.imageCreator.getMultiLayerBase64Image(canvasImages, uncertifiedCanvasWidth, uncertifiedCanvasWidth).then(img => this.updateNodeStyles(node,uncertifiedCanvasWidth,img));
+        this.imageCreator.getMultiLayerBase64Image(canvasImages, infoCanvasWidth, infoCanvasWidth).then(img => this.updateNodeStyles(node,infoCanvasWidth,img));
         return this.img; // Return the referance to the image (in Base64 format)
     }
 
-    
-    public setArchivedImageBgStyle(node:Cy.Collection, nodeMinSize:number):string {        
+
+    public setArchivedImageBgStyle(node:Cy.Collection, nodeMinSize:number):string {
         let archivedIconWidth:number = GraphUIObjects.HANDLE_SIZE;
         let nodeWidth:number = node.data('imgWidth') || node.width();
         let archivedCanvasWidth: number = nodeWidth;
@@ -92,6 +90,10 @@ export abstract class CompositionCiNodeBase extends CommonCINodeBase implements
         return this.img; // Return the default img
     }
 
+    public initUncertifiedImage(node:Cy.Collection, nodeMinSize:number):string {
+        return this.enhanceImage(node, nodeMinSize, this.imagesPath + 'uncertified.png');
+    }
+
     protected getDisplayName():string {
         let graphResourceName = AngularJSBridge.getFilter('graphResourceName');
         let resourceName = AngularJSBridge.getFilter('resourceName');
@@ -99,7 +101,7 @@ export abstract class CompositionCiNodeBase extends CommonCINodeBase implements
     }
 
     //TODO:: move to Base class ???
-    private updateNodeStyles(node,canvasWidth,imageBase64){     
+    private updateNodeStyles(node,canvasWidth,imageBase64){
         this.img = imageBase64;
         node.style({
             'background-image': this.img,
@@ -107,7 +109,7 @@ export abstract class CompositionCiNodeBase extends CommonCINodeBase implements
             'background-height': canvasWidth,
             'background-position-x':0,
             'background-position-y':0
-        });        
+        });
     }
 
 }
index b025221..5ef3a73 100644 (file)
  * ============LICENSE_END=========================================================
  */
 
-import { ImagesUrl, GraphUIObjects} from "../../../../utils/constants";
-import {ComponentInstance, CompositionCiNodeBase} from "../../../../models";
-import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service";
+import {ComponentInstance, CompositionCiNodeBase} from "app/models";
+import {ImageCreatorService} from "app/directives/graphs-v2/image-creator/image-creator.service";
+import {ImagesUrl, GraphUIObjects} from "app/utils";
 export class CompositionCiNodeServiceProxy extends CompositionCiNodeBase {
+    private isDependent: boolean;
+    private originalImg: string;
 
     constructor(instance:ComponentInstance,
                 imageCreator:ImageCreatorService) {
         super(instance, imageCreator);
+        this.isDependent =instance.isDependent();
         this.initService();
     }
 
     private initService():void {
         this.imagesPath = this.imagesPath + ImagesUrl.SERVICE_PROXY_ICONS;
         this.img = this.imagesPath + this.componentInstance.icon + '.png';
+        this.originalImg = this.img;
         this.imgWidth = GraphUIObjects.DEFAULT_RESOURCE_WIDTH;
         this.classes = 'service-node';
         if(this.archived){
             this.classes = this.classes + ' archived';
             return;
         }
+        if (this.isDependent) {
+            this.classes += ' dependent';
+        }
         if (!this.certified) {
             this.classes = this.classes + ' not-certified';
         }
 
     }
+
+    public initUncertifiedDependentImage(node:Cy.Collection, nodeMinSize:number):string {
+        return this.enhanceImage(node, nodeMinSize, this.imagesPath + 'uncertified_dependent.png');
+    }
+
+    public initDependentImage(node:Cy.Collection, nodeMinSize:number):string {
+        return this.enhanceImage(node, nodeMinSize, this.imagesPath + 'dependent.png');
+    }
+
 }
index 14b6385..aa68551 100644 (file)
@@ -39,6 +39,7 @@ export class PropertyBEModel {
     password: boolean;
     required: boolean;
     schema: SchemaPropertyGroupModel;
+    schemaType: string;
     type: string;
     uniqueId: string;
     value: string;
@@ -53,6 +54,7 @@ export class PropertyBEModel {
             this.password = property.password;
             this.required = property.required;
             this.schema = property.schema;
+            this.schemaType = property.schemaType;
             this.type = property.type;
             this.uniqueId = property.uniqueId;
             this.value = property.value;
diff --git a/catalog-ui/src/app/models/service-instance-properties.ts b/catalog-ui/src/app/models/service-instance-properties.ts
new file mode 100644 (file)
index 0000000..9e9f1cc
--- /dev/null
@@ -0,0 +1,31 @@
+/*!
+ * 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} from 'app/models';
+
+export class ServiceInstanceObject {
+    id: string;
+    name: string;
+    properties: Array<PropertyModel> = [];
+
+    constructor(input?:any) {
+        if(input) {
+            this.id = input.id;
+            this.name = input.name;
+            this.properties = input.properties;
+        }
+    }
+}
\ No newline at end of file
index 683a334..c541d58 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 { 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';
 import { PluginFrameComponent } from "../ng2/components/ui/plugin/plugin-frame.component";
@@ -245,6 +246,12 @@ directiveModule.directive('ng2ServicePathSelector', downgradeComponent({
     outputs: []
 }) as angular.IDirectiveFactory);
 
+directiveModule.directive('ng2ServiceDependencies', downgradeComponent({
+    component: ServiceDependenciesComponent,
+    inputs: ['compositeService', 'currentServiceInstance', 'selectedInstanceProperties', 'selectedInstanceSiblings', 'selectedInstanceConstraints', 'readonly'],
+    outputs: ['updateRulesListEvent', 'loadRulesListEvent','dependencyStatus']
+}) as angular.IDirectiveFactory);
+
 directiveModule.directive('interfaceOperation', downgradeComponent({
     component: InterfaceOperationComponent,
     inputs: ['component', 'readonly'],
index c1929e0..a390ab4 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 {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";
 import {ModulePropertyView} from "../view-models/forms/property-forms/module-property-modal/module-property-model";
@@ -82,6 +83,7 @@ viewModelModule
 
   .controller(moduleName + '.DetailsViewModel', DetailsViewModel)
   .controller(moduleName + '.ResourceArtifactsViewModel', ResourceArtifactsViewModel)
+  .controller(moduleName + '.ServiceDependenciesViewModel', ServiceDependenciesViewModel)
   .controller(moduleName + '.PropertyFormBaseView', PropertyFormBaseView)
   .controller(moduleName + '.PropertyFormViewModel', PropertyFormViewModel)
   .controller(moduleName + '.ModulePropertyView', ModulePropertyView)
index 750563b..1ae2df2 100644 (file)
@@ -60,6 +60,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 {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';
 import { WindowRef } from "./services/window.service";
 import {ArchiveService} from "./services/archive.service";
@@ -110,7 +112,9 @@ export function configServiceFactory(config: ConfigService) {
         ServicePathCreatorModule,
         ServicePathsListModule,
         ServicePathModule,
-        ServicePathSelectorModule
+        ServicePathSelectorModule,
+        ServiceDependenciesModule,
+        ServiceDependenciesEditorModule
     ],
     exports: [],
     entryComponents: [
diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.html b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.html
new file mode 100644 (file)
index 0000000..d71a4fb
--- /dev/null
@@ -0,0 +1,35 @@
+<div class="service-dependencies">
+    <loader [display]="isLoading" [size]="'medium'" [relative]="true"></loader>
+    <div class="checkbox-label-mark-as-dependent">
+            <checkbox
+                    class="checkbox-label"
+                    data-tests-id="checkbox-mark-as-dependent"
+                    [label]="'Mark as Dependent'"
+                    (checkedChange)="onMarkAsDependent()"
+                    [(checked)]="isDependent"
+                    [disabled]="readonly">
+            </checkbox>
+    </div>
+    <div class="i-sdc-designer-sidebar-section-content-item-rules-section" *ngIf="isDependent">
+
+        <div class="i-sdc-designer-sidebar-section-content-item-rule" [ngClass]="{'hand': !readonly}"
+             *ngFor="let rule of rulesList; let i = index">
+            <div class="rule-details" [ngClass]="{'readonly': readonly}">
+                <div class="rule-desc" (click)="!readonly && onSelectRule(i)" tooltips tooltip="{{rule.servicePropertyName + ' ' + getSymbol(rule.constraintOperator) + ' ' + (rule.sourceName ? rule.sourceName + ':' : '') + rule.value}}">
+                    {{rule.servicePropertyName + ' ' + getSymbol(rule.constraintOperator) + ' ' + (rule.sourceName ? rule.sourceName + ':' : '') + rule.value}}
+                </div>
+                <span *ngIf="!readonly" class="sprite-new delete-btn delete-icon" (click)="openDeleteModal(i)" data-tests-id="delete-input-button"></span>
+            </div>
+        </div>
+
+        <div class="w-sdc-designer-sidebar-section-footer" >
+            <button
+                    class="w-sdc-designer-sidebar-section-footer-action add-rule-btn tlv-btn blue"
+                    data-tests-id="add-rule-button"
+                    (click)="onAddRule()"
+                    [disabled]="readonly">
+                {{'SERVICE_DEPENDENCY_ADD_RULE' | translate}}
+            </button>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less
new file mode 100644 (file)
index 0000000..ae990dc
--- /dev/null
@@ -0,0 +1,70 @@
+@import './../../../../../assets/styles/variables.less';
+@import './../../../../../assets/styles/variables-old.less';
+@import './../../../../../assets/styles/mixins_old.less';
+@import './../../../../../assets/styles/mixins.less';
+
+.service-dependencies {
+
+  /deep/ .checkbox-label-mark-as-dependent {
+    padding: 7px 18px;
+    position: relative;
+    height: 61px;
+    color: @main_color_a;
+    box-shadow: 0 2px 7px @main_color_o;
+    border-bottom: 1px solid @main_color_o;
+    .checkbox-label {
+      margin-top: 14px;
+      .checkbox-label-content {
+        font-size: 14px;
+      }
+    }
+    .checkbox-container input[type=checkbox].checkbox-hidden[disabled] ~ .checkbox-label-content {
+      opacity: 0.5;
+    }
+
+    loader {
+      top: 20px;
+    }
+  }
+
+  .i-sdc-designer-sidebar-section-content-item-rules-section {
+    .i-sdc-designer-sidebar-section-content-item-rule {
+      border-bottom: 1px solid @main_color_o;
+      padding: 5px 10px 5px 18px;
+      position: relative;
+      height: 61px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .rule-details {
+        .s_1;
+        display: flex;
+        flex: 1;
+        align-items: center;
+        justify-content: space-between;
+        margin-left: 5px;
+        width: 180px;
+        .delete-icon {
+          visibility: hidden;
+        }
+        &:not(.readonly):hover {
+          .a_1;
+        }
+        &:hover .delete-icon{
+          visibility: visible;
+        }
+        &.readonly {
+          opacity: 0.5;
+        }
+      }
+      .rule-desc {
+        .sdc-ellipsis;
+        width: 220px;
+        position: relative;
+        padding-right: 1em;
+      }
+
+    }
+  }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts
new file mode 100644 (file)
index 0000000..ac87576
--- /dev/null
@@ -0,0 +1,325 @@
+/*!
+ * 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, Output, EventEmitter, ComponentRef} from '@angular/core';
+import {ModalService} from 'app/ng2/services/modal.service';
+import {Service, ComponentInstance, ModalModel, ButtonModel, PropertyBEModel, ServiceInstanceObject} from 'app/models';
+import {ServiceDependenciesEditorComponent} from 'app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component';
+import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
+import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service';
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+
+export class ConstraintObject {
+    servicePropertyName: string;
+    constraintOperator: string;
+    sourceType: string;
+    sourceName: string;
+    value: string;
+
+    constructor(input?: any) {
+        if (input) {
+            this.servicePropertyName = input.servicePropertyName;
+            this.constraintOperator = input.constraintOperator;
+            this.sourceType = input.sourceType;
+            this.sourceName = input.sourceName;
+            this.value = input.value;
+        }
+    }
+}
+
+export class ConstraintObjectUI extends ConstraintObject{
+    isValidValue: boolean;
+
+    constructor(input?: any) {
+        super(input);
+        if(input) {
+            this.isValidValue = input.isValidValue ? input.isValidValue : input.value !== '';
+        }
+    }
+
+    public updateValidity(isValidValue: boolean) {
+        this.isValidValue = isValidValue;
+    }
+
+    public isValidRule(isStatic) {
+        let isValidValue = isStatic ? this.isValidValue : true;
+        return this.servicePropertyName != null && this.servicePropertyName !== ''
+            && this.value != null && this.value !== '' && isValidValue;
+    }
+}
+
+export const OPERATOR_TYPES = {
+    EQUAL: 'equal',
+    GREATER_THAN: 'greater_than',
+    LESS_THAN: 'less_than'
+};
+
+class I18nTexts {
+    static uncheckModalTitle: string;
+    static uncheckModalText: string;
+    static modalApprove: string;
+    static modalCancel: string;
+    static modalCreate: string;
+    static modalSave: string;
+    static modalDelete: string;
+    static addRuleTxt: string;
+    static updateRuleTxt: string;
+    static deleteRuleTxt: string;
+    static deleteRuleMsg: string;
+
+    public static translateTexts(translateService) {
+            I18nTexts.uncheckModalTitle = translateService.translate("SERVICE_DEPENDENCY_UNCHECK_TITLE");
+            I18nTexts.uncheckModalText = translateService.translate("SERVICE_DEPENDENCY_UNCHECK_TEXT");
+            I18nTexts.modalApprove = translateService.translate("MODAL_APPROVE");
+            I18nTexts.modalCancel = translateService.translate("MODAL_CANCEL");
+            I18nTexts.modalCreate = translateService.translate("MODAL_CREATE");
+            I18nTexts.modalSave = translateService.translate("MODAL_SAVE");
+            I18nTexts.modalDelete = translateService.translate("MODAL_DELETE");
+            I18nTexts.addRuleTxt = translateService.translate("SERVICE_DEPENDENCY_ADD_RULE");
+            I18nTexts.updateRuleTxt = translateService.translate("SERVICE_DEPENDENCY_UPDATE_RULE");
+            I18nTexts.deleteRuleTxt = translateService.translate("SERVICE_DEPENDENCY_DELETE_RULE");
+            I18nTexts.deleteRuleMsg = translateService.translate("SERVICE_DEPENDENCY_DELETE_RULE_MSG");
+    }
+}
+
+
+@Component({
+    selector: 'service-dependencies',
+    templateUrl: './service-dependencies.component.html',
+    styleUrls: ['service-dependencies.component.less'],
+    providers: [ModalService, TranslateService]
+})
+
+export class ServiceDependenciesComponent {
+    modalInstance: ComponentRef<ModalComponent>;
+    isDependent: boolean;
+    isLoading: boolean;
+    compositeServiceProperties: Array<PropertyBEModel> = [];
+    rulesList: Array<ConstraintObject> = [];
+    operatorTypes: Array<any>;
+
+    @Input() readonly: boolean;
+    @Input() compositeService: Service;
+    @Input() currentServiceInstance: ComponentInstance;
+    @Input() selectedInstanceSiblings: Array<ServiceInstanceObject>;
+    @Input() selectedInstanceConstraints: Array<ConstraintObject> = [];
+    @Input() selectedInstanceProperties: Array<PropertyBEModel> = [];
+    @Output() updateRulesListEvent:EventEmitter<Array<ConstraintObject>> = new EventEmitter<Array<ConstraintObject>>();
+    @Output() loadRulesListEvent:EventEmitter<any> = new EventEmitter();
+    @Output() dependencyStatus = new EventEmitter<boolean>();
+
+
+    constructor(private componentServiceNg2: ComponentServiceNg2, private ModalServiceNg2: ModalService, private translateService: TranslateService) {
+    }
+
+    ngOnInit() {
+        this.isLoading = false;
+        this.operatorTypes = [
+            {label: ">", value: OPERATOR_TYPES.GREATER_THAN},
+            {label: "<", value: OPERATOR_TYPES.LESS_THAN},
+            {label: "=", value: OPERATOR_TYPES.EQUAL}
+        ];
+        this.componentServiceNg2.getServiceProperties(this.compositeService).subscribe((properties: Array<PropertyBEModel>) => {
+            this.compositeServiceProperties = properties;
+        });
+        this.loadRules();
+        this.translateService.languageChangedObservable.subscribe(lang => {
+            I18nTexts.translateTexts(this.translateService);
+        });
+    }
+
+    ngOnChanges(changes) {
+        if(changes.currentServiceInstance) {
+            this.currentServiceInstance = changes.currentServiceInstance.currentValue;
+            this.isDependent = this.currentServiceInstance.isDependent();
+        }
+        if(changes.selectedInstanceConstraints && changes.selectedInstanceConstraints.currentValue !== changes.selectedInstanceConstraints.previousValue) {
+            this.selectedInstanceConstraints = changes.selectedInstanceConstraints.currentValue;
+            this.loadRules();
+        }
+    }
+
+    public openRemoveDependencyModal = (): ComponentRef<ModalComponent> => {
+        let actionButton: ButtonModel = new ButtonModel(I18nTexts.modalApprove, 'blue', this.onUncheckDependency);
+        let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'grey', this.onCloseRemoveDependencyModal);
+        let modalModel: ModalModel = new ModalModel('sm', I18nTexts.uncheckModalTitle, I18nTexts.uncheckModalText, [actionButton, cancelButton]);
+        return this.ModalServiceNg2.createCustomModal(modalModel);
+    }
+
+    loadRules() {
+        this.rulesList = this.selectedInstanceConstraints && this.selectedInstanceConstraints.map((co: ConstraintObject) => ({
+                servicePropertyName: co.servicePropertyName,
+                constraintOperator: co.constraintOperator,
+                sourceType: co.sourceType,
+                sourceName: co.sourceName !== 'SELF' ? co.sourceName : this.compositeService.name,
+                value: co.value,
+            }));
+    }
+
+    onUncheckDependency = () => {
+        this.ModalServiceNg2.closeCurrentModal();
+        this.isLoading = true;
+        let isDepOrig = this.isDependent;
+        let rulesListOrig = this.rulesList;
+        this.currentServiceInstance.unmarkAsDependent();
+        this.updateComponentInstance(isDepOrig, rulesListOrig);
+    }
+
+    onCloseRemoveDependencyModal = () => {
+        this.isDependent = true;
+        this.ModalServiceNg2.closeCurrentModal();
+    }
+
+    onCheckDependency = () => {
+        let isDepOrig = this.isDependent;
+        let rulesListOrig = this.rulesList;
+        this.currentServiceInstance.markAsDependent();
+        this.rulesList = [];
+        this.updateComponentInstance(isDepOrig, rulesListOrig);
+    }
+
+    onMarkAsDependent() {
+        if(!this.currentServiceInstance.isDependent()) {
+            this.onCheckDependency();
+        }
+        else {
+            this.openRemoveDependencyModal().instance.open();
+        }
+    }
+
+    updateComponentInstance(isDependent_origVal : boolean, rulesList_orig: Array<ConstraintObject>) {
+        this.isLoading = true;
+        this.componentServiceNg2.updateComponentInstance(this.compositeService, this.currentServiceInstance).subscribe((updatedServiceIns: ComponentInstance) => {
+            this.currentServiceInstance = new ComponentInstance(updatedServiceIns);
+            this.isDependent = this.currentServiceInstance.isDependent();
+            this.dependencyStatus.emit(this.isDependent);
+            if(this.isDependent) {
+                this.loadRulesListEvent.emit();
+            }
+            this.isLoading = false;
+        }, err=> {
+            this.isDependent = isDependent_origVal;
+            this.rulesList = rulesList_orig;
+            this.isLoading = false;
+            console.log('An error has occurred.');
+        });
+    }
+
+    onAddRule () {
+        let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.ModalServiceNg2.closeCurrentModal);
+        let saveButton: ButtonModel = new ButtonModel(I18nTexts.modalCreate, 'blue', this.createRule, this.getDisabled);
+        let modalModel: ModalModel = new ModalModel('l', I18nTexts.addRuleTxt, '', [saveButton, cancelButton], 'standard');
+        this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel);
+        this.ModalServiceNg2.addDynamicContentToModal(
+            this.modalInstance,
+            ServiceDependenciesEditorComponent,
+            {
+                currentServiceName: this.currentServiceInstance.name,
+                operatorTypes: this.operatorTypes,
+                compositeServiceName: this.compositeService.name,
+                compositeServiceProperties: this.compositeServiceProperties,
+                selectedInstanceProperties: this.selectedInstanceProperties,
+                selectedInstanceSiblings: this.selectedInstanceSiblings
+            }
+        );
+        this.modalInstance.instance.open();
+    }
+
+    onSelectRule(index: number) {
+        let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.ModalServiceNg2.closeCurrentModal);
+        let saveButton: ButtonModel = new ButtonModel(I18nTexts.modalSave, 'blue', () => this.updateRules(), this.getDisabled);
+        let modalModel: ModalModel = new ModalModel('l', I18nTexts.updateRuleTxt, '', [saveButton, cancelButton], 'standard');
+        this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel);
+        this.ModalServiceNg2.addDynamicContentToModal(
+            this.modalInstance,
+            ServiceDependenciesEditorComponent,
+            {
+                serviceRuleIndex: index,
+                serviceRules: _.map(this.rulesList, rule => new ConstraintObjectUI(rule)),
+                currentServiceName: this.currentServiceInstance.name,
+                operatorTypes: this.operatorTypes,
+                compositeServiceName: this.compositeService.name,
+                compositeServiceProperties: this.compositeServiceProperties,
+                selectedInstanceProperties: this.selectedInstanceProperties,
+                selectedInstanceSiblings: this.selectedInstanceSiblings
+            }
+        );
+        this.modalInstance.instance.open();
+    }
+
+    getDisabled = ():boolean =>  {
+        return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit();
+    };
+
+    createRule  = ():void => {
+        let newRuleToCreate: ConstraintObject = new ConstraintObject(this.modalInstance.instance.dynamicContent.instance.currentRule);
+        this.isLoading = true;
+        this.componentServiceNg2.createServiceFilterConstraints(
+            this.compositeService,
+            this.currentServiceInstance,
+            newRuleToCreate
+        ).subscribe( (response) => {
+            this.updateRulesListEvent.emit(response.properties);
+            this.isLoading = false;
+        }, err=> {
+            this.isLoading = false;
+        });
+        this.ModalServiceNg2.closeCurrentModal();
+    };
+
+    updateRules = ():void => {
+        let allRulesToUpdate: Array<ConstraintObject> = this.modalInstance.instance.dynamicContent.instance.serviceRulesList.map(rule => new ConstraintObject(rule));
+        this.isLoading = true;
+        this.componentServiceNg2.updateServiceFilterConstraints(
+            this.compositeService,
+            this.currentServiceInstance,
+            allRulesToUpdate
+        ).subscribe((response) => {
+            this.updateRulesListEvent.emit(response.properties);
+            this.isLoading = false;
+        }, err => {
+            this.isLoading = false;
+        });
+        this.ModalServiceNg2.closeCurrentModal();
+    }
+
+    getSymbol(constraintOperator) {
+        switch (constraintOperator) {
+            case OPERATOR_TYPES.LESS_THAN: return '<';
+            case OPERATOR_TYPES.EQUAL: return '=';
+            case OPERATOR_TYPES.GREATER_THAN: return '>';
+        }
+    }
+
+    onDeleteRule = (index:number) => {
+        this.isLoading = true;
+        this.componentServiceNg2.deleteServiceFilterConstraints(
+            this.compositeService,
+            this.currentServiceInstance,
+            index
+        ).subscribe( (response) => {
+            this.updateRulesListEvent.emit(response.properties);
+            this.isLoading = false;
+        }, err=> {
+            this.isLoading = false;
+        });
+        this.ModalServiceNg2.closeCurrentModal();
+    };
+
+    openDeleteModal = (index:number) => {
+        this.ModalServiceNg2.createActionModal(I18nTexts.deleteRuleTxt, I18nTexts.deleteRuleMsg,
+            I18nTexts.modalDelete, () => this.onDeleteRule(index), I18nTexts.modalCancel).instance.open();
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts
new file mode 100644 (file)
index 0000000..7e66ed9
--- /dev/null
@@ -0,0 +1,24 @@
+
+import { NgModule } from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {ServiceDependenciesComponent} from "./service-dependencies.component";
+import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module";
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+
+@NgModule({
+    declarations: [
+        ServiceDependenciesComponent
+    ],
+    imports: [
+        CommonModule,
+        UiElementsModule,
+        TranslateModule
+    ],
+    exports: [],
+    entryComponents: [
+        ServiceDependenciesComponent
+    ],
+    providers: []
+})
+export class ServiceDependenciesModule {
+}
\ No newline at end of file
index 76b0b9e..4f283fb 100644 (file)
@@ -175,8 +175,12 @@ export class DynamicElementComponent {
                 tmp.push(new DropdownValue(true,'TRUE'));
                 tmp.push(new DropdownValue(false,'FALSE'));
                 this.cmpRef.instance.values = tmp;
-                if(!_.isUndefined(this.value)){//contains the real value (and not a string)
-                    this.value = JSON.parse(this.value);
+                try {
+                    if (typeof this.value === 'string') {
+                        this.value = JSON.parse(this.value);
+                    }
+                } catch(err) {
+                    this.value = null;
                 }
                 break;
 
index 9df2680..5c061a4 100644 (file)
         width:0;
         height:0;
         display:none;
-        &:checked ~ .checkbox-icon::before{
-            .sprite-new;
-            .filled-checkbox-icon
+        &:checked {
+            ~ .checkbox-icon::before{
+                .sprite-new;
+                .filled-checkbox-icon
+            }
+            &[disabled] ~ .checkbox-icon::before {
+                opacity: 0.5;
+            }
         }
-        &[disabled] ~ .checkbox-icon::before {
+        &[disabled]:not(:checked) ~ .checkbox-icon::before {
             /* TODO: add disabled styles here */
             background-image: none;
             background-color: #EFEFEF;
diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.html b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.html
new file mode 100644 (file)
index 0000000..f22e454
--- /dev/null
@@ -0,0 +1,56 @@
+<div class="service-dependencies-editor">
+    <form class="w-sdc-form">
+
+        <div class="sdc-modal-top-bar">
+            <div class="sdc-modal-top-bar-buttons" *ngIf="currentIndex >= 0">
+                <span (click)="onChangePage(currentIndex - 1)" [ngClass]="{'disabled' : currentIndex === 0}" class="sprite-new left-arrow" data-tests-id="get-prev-rule" tooltip="Previous"></span>
+                <span (click)="onChangePage(currentIndex + 1)" [ngClass]="{'disabled' : currentIndex === input.serviceRules.length - 1}" class="sprite-new right-arrow" data-tests-id="get-next-rule" tooltip="Next"></span>
+            </div>
+        </div>
+
+        <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+
+        <div class="i-sdc-form-content">
+            <div class="rule-builder-content">
+                <div class="i-sdc-form-item rule-input-field">
+                    <label class="i-sdc-form-label required">Service {{currentServiceName}} Property</label>
+                    <ui-element-dropdown
+                            class="i-sdc-form-select"
+                            data-tests-id="servicePropertyName"
+                            [values]="ddValueSelectedServicePropertiesNames"
+                            [(value)]="currentRule.servicePropertyName"
+                            (change)="onServicePropertyChanged()">
+                    </ui-element-dropdown>
+                </div>
+
+                <div class="i-sdc-form-item rule-input-field operator">
+                    <ui-element-dropdown class="i-sdc-form-select" data-tests-id="constraintOperator" [values]="operatorTypes" [(value)]="currentRule.constraintOperator"></ui-element-dropdown>
+                </div>
+
+                <div class="i-sdc-form-item rule-input-field">
+                    <label class="i-sdc-form-label required" >Source</label>
+                    <ui-element-dropdown class="i-sdc-form-select" data-tests-id="sourceType" [values]="sourceTypes" [(value)]="currentRule.sourceName" (change)="onSelectSourceType($event)"></ui-element-dropdown>
+                </div>
+
+                <div class="rule-input-field assigned-value-field">
+                    <label class="i-sdc-form-label required" >{{assignedValueLabel}}</label>
+                    <dynamic-element
+                            *ngIf="currentRule.sourceType === SOURCE_TYPES.STATIC.value"
+                            [(value)]="currentRule.value"
+                            class="rule-assigned-value"
+                            data-tests-id="ruleAssignedValue"
+                            (elementChanged)="onValueChange($event.isValid)"
+                            [type]="selectedPropertyObj ? selectedPropertyObj.type : 'string'">
+                    </dynamic-element>
+                    <ui-element-dropdown *ngIf="currentRule.sourceType !== SOURCE_TYPES.STATIC.value"
+                                         class="rule-assigned-value"
+                                         data-tests-id="ruleAssignedValue"
+                                         [(value)]="currentRule.value"
+                                         [values]="listOfValuesToAssign">
+                    </ui-element-dropdown>
+                </div>
+            </div>
+        </div>
+    </form>
+
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.less b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.less
new file mode 100644 (file)
index 0000000..e03b73c
--- /dev/null
@@ -0,0 +1,43 @@
+@import './../../../../assets/styles/variables.less';
+
+.sdc-modal-top-bar {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.i-sdc-form-content {
+  display: flex;
+  flex-direction: column;
+  margin-top: 10px;
+  padding-bottom: 20px;
+
+  .i-sdc-form-item {
+    width: 250px;
+    &.operation {
+      width: 60px;
+    }
+  }
+
+  .rule-builder-content {
+    display: flex;
+    align-items: flex-end;
+    .rule-input-field {
+      flex: 1;
+      &:not(:last-of-type) {
+        margin-right: 20px;
+      }
+      &.operator{
+        width: 55px;
+        flex: 0 1 auto;
+      }
+      &.assigned-value-field {
+        margin-bottom: 10px;
+      }
+      /deep/ ui-element-dropdown select,
+      /deep/ ui-element-input input {
+        height: 30px;
+      }
+    }
+
+  }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts
new file mode 100644 (file)
index 0000000..db5e7a9
--- /dev/null
@@ -0,0 +1,192 @@
+/*!
+ * 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 } from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {ConstraintObjectUI, OPERATOR_TYPES} from 'app/ng2/components/logic/service-dependencies/service-dependencies.component';
+import {ServiceInstanceObject, PropertyBEModel} from 'app/models';
+import { PROPERTY_DATA } from 'app/utils';
+import {DropdownValue} from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component';
+
+export class UIDropDownSourceTypesElement extends DropdownValue{
+    options: Array<any>;
+    assignedLabel: string;
+    type: string;
+    constructor(input?: any){
+        if(input) {
+            let value = input.value || '';
+            let label = input.label || '';
+            super(value, label);
+            this.options = input.options;
+            this.assignedLabel = input.assignedLabel;
+            this.type = input.type;
+        }
+    }
+}
+
+@Component({
+    selector: 'service-dependencies-editor',
+    templateUrl: './service-dependencies-editor.component.html',
+    styleUrls:['./service-dependencies-editor.component.less'],
+    providers: [ServiceServiceNg2]
+})
+
+export class ServiceDependenciesEditorComponent {
+
+    input: {
+        serviceRuleIndex: number,
+        serviceRules: Array<ConstraintObjectUI>,
+        compositeServiceName: string,
+        currentServiceName: string,
+        compositeServiceProperties: Array<PropertyBEModel>,
+        selectedInstanceProperties: Array<PropertyBEModel>,
+        operatorTypes: Array<DropdownValue>,
+        selectedInstanceSiblings: Array<ServiceInstanceObject>
+    };
+    currentServiceName: string;
+    selectedServiceProperties: Array<PropertyBEModel>;
+    selectedPropertyObj: PropertyBEModel;
+    ddValueSelectedServicePropertiesNames: Array<DropdownValue>;
+    operatorTypes: Array<DropdownValue>;
+    sourceTypes: Array<UIDropDownSourceTypesElement> = [];
+    currentRule: ConstraintObjectUI;
+    currentIndex: number;
+    listOfValuesToAssign: Array<DropdownValue>;
+    listOfSourceOptions: Array<PropertyBEModel>;
+    assignedValueLabel: string;
+    serviceRulesList: Array<ConstraintObjectUI>;
+
+
+    SOURCE_TYPES = {
+        STATIC: {label: 'Static', value: 'static'},
+        SERVICE_PROPERTY: {label: 'Service Property', value: 'property'}
+    };
+
+
+    ngOnInit() {
+        this.currentIndex = this.input.serviceRuleIndex;
+        this.serviceRulesList = this.input.serviceRules;
+        this.currentRule = this.serviceRulesList && this.input.serviceRuleIndex >= 0 ?
+            this.serviceRulesList[this.input.serviceRuleIndex]:
+            new ConstraintObjectUI({sourceName: this.SOURCE_TYPES.STATIC.value, sourceType: this.SOURCE_TYPES.STATIC.value, value: "", constraintOperator: OPERATOR_TYPES.EQUAL});
+        this.currentServiceName = this.input.currentServiceName;
+        this.operatorTypes = this.input.operatorTypes;
+        this.selectedServiceProperties = this.input.selectedInstanceProperties;
+        this.ddValueSelectedServicePropertiesNames = _.map(this.input.selectedInstanceProperties, prop => new DropdownValue(prop.name, prop.name));
+        this.initSourceTypes();
+        this.syncRuleData();
+        this.updateSourceTypesRelatedValues();
+    }
+
+    initSourceTypes() {
+        this.sourceTypes.push({label: this.SOURCE_TYPES.STATIC.label, value: this.SOURCE_TYPES.STATIC.value,
+            options: [], assignedLabel: this.SOURCE_TYPES.STATIC.label, type: this.SOURCE_TYPES.STATIC.value});
+        this.sourceTypes.push({
+            label: this.input.compositeServiceName,
+            value: this.input.compositeServiceName,
+            assignedLabel: this.SOURCE_TYPES.SERVICE_PROPERTY.label,
+            type: this.SOURCE_TYPES.SERVICE_PROPERTY.value,
+            options: this.input.compositeServiceProperties
+        });
+        _.forEach(this.input.selectedInstanceSiblings, sib =>
+            this.sourceTypes.push({
+                label: sib.name,
+                value: sib.name,
+                options: sib.properties || [],
+                assignedLabel: this.SOURCE_TYPES.SERVICE_PROPERTY.label,
+                type: this.SOURCE_TYPES.SERVICE_PROPERTY.value
+            })
+        );
+    }
+
+    syncRuleData() {
+        if(!this.currentRule.sourceName && this.currentRule.sourceType === this.SOURCE_TYPES.STATIC.value) {
+            this.currentRule.sourceName = this.SOURCE_TYPES.STATIC.value;
+        }
+        this.selectedPropertyObj = _.find(this.selectedServiceProperties, prop => prop.name === this.currentRule.servicePropertyName);
+        this.updateOperatorTypesList();
+        this.updateSourceTypesRelatedValues();
+    }
+
+    updateOperatorTypesList() {
+        if (this.selectedPropertyObj && PROPERTY_DATA.SIMPLE_TYPES_COMPARABLE.indexOf(this.selectedPropertyObj.type) === -1) {
+            this.operatorTypes = [{label: "=", value: OPERATOR_TYPES.EQUAL}];
+            this.currentRule.constraintOperator = OPERATOR_TYPES.EQUAL;
+        }
+        else {
+            this.operatorTypes = this.input.operatorTypes;
+        }
+    }
+
+    updateSourceTypesRelatedValues() {
+        if(this.currentRule.sourceName) {
+            let selectedSourceType: UIDropDownSourceTypesElement = this.sourceTypes.find(
+                t => t.value === this.currentRule.sourceName && t.type === this.currentRule.sourceType
+            );
+            this.listOfSourceOptions = selectedSourceType.options || [];
+            this.assignedValueLabel = selectedSourceType.assignedLabel || this.SOURCE_TYPES.STATIC.label;
+            this.filterOptionsByType();
+        }
+    }
+
+    onChangePage(newIndex) {
+        if (newIndex >= 0 && newIndex < this.input.serviceRules.length) {
+            this.currentIndex = newIndex;
+            this.currentRule = this.serviceRulesList[newIndex];
+            this.syncRuleData();
+        }
+    }
+
+    onServicePropertyChanged() {
+        this.selectedPropertyObj = _.find(this.selectedServiceProperties, prop => prop.name === this.currentRule.servicePropertyName);
+        this.updateOperatorTypesList();
+        this.filterOptionsByType();
+        this.currentRule.value = '';
+    }
+
+    onSelectSourceType() {
+        this.currentRule.sourceType = this.currentRule.sourceName === this.SOURCE_TYPES.STATIC.value ?
+            this.SOURCE_TYPES.STATIC.value :
+            this.SOURCE_TYPES.SERVICE_PROPERTY.value;
+        this.updateSourceTypesRelatedValues();
+        this.currentRule.value = '';
+    }
+
+    filterOptionsByType() {
+        if(!this.selectedPropertyObj) {
+            this.listOfValuesToAssign = [];
+            return;
+        }
+        this.listOfValuesToAssign =  this.listOfSourceOptions.reduce((result, op:PropertyBEModel) => {
+            if(op.type === this.selectedPropertyObj.type && op.schemaType === this.selectedPropertyObj.schemaType) {
+                result.push(new DropdownValue(op.name, op.name));
+            }
+            return result;
+        }, []);
+    }
+
+    onValueChange(isValidValue) {
+        this.currentRule.updateValidity(isValidValue);
+    }
+
+    checkFormValidForSubmit() {
+        if(!this.serviceRulesList) { //for create modal
+            let isStatic = this.currentRule.sourceName === this.SOURCE_TYPES.STATIC.value;
+            return this.currentRule.isValidRule(isStatic);
+        }
+        //for update all rules
+        return this.serviceRulesList.every(rule => rule.isValidRule(rule.sourceName === this.SOURCE_TYPES.STATIC.value));
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts
new file mode 100644 (file)
index 0000000..98ac997
--- /dev/null
@@ -0,0 +1,25 @@
+import { NgModule } from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {ServiceDependenciesEditorComponent} from "./service-dependencies-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";
+
+@NgModule({
+    declarations: [
+        ServiceDependenciesEditorComponent
+    ],
+    imports: [
+        CommonModule,
+        FormsModule,
+        FormElementsModule,
+        UiElementsModule
+    ],
+    exports: [],
+    entryComponents: [
+        ServiceDependenciesEditorComponent
+    ],
+    providers: []
+})
+export class ServiceDependenciesEditorModule {
+}
\ No newline at end of file
index 69871ab..e9036a8 100644 (file)
@@ -24,15 +24,17 @@ import {Observable} from 'rxjs/Observable';
 import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/toPromise';
 import {Response, URLSearchParams} from '@angular/http';
-import { Component, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, PropertyBEModel, OperationModel, BEOperationModel, CreateOperationResponse} from "app/models";
+import { Component, ComponentInstance, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData,
+    PropertyBEModel, OperationModel, BEOperationModel, CreateOperationResponse} from "app/models";
 import {downgradeInjectable} from '@angular/upgrade/static';
-import {COMPONENT_FIELDS, CommonUtils} from "app/utils";
+import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils";
 import {ComponentGenericResponse} from "../responses/component-generic-response";
 import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map";
 import {API_QUERY_PARAMS} from "app/utils";
 import { ComponentType, ServerTypeUrl } from "../../../utils/constants";
 import { HttpService } from '../http.service';
 import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config";
+import {ConstraintObject} from 'app/ng2/components/logic/service-dependencies/service-dependencies.component';
 import {IDependenciesServerResponse} from "../responses/dependencies-server-response";
 import {AutomatedUpgradeGenericResponse} from "../responses/automated-upgrade-response";
 import {IAutomatedUpgradeRequestObj} from "../../pages/automated-upgrade/automated-upgrade.service";
@@ -82,6 +84,10 @@ export class ComponentServiceNg2 {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES]);
     }
 
+    getComponentInstanceProperties(component:Component):Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES]);
+    }
+
     getComponentAttributes(component:Component):Observable<ComponentGenericResponse> {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_ATTRIBUTES]);
     }
@@ -226,7 +232,7 @@ export class ComponentServiceNg2 {
         return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input')
             .map((res:Response) => {
                 return new InputBEModel(res.json());
-            })
+            });
     }
 
     updateComponentInputs(component:Component, inputs:InputBEModel[]):Observable<InputBEModel[]> {
@@ -297,5 +303,37 @@ export class ComponentServiceNg2 {
                 return res.json();
             });
     }
+
+    updateComponentInstance(component:Component, componentInstance:ComponentInstance):Observable<ComponentInstance> {
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstance.uniqueId, componentInstance)
+            .map((res:Response) => {
+                return res.json();
+            });
+    }
+
+    getServiceFilterConstraints(component:Component):Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [SERVICE_FIELDS.NODE_FILTER]);
+    }
+
+    createServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraint:ConstraintObject):Observable<any> {
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter', constraint)
+            .map((res:Response) => {
+                return res.json();
+            });
+    }
+
+    updateServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraints:Array<ConstraintObject>):Observable<any> {
+        return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/', constraints)
+            .map((res:Response) => {
+                return res.json();
+            });
+    }
+
+    deleteServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraintIndex:number) {
+        return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/' + constraintIndex)
+            .map((res:Response) => {
+                return res.json();
+            });
+    }
 }
 
index 647cc92..37ccf38 100644 (file)
@@ -52,6 +52,7 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
     public interfaceOperations:Array<OperationModel>;
     public additionalInformation:any;
     public derivedList:Array<any>;
+    public nodeFilterData: Array<any>;
 
     deserialize (response): ComponentGenericResponse {
 
@@ -105,6 +106,9 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
         if(response.policies) {
             this.policies = CommonUtils.initPolicies(response.policies);
         }
+        if(response.nodeFilterData) {
+            this.nodeFilterData = response.nodeFilterData;
+        }
         return this;
     }
 }
index 0694560..379f2f1 100644 (file)
@@ -124,6 +124,7 @@ export class SOURCES {
 export class PROPERTY_DATA {
     public static TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON, PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME, PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP];
     public static SIMPLE_TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON, PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME];
+    public static SIMPLE_TYPES_COMPARABLE = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT];
     public static SCALAR_TYPES = [PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME];
     public static ROOT_DATA_TYPE = "tosca.datatypes.Root";
     public static OPENECOMP_ROOT = "org.openecomp.datatypes.Root";
@@ -327,6 +328,10 @@ export class GRAPH_EVENTS {
     static ON_GROUP_INSTANCE_UPDATE = 'onGroupInstanceUpdate';
 }
 
+export class DEPENDENCY_EVENTS {
+    static ON_DEPENDENCY_CHANGE = 'onDependencyStatusChange';
+}
+
 
 export class COMPONENT_FIELDS {
     static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties";
@@ -351,6 +356,7 @@ export class COMPONENT_FIELDS {
 
 export class SERVICE_FIELDS {
     static FORWARDING_PATHS = "forwardingPaths";
+    static NODE_FILTER = "nodeFilter";
 }
 
 export class API_QUERY_PARAMS {
index a77377b..2270a7b 100644 (file)
@@ -71,6 +71,11 @@ export interface ICompositionViewModelScope extends IWorkspaceViewModelScope {
     isPNF():boolean;
     isConfiguration():boolean;
     preventMoveTab(state: boolean):void;
+    registerCreateInstanceEvent(callback: Function):void;
+    unregisterCreateInstanceEvent():void;
+    registerChangeComponentInstanceNameEvent(callback: Function):void;
+    unregisterChangeComponentInstanceNameEvent():void;
+
     ComponentServiceNg2:ComponentServiceNg2,
     cacheComponentsInstancesFullData:Component;
 }
@@ -116,7 +121,7 @@ export class CompositionViewModel {
                 private ComponentServiceFactoryNg2: ComponentServiceFactoryNg2,
                 private ComponentServiceNg2:ComponentServiceNg2,
                 private Notification:any
-            ) {
+    ) {
 
         this.$scope.setValidState(true);
         this.initScope();
@@ -165,12 +170,12 @@ export class CompositionViewModel {
         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance);
         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, scope.setSelectedZoneInstance);
         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick);
-        this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_START, () => { 
+        this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_START, () => {
             scope.isCanvasTagging = true;
             this.eventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, true, this.showUnsavedChangesAlert);
         });
-        this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_END, () => { 
-            scope.isCanvasTagging = false; 
+        this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_END, () => {
+            scope.isCanvasTagging = false;
             this.resetUnsavedChanges();
         });
         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ZONE_INSTANCE_NAME_CHANGED, scope.changeZoneInstanceName);
@@ -184,13 +189,13 @@ export class CompositionViewModel {
                     title: "Unsaved Changes",
                     size: 'sm',
                     type: 'custom',
-                    
+
                     buttons: [
                         {id: 'cancelButton', text: 'Cancel', type: 'secondary', size: 'xsm', closeModal: true, callback: () => reject()},
                         {id: 'discardButton', text: 'Discard', type: 'secondary', size: 'xsm', closeModal: true, callback: () => { this.resetUnsavedChanges(); resolve()}},
                         {id: 'saveButton', text: 'Save', type: 'primary', size: 'xsm', closeModal: true, callback: () => {  reject(); this.saveUnsavedChanges(afterSave);  }}
-                ] as IModalButtonComponent[]
-            }, UnsavedChangesComponent, { isValidChangedData: true});
+                    ] as IModalButtonComponent[]
+                }, UnsavedChangesComponent, { isValidChangedData: true});
         });
 
         return deferred;
@@ -225,7 +230,7 @@ export class CompositionViewModel {
             testId: "renameInstanceModal",
             buttons: [
                 {id: 'saveButton', text: 'OK', size: 'xsm', callback: this.saveInstanceName, closeModal: false},
-                {id: 'cancelButton', text: 'Cancel', size: 'sm', closeModal: true} 
+                {id: 'cancelButton', text: 'Cancel', size: 'sm', closeModal: true}
             ]
         };
 
@@ -274,7 +279,7 @@ export class CompositionViewModel {
         }  else {
             this.ModalServiceSdcUI.closeModal();
         }
-        
+
     };
 
     private removeSelectedComponentInstance = ():void => {
@@ -372,12 +377,12 @@ export class CompositionViewModel {
             this.$scope.selectedZoneInstance = null;
             this.$scope.updateSelectedComponent();
 
-
-
-
             if (this.$state.current.name === 'workspace.composition.api') {
                 this.$state.go('workspace.composition.details');
             }
+            if(!selectedComponent.isServiceProxy() && (this.$state.current.name === 'workspace.composition.consumption' || this.$state.current.name === 'workspace.composition.dependencies')) {
+                this.$state.go('workspace.composition.details');
+            }
         };
 
         this.$scope.setSelectedZoneInstance = (zoneInstance: ZoneInstance): void => {
@@ -390,7 +395,7 @@ export class CompositionViewModel {
             this.$scope.selectedZoneInstance = null;
             this.$scope.selectedComponent = this.$scope.currentComponent;
 
-            if (this.$state.current.name === 'workspace.composition.api') {
+            if (this.$state.current.name === 'workspace.composition.api' || this.$state.current.name === 'workspace.composition.consumption' || this.$state.current.name === 'workspace.composition.dependencies') {
                 this.$state.go('workspace.composition.details');
             }
 
@@ -406,7 +411,7 @@ export class CompositionViewModel {
         this.$scope.changeZoneInstanceName = (newName:string):void => {
             this.$scope.selectedZoneInstance.instanceData.name = newName;
         };
-    
+
         this.$scope.deleteSelectedComponentInstance = ():void => {
             const {currentComponent} = this.$scope;
             const {title, message} = this.$scope.sdcMenu.alertMessages['deleteInstance'];
@@ -477,5 +482,20 @@ export class CompositionViewModel {
 
         this.eventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.$scope.reload);
 
+        this.$scope.registerCreateInstanceEvent = (callback: Function): void => {
+            this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, callback);
+        };
+
+        this.$scope.unregisterCreateInstanceEvent = (): void => {
+            this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE);
+        };
+
+        this.$scope.registerChangeComponentInstanceNameEvent = (callback: Function): void => {
+            this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, callback);
+        };
+
+        this.$scope.unregisterChangeComponentInstanceNameEvent = (): void => {
+            this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED);
+        };
     }
 }
index 75265d9..a571fdc 100644 (file)
                 </button>
                 <button data-ng-if="!selectedComponent.isService() || (isComponentInstanceSelected() && currentComponent.selectedInstance.isServiceProxy())" class="i-sdc-designer-sidebar-tab"
                         data-ui-sref-active="active" ui-sref="workspace.composition.relations"
-                        tooltips tooltip-class="tooltip-custom tab-tooltip  tooltip-rightside"
+                        tooltips tooltip-class="tooltip-custom tab-tooltip {{currentComponent.selectedInstance.isServiceProxy() ? '' : 'tooltip-rightside'}}"
                         data-tests-id="requirements-and-capabilities"
                         tooltip-content="Requirements and Capabilities"
                         data-ng-class="{'disabled': disabledTabs}">
                         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.dependencies"
+                        tooltips tooltip-class="tooltip-custom tab-tooltip " tooltip-content="Service Dependencies"
+                        data-tests-id="service-dependency-tab"
+                        data-ng-if="(isComponentInstanceSelected() && currentComponent.selectedInstance.isServiceProxy())"
+                        data-ng-class="{'disabled': disabledTabs}">
+                    <div class="i-sdc-designer-sidebar-tab-icon sprite-new dependencies-icon"></div>
+                </button>
 
             </div>
             <div data-ui-view="" class="w-sdc-designer-sidebar-tab-content-view"></div>
index e389395..36ceabf 100644 (file)
@@ -20,7 +20,7 @@
 
 'use strict';
 import * as _ from "lodash";
-import {Component} from "app/models";
+import {Component, ModalModel, ButtonModel} from "app/models";
 import {GRAPH_EVENTS} from "app/utils";
 import {LeftPaletteLoaderService, EventListenerService} from "app/services";
 import {ICompositionViewModelScope} from "../../composition-view-model";
@@ -28,6 +28,7 @@ import {LeftPaletteComponent} from "../../../../../../models/components/displayC
 import {ComponentServiceFactoryNg2} from "app/ng2/services/component-services/component.service.factory";
 import {ServiceServiceNg2} from 'app/ng2/services/component-services/service.service';
 import {Service} from "app/models/components/service";
+import {ModalService} from 'app/ng2/services/modal.service';
 
 export interface IEditResourceVersion {
     allVersions:any;
@@ -41,24 +42,31 @@ interface IDetailsViewModelScope extends ICompositionViewModelScope {
     editForm:ng.IFormController;
     editResourceVersion:IEditResourceVersion;
 
-    changeResourceVersion():void;
+    onChangeResourceVersion():void;
+    alertBeforeChangeResourceVersion():void;
+    changeVersion():void;
+    cancelChangeResourceVersion():void;
 }
 
 export class DetailsViewModel {
 
     static '$inject' = [
         '$scope',
+        '$filter',
         'LeftPaletteLoaderService',
         'EventListenerService',
         'ComponentServiceFactoryNg2',
-        'ServiceServiceNg2'
+        'ServiceServiceNg2',
+        'ModalServiceNg2'
     ];
 
     constructor(private $scope:IDetailsViewModelScope,
+                private $filter:ng.IFilterService,
                 private LeftPaletteLoaderService:LeftPaletteLoaderService,
                 private eventListenerService:EventListenerService,
                 private ComponentServiceFactoryNg2: ComponentServiceFactoryNg2,
-                private serviceService: ServiceServiceNg2) {
+                private serviceService: ServiceServiceNg2,
+                private ModalServiceNg2: ModalService) {
         this.initScope();
     }
 
@@ -115,7 +123,35 @@ export class DetailsViewModel {
             }
         });
 
-        this.$scope.changeResourceVersion = ():void => {
+        this.$scope.onChangeResourceVersion = ():void => {
+            if(this.$scope.isComponentInstanceSelected() && this.$scope.currentComponent.selectedInstance.isServiceProxy()) {
+                this.$scope.alertBeforeChangeResourceVersion();
+            }
+            else {
+                this.$scope.changeVersion();
+            }
+        };
+
+        this.$scope.alertBeforeChangeResourceVersion = ():void => {
+            let modalApproveTxt:string = this.$filter('translate')("MODAL_APPROVE");
+            let modalCancelTxt:string = this.$filter('translate')("MODAL_CANCEL");
+            let changeVersionModalTitle:string = this.$filter('translate')("DETAILS_TAB_CHANGE_VERSION_MODAL_TITLE");
+            let changeVersionModalMsg:string = this.$filter('translate')("DETAILS_TAB_CHANGE_VERSION_MODAL_MSG");
+
+            let actionButton: ButtonModel = new ButtonModel(modalApproveTxt, 'blue', this.$scope.changeVersion);
+            let cancelButton: ButtonModel = new ButtonModel(modalCancelTxt, 'grey', this.$scope.cancelChangeResourceVersion);
+            let modalModel: ModalModel = new ModalModel('sm', changeVersionModalTitle, changeVersionModalMsg, [actionButton, cancelButton]);
+            let customModal = this.ModalServiceNg2.createCustomModal(modalModel);
+            customModal.instance.open();
+        };
+
+        this.$scope.cancelChangeResourceVersion = () => {
+            this.ModalServiceNg2.closeCurrentModal();
+            this.$scope.editResourceVersion.changeVersion = this.$scope.currentComponent.selectedInstance.componentVersion;
+        };
+
+        this.$scope.changeVersion = ():void => {
+            this.ModalServiceNg2.closeCurrentModal();
             this.$scope.isLoading = true;
             this.$scope.$parent.isLoading = true;
 
index 145943e..db5322a 100644 (file)
@@ -46,7 +46,7 @@
                             data-ng-disabled="$parent.isViewOnly || selectedComponent.uniqueId != editResourceVersion.allVersions[editResourceVersion.changeVersion] || selectedComponent.archived"
                             class="i-sdc-designer-sidebar-section-content-item-value i-sdc-form-select"
                             data-ng-class="{'minor': (editResourceVersion.changeVersion)%1, 'disabled':selectedComponent.archived}"
-                            data-ng-change="changeResourceVersion()">
+                            data-ng-change="onChangeResourceVersion()">
                         <option class="select-instance-version" data-ng-class="{'minor': key%1}"
                                 ng-repeat="(key, value) in editResourceVersion.allVersions">{{key}}</option>
                     </select></ng-form>
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view-model.ts
new file mode 100644 (file)
index 0000000..b634e60
--- /dev/null
@@ -0,0 +1,125 @@
+/*!
+ * 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, ComponentInstance, PropertiesGroup, ServiceInstanceObject, PropertyBEModel} from 'app/models';
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ConstraintObject} from "app/ng2/components/logic/service-dependencies/service-dependencies.component";
+import {ComponentGenericResponse} from 'app/ng2/services/responses/component-generic-response';
+import {DEPENDENCY_EVENTS} from "app/utils/constants";
+import {EventListenerService} from 'app/services';
+
+interface IServiceDependenciesViewModelScope extends ICompositionViewModelScope {
+    service: Service;
+    selectedInstanceSiblings: Array<ServiceInstanceObject>;
+    componentInstancesConstraints: Array<any>;
+    selectedInstanceConstraints: Array<ConstraintObject>;
+    selectedInstanceProperties: Array<PropertyBEModel>;
+    updateSelectedInstanceConstraints(constraintsList:Array<ConstraintObject>): void;
+    loadConstraints(): void;
+    componentInstanceProperties: PropertiesGroup;
+    notifyDependencyEventsObserver: Function;
+}
+
+
+
+export class ServiceDependenciesViewModel {
+
+    static '$inject' = [
+        '$scope',
+        'ComponentServiceNg2',
+        'EventListenerService'
+    ];
+
+    constructor(private $scope:IServiceDependenciesViewModelScope, private ComponentServiceNg2:ComponentServiceNg2, private eventListenerService: EventListenerService) {
+        this.$scope.service = <Service>this.$scope.currentComponent;
+        this.$scope.notifyDependencyEventsObserver = this.notifyDependencyEventsObserver;
+        this.initInstancesWithProperties();
+        this.loadConstraints();
+
+        this.initScope();
+    }
+
+    private initInstancesWithProperties = ():void => {
+        this.ComponentServiceNg2.getComponentInstanceProperties(this.$scope.currentComponent).subscribe((genericResponse:ComponentGenericResponse) => {
+            this.$scope.componentInstanceProperties = genericResponse.componentInstancesProperties;
+            this.updateInstanceAttributes();
+        });
+    }
+
+    private updateInstanceAttributes = ():void => {
+        if (this.$scope.isComponentInstanceSelected() && this.$scope.componentInstanceProperties) {
+            let instancesMappedList = this.$scope.service.componentInstances.map(coInstance => new ServiceInstanceObject({
+                id: coInstance.uniqueId,
+                name: coInstance.name,
+                properties: this.$scope.componentInstanceProperties[coInstance.uniqueId] || []
+            }));
+            this.$scope.selectedInstanceProperties = this.$scope.componentInstanceProperties[this.$scope.currentComponent.selectedInstance.uniqueId];
+            this.$scope.selectedInstanceSiblings = instancesMappedList.filter(coInstance => coInstance.id !== this.$scope.currentComponent.selectedInstance.uniqueId);
+        }
+    }
+
+    private initScope = ():void => {
+        this.$scope.$watch('currentComponent.selectedInstance', (newInstance:ComponentInstance):void => {
+            if (angular.isDefined(newInstance) && this.$scope.componentInstancesConstraints) {
+                this.updateInstanceAttributes();
+                this.$scope.selectedInstanceConstraints = this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId] ?
+                    this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId].properties :
+                    [];
+            }
+        });
+        this.$scope.$watch('componentInstancesConstraints', (constraints: Array<any>):void => {
+            if (angular.isDefined(constraints)) {
+                if(this.$scope.isComponentInstanceSelected()) {
+                    this.$scope.selectedInstanceConstraints = this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId] ?
+                        this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId].properties || [] :
+                        [];
+                }
+            }
+        });
+
+        this.$scope.updateSelectedInstanceConstraints = (constraintsList:Array<ConstraintObject>):void => {
+            this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId].properties = constraintsList;
+            this.$scope.selectedInstanceConstraints = this.$scope.componentInstancesConstraints[this.$scope.currentComponent.selectedInstance.uniqueId].properties;
+        }
+
+        this.$scope.loadConstraints = ():void => {
+            this.loadConstraints();
+        }
+
+        this.$scope.registerCreateInstanceEvent(() => {
+            this.initInstancesWithProperties();
+        });
+
+        this.$scope.registerChangeComponentInstanceNameEvent((updatedComponentInstance) => {
+            this.$scope.currentComponent.selectedInstance = updatedComponentInstance;
+        });
+
+        this.$scope.$on('$destroy', this.$scope.unregisterCreateInstanceEvent);
+        this.$scope.$on('$destroy', this.$scope.unregisterChangeComponentInstanceNameEvent);
+    }
+
+    private loadConstraints = ():void => {
+        this.ComponentServiceNg2.getServiceFilterConstraints(this.$scope.service).subscribe((response) => {
+            this.$scope.componentInstancesConstraints = response.nodeFilterData;
+        });
+    }
+
+    public notifyDependencyEventsObserver = (isChecked: boolean):void => {
+        this.eventListenerService.notifyObservers(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE, isChecked);
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view.html
new file mode 100644 (file)
index 0000000..ba50994
--- /dev/null
@@ -0,0 +1,25 @@
+
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content service-dependencies">
+    <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="Service Dependencies">Service Dependencies</span>
+            <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+        </expand-collapse>
+        <div class="w-sdc-designer-sidebar-section-content" data-ng-if="isComponentInstanceSelected()">
+            <div class="i-sdc-designer-sidebar-section-content-item">
+                <ng2-service-dependencies
+                    [composite-service]="service"
+                    [current-service-instance]="currentComponent.selectedInstance"
+                    [selected-instance-properties]="selectedInstanceProperties"
+                    [selected-instance-siblings]="selectedInstanceSiblings"
+                    [selected-instance-constraints]="selectedInstanceConstraints"
+                    [readonly]="isViewMode() || !isDesigner()"
+                    (dependency-status)="notifyDependencyEventsObserver($event)"
+                    (update-rules-list-event)="updateSelectedInstanceConstraints($event)"
+                    (load-rules-list-event)="loadConstraints()">
+                </ng2-service-dependencies>
+            </div>
+        </div>
+    </div>
+</perfect-scrollbar>
\ No newline at end of file
index 8e43f18..1c68091 100644 (file)
@@ -42,7 +42,6 @@
     "GENERAL_LABEL_SYSTEM_NAME": "System Name:",
     "GENERAL_LABEL_SOURCE_SERVICE_NAME": "Source Service Name:",
     "GENERAL_LABEL_RESOURCE_CUSTOMIZATION_UUID": "Resource Customization UUID:",
-    
 
     "=========== GENERAL_TAB ===========": "",
     "GENERAL_TAB_LABEL_RESOURCE_MODEL_NUMBER":"Vendor Model Number",
     "DEPLOYMENT_ARTIFACT_BUTTON_ADD_VOLUME_HEAT": "Add Volume HEAT Artifact",
     "DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER": "Add Other Artifact"
 
-    ,"=========== IMPORT VF ===========": "",
+,"=========== IMPORT VF ===========": "",
     "IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_TITLE": "Create VF",
     "IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_DESCRIPTION": "Your VF is being created.<br/>It can take up to 10 minutes.<br/>When done, you can view it in SDC Catalog.",
     "IMPORT_VF_MESSAGE_CREATE_FINISHED_TITLE": "Create/Update",
     "PLUGIN_NOT_CONNECTED_ERROR_MAIN": "The \"{{pluginName}}\" plugin is currently unavailable.",
     "PLUGIN_NOT_CONNECTED_ERROR_SUB": "Please try again later.",
 
+    "============SERVICE CONSUMPTION=============" : "",
+    "CONSUMPTION_TYPE": "Type",
+    "CONSUMPTION_SOURCE": "Source",
+    "CONSUMPTION_STATIC_VALUES": "Static Values",
+    "CONSUMPTION_EXPAND_ALL": "Expand All",
+    "CONSUMPTION_COLLAPSE_ALL": "Collapse All",
+    "CONSUMPTION_NO_INPUTS_TO_SHOW": "NO INPUTS TO SHOW",
+    "CONSUMPTION_NO_OPERATIONS_TO_SHOW": "NO OPERATIONS TO SHOW",
+
+    "============= SERVICE DEPENDENCY ============" : "",
+    "SERVICE_DEPENDENCY_ADD_RULE": "Add Rule",
+    "SERVICE_DEPENDENCY_UPDATE_RULE": "Update Rule",
+    "SERVICE_DEPENDENCY_DELETE_RULE": "Delete Rule",
+    "SERVICE_DEPENDENCY_DELETE_RULE_MSG": "Are you sure you want to delete this rule?",
+    "SERVICE_DEPENDENCY_UNCHECK_TITLE": "Remove Dependency",
+    "SERVICE_DEPENDENCY_UNCHECK_TEXT": "Unchecking \"Mark as Dependent\" will remove dependency and erase all the dependency rules. Are you sure you want to remove dependency?",
+
+    "============= COMPOSITION DETAILS TAB ======" : "",
+    "DETAILS_TAB_CHANGE_VERSION_MODAL_TITLE": "Change Version",
+    "DETAILS_TAB_CHANGE_VERSION_MODAL_MSG": "Are you sure you want to change the version?\nIt will affect Service-Consumption and Service-Dependencies data",
+
+    "============= Generic Modal Btn ============" : "",
+    "MODAL_APPROVE" : "Yes",
+    "MODAL_SAVE" : "Save",
+    "MODAL_CREATE" : "Create",
+    "MODAL_CANCEL" : "Cancel",
+    "MODAL_DELETE" : "Delete",
     "=========== POLICY AND GROUPS ===========": "",
     "ADD_TARGETS" : "Add Targets",
     "ADD_MEMBERS" : "Add Members",
diff --git a/catalog-ui/src/assets/styles/images/service-proxy-icons/dependent.png b/catalog-ui/src/assets/styles/images/service-proxy-icons/dependent.png
new file mode 100644 (file)
index 0000000..603093e
Binary files /dev/null and b/catalog-ui/src/assets/styles/images/service-proxy-icons/dependent.png differ
diff --git a/catalog-ui/src/assets/styles/images/service-proxy-icons/uncertified_dependent.png b/catalog-ui/src/assets/styles/images/service-proxy-icons/uncertified_dependent.png
new file mode 100644 (file)
index 0000000..26ebe60
Binary files /dev/null and b/catalog-ui/src/assets/styles/images/service-proxy-icons/uncertified_dependent.png differ
index e71f344..43bd2a5 100644 (file)
     user-select: text;
 }
 
+.multiline-ellipsis(@lineHeight: 1.3em, @lineCount: 2, @bgColor: @main_color_p){
+    overflow: hidden;
+    position: relative;
+    line-height: @lineHeight;
+    max-height: @lineHeight * @lineCount;
+    text-align: left;
+    word-break: normal;
+    white-space: normal;
+    padding-right: 1em;
+    &:before {
+        content: '...';
+        position: absolute;
+        right: 3px;
+        bottom: 0;
+    }
+    &:after {
+        content: '';
+        position: absolute;
+        right: 0;
+        width: 1em;
+        height: 1em;
+        margin-top: 0.2em;
+        background: @bgColor;
+    }
+}
+
 
 .square-icon() {
     display: inline-block;
index 29c9574..7deed22 100644 (file)
 .t_15 {.font-color > .t; .font-type > ._15;}
 
 /* Added by ikram - */
+.s_1 {.font-color > .s; .font-type > ._1;}
 .s_12 {.font-color > .s; .font-type > ._12;}
 
 .z_9  {.font-color > .z; .font-type > ._9;}
index 3e56a01..383a830 100644 (file)
 .sprite-new.properties:active                   { background-position: -350px -671px; width: 19px;  height: 19px;}
 .active > .sprite-new.properties                { background-position: -350px -671px; width: 19px;  height: 19px;}
 
+.sprite-new.dependencies-icon                     { background-position: -751px -693px; width: 19px;  height: 19px;  opacity: 0.7;}
+.sprite-new.dependencies:active                   { background-position: -751px -693px; width: 19px;  height: 19px;}
+
 .sprite-new.distribution-bth                    { background-position: -399px -716px; width: 55px;  height: 21px;}
 .sprite-new.distribution-bth.disable            { background-position: -464px -716px; width: 55px;  height: 21px;}