Add 'Req & Cap' screen for VF/PNF/Service - UI 16/79616/2
authormiriame <miriam.eini@amdocs.com>
Mon, 4 Mar 2019 11:49:15 +0000 (13:49 +0200)
committerAvi Gaffa <avi.gaffa@amdocs.com>
Tue, 5 Mar 2019 06:57:57 +0000 (06:57 +0000)
 Issue-ID: SDC-2142

Change-Id: I23a2de18862e18389f801cbec3e452d7094df8e9
Signed-off-by: miriame <miriam.eini@amdocs.com>
31 files changed:
catalog-ui/configurations/menu.js
catalog-ui/src/app/app.ts
catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html
catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts
catalog-ui/src/app/models.ts
catalog-ui/src/app/models/capability-types.ts [new file with mode: 0644]
catalog-ui/src/app/models/capability.ts
catalog-ui/src/app/models/component-metadata.ts
catalog-ui/src/app/models/node-types.ts [new file with mode: 0644]
catalog-ui/src/app/models/relationship-types.ts [new file with mode: 0644]
catalog-ui/src/app/models/requirement.ts
catalog-ui/src/app/models/tosca-presentation.ts [new file with mode: 0644]
catalog-ui/src/app/modules/service-module.ts
catalog-ui/src/app/ng2/app.module.ts
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-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/tosca-types.service.ts [new file with mode: 0644]
catalog-ui/src/app/utils/constants.ts
catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html [new file with mode: 0644]
catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
catalog-ui/src/assets/languages/en_US.json
catalog-ui/src/assets/styles/sprite.less

index e433ebc..d93b3ac 100644 (file)
@@ -465,8 +465,13 @@ const SDC_MENU_CONFIG = {
             {"text": "Composition", "action": "onMenuItemPressed", "state": "workspace.composition.details"},
             {"text": "Operation", "action":"onMenuItemPressed", "state": "workspace.interface_operation"},
             {"text": "Activity Log", "action": "onMenuItemPressed", "state": "workspace.activity_log"},
-            {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"},            
-            {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"}
+            {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"},
+            {
+                "text": "Properties Assignment",
+                "action": "onMenuItemPressed",
+                "state": "workspace.properties_assignment"
+            },
+            {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"}
         ],
         "PNF": [
             {"text": "General", "action": "onMenuItemPressed", "state": "workspace.general"},
@@ -475,7 +480,12 @@ const SDC_MENU_CONFIG = {
             {"text": "Composition", "action": "onMenuItemPressed", "state": "workspace.composition.details"},
             {"text": "Operation", "action": "onMenuItemPressed", "state": "workspace.interface_operation"},
             {"text": "Activity Log", "action": "onMenuItemPressed", "state": "workspace.activity_log"},
-            {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"}
+            {
+                "text": "Properties Assignment",
+                "action": "onMenuItemPressed",
+                "state": "workspace.properties_assignment"
+            },
+            {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"}
         ],
         "CR": [
             {"text": "General", "action": "onMenuItemPressed", "state": "workspace.general"},
@@ -495,8 +505,13 @@ const SDC_MENU_CONFIG = {
             {"text": "Management Workflow", "action": "onMenuItemPressed", "state": "workspace.management_workflow"},
             {"text": "Network Call Flow ", "action": "onMenuItemPressed", "state": "workspace.network_call_flow"},
             {"text": "Distribution","action": "onMenuItemPressed","state": "workspace.distribution","disabledRoles": ["ADMIN", "TESTER", "GOVERNOR", "DESIGNER"]},
-            {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"},            
-            {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"}
+            {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"},
+            {
+                "text": "Properties Assignment",
+                "action": "onMenuItemPressed",
+                "state": "workspace.properties_assignment"
+            },
+            {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"}
         ]
     }
 
index 8fa7f1e..ebcfdd2 100644 (file)
@@ -388,6 +388,18 @@ ng1appModule.config([
             }
         );
 
+        $stateProvider.state(
+            States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE, {
+                url: 'req_and_capabilities_editable',
+                parent: 'workspace',
+                controller: viewModelsModuleName + '.ReqAndCapabilitiesViewModel',
+                templateUrl: './view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html',
+                data: {
+                    bodyClass: 'attributes'
+                }
+            }
+        );
+
 
         $stateProvider.state(
             States.WORKSPACE_MANAGEMENT_WORKFLOW, {
index 6ddbd61..d59c44d 100644 (file)
@@ -18,7 +18,8 @@
 {{actualText}}
 
 <span class="ellipsis-directive-more-less"
-      data-ng-click="collapsed = !collapsed; toggleText()"
-      data-ng-hide="ellipsis.length <= maxChars">
-  {{collapsed ? "More" : "Less"}}
+      data-ng-click="onMoreLessClick($event)"
+      data-ng-hide="ellipsis.length <= maxChars"
+      data-tests-id="ellipsis-more-less">
+  {{actualText ? (collapsed ? "More" : "Less") : ""}}
 </span>
index 60baf3e..21e074a 100644 (file)
@@ -23,6 +23,7 @@ export interface IEllipsisScope extends ng.IScope {
     ellipsis:string;
     maxChars:number;
     toggleText():void;
+    onMoreLessClick(event): void;
     collapsed:boolean;
     actualText:string;
 
@@ -50,6 +51,12 @@ export class EllipsisDirective implements ng.IDirective {
 
         scope.collapsed = true;
 
+        scope.onMoreLessClick = (event): void => {
+            event.stopPropagation();
+            scope.collapsed = !scope.collapsed;
+            scope.toggleText();
+        };
+
         scope.toggleText = ():void => {
             if (scope.ellipsis && scope.collapsed) {
                 scope.actualText = scope.ellipsis.substr(0, scope.maxChars);
index 3a31caf..5c79155 100644 (file)
@@ -112,7 +112,11 @@ export * from './models/modal';
 export * from './models/button';
 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/filter-properties-assignment-data';
+export * from './models/properties-inputs/input-be-model';
+export * from './models/service-instance-properties';
+export * from './models/relationship-types';
+export * from './models/tosca-presentation';
+export * from './models/node-types';
+export * from './models/capability-types';
 
diff --git a/catalog-ui/src/app/models/capability-types.ts b/catalog-ui/src/app/models/capability-types.ts
new file mode 100644 (file)
index 0000000..fc01f54
--- /dev/null
@@ -0,0 +1,41 @@
+/*!
+ * 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 {ToscaPresentationData} from "./tosca-presentation";
+
+export class CapabilityTypesMap {
+    capabilityTypesMap: CapabilityTypesMapData;
+
+    constructor(capabilityTypesMap: CapabilityTypesMapData) {
+        this.capabilityTypesMap = capabilityTypesMap;
+    }
+}
+
+export class CapabilityTypesMapData {
+    [capabilityTypeId: string]: CapabilityTypeModel;
+}
+
+export class CapabilityTypeModel {
+    derivedFrom: string;
+    toscaPresentation: ToscaPresentationData;
+
+    constructor(capabilityType?: CapabilityTypeModel) {
+        if (capabilityType) {
+            this.derivedFrom = capabilityType.derivedFrom;
+            this.toscaPresentation = capabilityType.toscaPresentation;
+        }
+    }
+}
\ No newline at end of file
index 74994b1..caef2e8 100644 (file)
@@ -57,7 +57,7 @@ export class Capability implements RequirementCapabilityModel{
     uniqueId:string;
     capabilitySources:Array<String>;
     leftOccurrences:string;
-    minOccurrences:string;
+    minOccurrences: number;
     maxOccurrences:string;
     description:string;
     validSourceTypes:Array<string>;
index 9f5e22c..0f0a30d 100644 (file)
@@ -49,6 +49,7 @@ export class ComponentMetadata {
     public systemName:string;
     public archived:boolean;
     public vspArchived: boolean;
+    public toscaResourceName: string;
 
     //Resource only
     public resourceType: string;
@@ -118,6 +119,7 @@ export class ComponentMetadata {
         this.archived = response.archived;
         this.instantiationType = response.instantiationType;
         this.vspArchived = response.vspArchived;
+        this.toscaResourceName = response.toscaResourceName;
         return this;
     }
 
diff --git a/catalog-ui/src/app/models/node-types.ts b/catalog-ui/src/app/models/node-types.ts
new file mode 100644 (file)
index 0000000..54d2ef5
--- /dev/null
@@ -0,0 +1,45 @@
+/*!
+ * 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 {ComponentMetadata} from "./component-metadata";
+
+export class NodeTypesMap {
+    nodeTypesMap: NodeTypesMapData;
+
+    constructor(nodeTypesMap: NodeTypesMapData) {
+        this.nodeTypesMap = nodeTypesMap;
+    }
+}
+
+export class NodeTypesMapData {
+    [nodeTypeId: string]: NodeTypeModel;
+}
+
+export class NodeTypeModel {
+    componentMetadataDefinition: ComponentMetadataDefModel;
+
+    constructor(nodeType?: NodeTypeModel) {
+        this.componentMetadataDefinition = nodeType.componentMetadataDefinition;
+    }
+}
+
+export class ComponentMetadataDefModel {
+    componentMetadataDataDefinition: ComponentMetadata;
+
+    constructor(componentMetadataDef?: ComponentMetadataDefModel) {
+        this.componentMetadataDataDefinition = componentMetadataDef.componentMetadataDataDefinition;
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/models/relationship-types.ts b/catalog-ui/src/app/models/relationship-types.ts
new file mode 100644 (file)
index 0000000..8ae827b
--- /dev/null
@@ -0,0 +1,41 @@
+/*!
+ * 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 {ToscaPresentationData} from "./tosca-presentation";
+
+export class RelationshipTypesMap {
+    relationshipTypesMap: RelationshipTypesMapData;
+
+    constructor(relationshipTypesMap: RelationshipTypesMapData) {
+        this.relationshipTypesMap = relationshipTypesMap;
+    }
+}
+
+export class RelationshipTypesMapData {
+    [relationshipTypeId: string]: RelationshipTypeModel;
+}
+
+export class RelationshipTypeModel {
+    derivedFrom: string;
+    toscaPresentation: ToscaPresentationData;
+
+    constructor(relationshipType?: RelationshipTypeModel) {
+        if (relationshipType) {
+            this.derivedFrom = relationshipType.derivedFrom;
+            this.toscaPresentation = relationshipType.toscaPresentation;
+        }
+    }
+}
index 65428b3..3cc0cf2 100644 (file)
@@ -51,7 +51,7 @@ export class Requirement implements RequirementCapabilityModel{
     uniqueId:string;
     relationship:string;
     leftOccurrences:string;
-    minOccurrences:string;
+    minOccurrences: number;
     maxOccurrences:string;
     //custom
     filterTerm:string;
diff --git a/catalog-ui/src/app/models/tosca-presentation.ts b/catalog-ui/src/app/models/tosca-presentation.ts
new file mode 100644 (file)
index 0000000..3fdddde
--- /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.
+ */
+
+export class ToscaPresentationData {
+    creationTime: number;
+    description: string;
+    type: string;
+    validTargetTypes: Array<string>;
+    modificationTime: number;
+    uniqueId: string;
+
+    constructor(toscaPresentation?: ToscaPresentationData) {
+        if (toscaPresentation) {
+            this.creationTime = toscaPresentation.creationTime;
+            this.description = toscaPresentation.description;
+            this.type = toscaPresentation.type;
+            this.validTargetTypes = toscaPresentation.validTargetTypes;
+            this.modificationTime = toscaPresentation.modificationTime;
+            this.uniqueId = toscaPresentation.uniqueId;
+        }
+    }
+}
index f4350a3..376a036 100644 (file)
@@ -61,6 +61,7 @@ import {DynamicComponentService} from "app/ng2/services/dynamic-component.servic
 import {AutomatedUpgradeService} from "../ng2/pages/automated-upgrade/automated-upgrade.service";
 import {ArchiveService as ArchiveServiceNg2} from "app/ng2/services/archive.service";
 import {ComponentFactory} from "app/utils/component-factory";
+import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service";
 
 let moduleName:string = 'Sdc.Services';
 let serviceModule:ng.IModule = angular.module(moduleName, []);
@@ -115,3 +116,4 @@ serviceModule.factory('EventBusService', downgradeInjectable(EventBusService));
 serviceModule.factory('DynamicComponentService', downgradeInjectable(DynamicComponentService));
 serviceModule.factory('ArchiveServiceNg2', downgradeInjectable(ArchiveServiceNg2));
 serviceModule.factory('AutomatedUpgradeService', downgradeInjectable(AutomatedUpgradeService));
+serviceModule.factory('ToscaTypesServiceNg2', downgradeInjectable(ToscaTypesServiceNg2));
index 1ae2df2..0ff9378 100644 (file)
@@ -43,6 +43,7 @@ import { ComponentServiceFactoryNg2 } from "./services/component-services/compon
 import { ServiceServiceNg2 } from "./services/component-services/service.service";
 import { ComponentInstanceServiceNg2 } from "./services/component-instance-services/component-instance.service";
 import { WorkflowServiceNg2 } from './services/workflow.service';
+import {ToscaTypesServiceNg2} from "./services/tosca-types.service";
 import { ModalService } from "./services/modal.service";
 import { UiElementsModule } from "./components/ui/ui-elements.module";
 import { ConnectionWizardModule } from "./pages/connection-wizard/connection-wizard.module";
@@ -73,6 +74,8 @@ import {GroupsService} from "./services/groups.service";
 import {PoliciesService} from "./services/policies.service";
 import {AutomatedUpgradeService} from "./pages/automated-upgrade/automated-upgrade.service";
 import {AutomatedUpgradeModule} from "./pages/automated-upgrade/automated-upgrade.module";
+import {RequirementsEditorModule} from "./pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module"
+import {CapabilitiesEditorModule} from "./pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module"
 
 export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
 
@@ -114,12 +117,14 @@ export function configServiceFactory(config: ConfigService) {
         ServicePathModule,
         ServicePathSelectorModule,
         ServiceDependenciesModule,
-        ServiceDependenciesEditorModule
+        ServiceDependenciesEditorModule,
+        RequirementsEditorModule,
+        CapabilitiesEditorModule
     ],
     exports: [],
     entryComponents: [
         // *** sdc-ui components to be used as downgraded:
-        // SdcUiComponents.ButtonComponent
+        SdcUiComponents.SvgIconComponent
     ],
     providers: [
         WindowRef,
@@ -143,6 +148,7 @@ export function configServiceFactory(config: ConfigService) {
         ServiceServiceNg2,
         AutomatedUpgradeService,
         WorkflowServiceNg2,
+        ToscaTypesServiceNg2,
         HttpService,
         UserService,
         PoliciesService,
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html
new file mode 100644 (file)
index 0000000..c0bfc8a
--- /dev/null
@@ -0,0 +1,92 @@
+<div class="capability-editor">
+    <form class="w-sdc-form">
+        <div class="i-sdc-form-content-capability-content">
+            <div class="content-row">
+                <div class="i-sdc-form-item">
+                    <sdc-input
+                            label="{{ 'CAP_NAME' | translate}}"
+                            required="true"
+                            class="i-sdc-form-input"
+                            testId="capName"
+                            [disabled]="isReadonly"
+                            [(value)]="capabilityData.name"
+                            (valueChange)="validityChanged()">
+                    </sdc-input>
+                </div>
+            </div>
+
+            <div class="group-with-border">
+                <div class="content-row i-sdc-form-item">
+                    <sdc-dropdown
+                            label="{{ 'CAP_TYPE' | translate}}"
+                            required="true"
+                            class="i-sdc-form-select"
+                            testId="capType"
+                            [disabled]="isReadonly"
+                            [options]="capabilityTypesMappedList"
+                            selectedOption="{{capabilityData.type}}"
+                            (changed)="onSelectCapabilityType($event)">
+                    </sdc-dropdown>
+                </div>
+                <div class="content-row i-sdc-form-item">
+                    <label class="i-sdc-form-label"> {{ 'CAP_DESCRIPTION' | translate}} </label>
+                    <textarea
+                            rows="3"
+                            class="i-sdc-form-input description"
+                            data-tests-id="capDesc"
+                            disabled
+                            value="{{capabilityData.description}}">
+                    </textarea>
+                </div>
+                <div class="content-row i-sdc-form-item">
+                    <label class="i-sdc-form-label valid-source-label"> {{ 'CAP_VALID_SOURCE' | translate}} </label>
+                    <textarea
+                            rows="2"
+                            class="i-sdc-form-input"
+                            data-tests-id="capValidSrc"
+                            disabled
+                            value="{{capabilityData.validSourceTypes}}">
+                    </textarea>
+                </div>
+            </div>
+
+            <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label>
+            <div class="content-row occurrences-section">
+                <div class="min-occurrences-value">
+                    <sdc-input
+                            label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}"
+                            class="i-sdc-form-input"
+                            testId="capOccurrencesMin"
+                            [disabled]="isReadonly"
+                            [(value)]="capabilityData.minOccurrences"
+                            (valueChange)="validityChanged()"
+                            type="number">
+                    </sdc-input>
+                </div>
+                <div class="sdc-input">
+                    <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label>
+                    <div class="max-occurrences-value">
+                        <sdc-checkbox
+                                class="checkbox-label unbounded-value"
+                                testId="capOccurrencesMaxUnbounded"
+                                label="{{translatedUnboundTxt.toLowerCase()}}"
+                                (checkedChange)="onUnboundedChanged()"
+                                [checked]="isUnboundedChecked"
+                                [disabled]="isReadonly">
+                        </sdc-checkbox>
+
+                        <sdc-input
+                                *ngIf="!isUnboundedChecked"
+                                class="i-sdc-form-input"
+                                testId="capOccurrencesMax"
+                                [disabled]="isReadonly"
+                                [(value)]="capabilityData.maxOccurrences"
+                                (valueChange)="validityChanged()"
+                                type="number">
+                        </sdc-input>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less
new file mode 100644 (file)
index 0000000..9156281
--- /dev/null
@@ -0,0 +1,38 @@
+@import '../../../../../assets/styles/variables.less';
+
+.capability-editor {
+  .i-sdc-form-content-capability-content {
+    padding: 10px 25px;
+    .group-with-border {
+      margin: 25px 0;
+      padding: 15px 0;
+      border-top: 1px solid @tlv_color_u;
+      border-bottom: 1px solid @tlv_color_u;
+      .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+      }
+    }
+
+    .occurrences-label {
+      font-family: @font-opensans-bold;
+      margin-bottom: 19px;
+    }
+    .occurrences-section, /deep/ .max-occurrences-value {
+      display: flex;
+      .min-occurrences-value {
+        padding-right: 30px;
+      }
+      .unbounded-value {
+        padding-top: 7px;
+        padding-right: 20px;
+        .sdc-checkbox__label {
+          text-transform: capitalize;
+        }
+      }
+    }
+    textarea {
+      min-height: unset;
+      height: unset;
+    }
+  }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts
new file mode 100644 (file)
index 0000000..82e2e46
--- /dev/null
@@ -0,0 +1,73 @@
+import {Component} from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {Capability, CapabilityTypeModel} from 'app/models';
+import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+
+@Component({
+    selector: 'capabilities-editor',
+    templateUrl: './capabilities-editor.component.html',
+    styleUrls: ['./capabilities-editor.component.less'],
+    providers: [ServiceServiceNg2]
+})
+
+export class CapabilitiesEditorComponent {
+    input: {
+        capability: Capability,
+        capabilityTypesList: Array<CapabilityTypeModel>,
+        isReadonly: boolean;
+        validityChangedCallback: Function;
+    };
+    capabilityData: Capability;
+    capabilityTypesMappedList: Array<DropdownValue>;
+    isUnboundedChecked: boolean;
+    isReadonly: boolean;
+    translatedUnboundTxt: string;
+
+    constructor(private translateService: TranslateService) {
+    }
+
+    ngOnInit() {
+        this.capabilityData = new Capability(this.input.capability);
+        this.translatedUnboundTxt = '';
+        this.capabilityData.minOccurrences = this.capabilityData.minOccurrences || 0;
+        this.translateService.languageChangedObservable.subscribe(lang => {
+            this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED');
+            this.capabilityData.maxOccurrences = this.capabilityData.maxOccurrences || this.translatedUnboundTxt;
+            this.isUnboundedChecked = this.capabilityData.maxOccurrences === this.translatedUnboundTxt;
+        });
+        this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type));
+        this.isReadonly = this.input.isReadonly;
+        this.validityChanged();
+    }
+
+    onUnboundedChanged() {
+        this.isUnboundedChecked = !this.isUnboundedChecked;
+        this.capabilityData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null;
+        this.validityChanged();
+    }
+
+    checkFormValidForSubmit() {
+        return this.capabilityData.name && this.capabilityData.name.length &&
+            this.capabilityData.type && this.capabilityData.type.length && !_.isEqual(this.capabilityData.minOccurrences, "") && this.capabilityData.minOccurrences >= 0 &&
+            (
+                this.isUnboundedChecked ||
+                (this.capabilityData.maxOccurrences && (this.capabilityData.minOccurrences <= parseInt(this.capabilityData.maxOccurrences)))
+            );
+    }
+
+    onSelectCapabilityType(selectedCapType: DropdownValue) {
+        this.capabilityData.type = selectedCapType && selectedCapType.value;
+        if (selectedCapType && selectedCapType.value) {
+            let selectedCapabilityTypeObj: CapabilityTypeModel = this.input.capabilityTypesList.find(capType => capType.toscaPresentation.type === selectedCapType.value);
+            this.capabilityData.description = selectedCapabilityTypeObj.toscaPresentation.description;
+            this.capabilityData.validSourceTypes = selectedCapabilityTypeObj.toscaPresentation.validTargetTypes;
+        }
+        this.validityChanged();
+    }
+
+    validityChanged = () => {
+        let validState = this.checkFormValidForSubmit();
+        this.input.validityChangedCallback(validState);
+    }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts
new file mode 100644 (file)
index 0000000..1e767a5
--- /dev/null
@@ -0,0 +1,28 @@
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {CapabilitiesEditorComponent} from "./capabilities-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 {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index";
+
+@NgModule({
+    declarations: [
+        CapabilitiesEditorComponent
+    ],
+    imports: [CommonModule,
+        FormsModule,
+        FormElementsModule,
+        UiElementsModule,
+        TranslateModule,
+        SdcUiComponentsModule
+    ],
+    exports: [],
+    entryComponents: [
+        CapabilitiesEditorComponent
+    ],
+    providers: []
+})
+export class CapabilitiesEditorModule {
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html
new file mode 100644 (file)
index 0000000..0fe326e
--- /dev/null
@@ -0,0 +1,88 @@
+<div class="requirement-editor">
+    <form class="w-sdc-form">
+        <div class="i-sdc-form-content-requirement-content">
+            <div class="content-row">
+                <div class="i-sdc-form-item">
+                    <sdc-input
+                            label="{{ 'REQ_NAME' | translate}}"
+                            required="true"
+                            testId="reqName"
+                            [disabled]="isReadonly"
+                            [(value)]="requirementData.name"
+                            (valueChange)="validityChanged()">
+                    </sdc-input>
+                </div>
+            </div>
+
+            <div class="group-with-border">
+                <div class="content-row i-sdc-form-item">
+                    <sdc-dropdown
+                            label="{{ 'REQ_RELATED_CAPABILITY' | translate}}"
+                            testId="reqRelatedCapability"
+                            required="true"
+                            [disabled]="isReadonly"
+                            [options]="capabilityTypesMappedList"
+                            selectedOption="{{requirementData.capability}}"
+                            (changed)="onCapabilityChanged($event)">
+                    </sdc-dropdown>
+                </div>
+                <div class="content-row i-sdc-form-item">
+                    <sdc-dropdown
+                            label="{{ 'REQ_NODE' | translate}}"
+                            testId="reqNode"
+                            [disabled]="isReadonly"
+                            [options]="nodeTypesMappedList"
+                            selectedOption="{{requirementData.node}}"
+                            (changed)="onNodeChanged($event)">
+                    </sdc-dropdown>
+                </div>
+                <div class="content-row i-sdc-form-item">
+                    <sdc-dropdown
+                            label="{{ 'REQ_RELATIONSHIP' | translate}}"
+                            testId="reqRelationship"
+                            [disabled]="isReadonly"
+                            [options]="relationshipTypesMappedList"
+                            selectedOption="{{requirementData.relationship}}"
+                            (changed)="onRelationshipChanged($event)">
+                    </sdc-dropdown>
+                </div>
+            </div>
+
+            <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label>
+            <div class="content-row occurrences-section">
+                <div class="min-occurrences-value">
+                    <sdc-input
+                            label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}"
+                            testId="reqOccurrencesMin"
+                            [disabled]="isReadonly"
+                            [(value)]="requirementData.minOccurrences"
+                            (valueChange)="validityChanged()"
+                            type="number">
+                    </sdc-input>
+                </div>
+                <div class="sdc-input">
+                    <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label>
+                    <div class="max-occurrences-value">
+                        <sdc-checkbox
+                                class="checkbox-label unbounded-value"
+                                testId="reqOccurrencesMaxUnbounded"
+                                label="{{translatedUnboundTxt.toLowerCase()}}"
+                                (checkedChange)="onUnboundedChanged()"
+                                [checked]="isUnboundedChecked"
+                                [disabled]="isReadonly">
+                        </sdc-checkbox>
+                        <sdc-input
+                                *ngIf="!isUnboundedChecked"
+                                testId="reqOccurrencesMax"
+                                [disabled]="isReadonly"
+                                [(value)]="requirementData.maxOccurrences"
+                                (valueChange)="validityChanged()"
+                                type="number">
+                        </sdc-input>
+                    </div>
+
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less
new file mode 100644 (file)
index 0000000..f0ae3bf
--- /dev/null
@@ -0,0 +1,35 @@
+@import '../../../../../assets/styles/variables.less';
+
+.requirement-editor {
+  .i-sdc-form-content-requirement-content {
+    padding: 10px 25px;
+
+    .group-with-border {
+      margin: 25px 0;
+      padding: 15px 0;
+      border-top: 1px solid @tlv_color_u;
+      border-bottom: 1px solid @tlv_color_u;
+      .content-row:not(:last-of-type) {
+        padding-bottom: 13px;
+      }
+    }
+
+    .occurrences-label {
+      font-family: @font-opensans-bold;
+      margin-bottom: 19px;
+    }
+    .occurrences-section, /deep/ .max-occurrences-value {
+      display: flex;
+      .min-occurrences-value {
+        padding-right: 30px;
+      }
+      .unbounded-value {
+        padding-top: 7px;
+        padding-right: 20px;
+        .sdc-checkbox__label {
+          text-transform: capitalize;
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts
new file mode 100644 (file)
index 0000000..464b581
--- /dev/null
@@ -0,0 +1,88 @@
+import {Component} from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {Requirement, RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel} from 'app/models';
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+
+@Component({
+    selector: 'requirements-editor',
+    templateUrl: 'requirements-editor.component.html',
+    styleUrls: ['requirements-editor.component.less'],
+    providers: [ServiceServiceNg2, TranslateService]
+})
+
+export class RequirementsEditorComponent {
+
+    input: {
+        requirement: Requirement,
+        relationshipTypesList: Array<RelationshipTypeModel>;
+        nodeTypesList: Array<NodeTypeModel>;
+        capabilityTypesList: Array<CapabilityTypeModel>;
+        isReadonly: boolean;
+        validityChangedCallback: Function;
+    };
+    requirementData: Requirement;
+    capabilityTypesMappedList: Array<DropdownValue>;
+    relationshipTypesMappedList: Array<DropdownValue>;
+    nodeTypesMappedList: Array<DropdownValue>;
+    isUnboundedChecked: boolean;
+    isReadonly: boolean;
+    translatedUnboundTxt: string;
+
+    constructor(private translateService: TranslateService) {
+    }
+
+    ngOnInit() {
+        this.requirementData = new Requirement(this.input.requirement);
+        this.requirementData.minOccurrences = this.requirementData.minOccurrences || 0;
+        this.translatedUnboundTxt = '';
+        this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type));
+        this.relationshipTypesMappedList = _.map(this.input.relationshipTypesList, rType => new DropdownValue(rType.toscaPresentation.type, rType.toscaPresentation.type));
+        this.nodeTypesMappedList = _.map(this.input.nodeTypesList, nodeType => {
+            return new DropdownValue(
+                nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName,
+                nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName)
+        });
+        this.translateService.languageChangedObservable.subscribe(lang => {
+            this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED');
+            this.requirementData.maxOccurrences = this.requirementData.maxOccurrences || this.translatedUnboundTxt;
+            this.isUnboundedChecked = this.requirementData.maxOccurrences === this.translatedUnboundTxt;
+        });
+        this.isReadonly = this.input.isReadonly;
+        this.validityChanged();
+    }
+
+    onUnboundedChanged() {
+        this.isUnboundedChecked = !this.isUnboundedChecked;
+        this.requirementData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null;
+        this.validityChanged();
+    }
+
+    onCapabilityChanged(selectedCapability: DropdownValue) {
+        this.requirementData.capability = selectedCapability && selectedCapability.value;
+        this.validityChanged();
+    }
+
+    onNodeChanged(selectedNode: DropdownValue) {
+        this.requirementData.node = selectedNode && selectedNode.value;
+    }
+
+    onRelationshipChanged(selectedRelationship: DropdownValue) {
+        this.requirementData.relationship = selectedRelationship && selectedRelationship.value;
+    }
+
+    checkFormValidForSubmit() {
+        return this.requirementData.name && this.requirementData.name.length &&
+            this.requirementData.capability && this.requirementData.capability.length && !_.isEqual(this.requirementData.minOccurrences, "") && this.requirementData.minOccurrences >= 0 &&
+            (
+                this.isUnboundedChecked ||
+                (this.requirementData.maxOccurrences && (this.requirementData.minOccurrences <= parseInt(this.requirementData.maxOccurrences)))
+            );
+    }
+
+    validityChanged = () => {
+        let validState = this.checkFormValidForSubmit();
+        this.input.validityChangedCallback(validState);
+    }
+
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts
new file mode 100644 (file)
index 0000000..1be8be5
--- /dev/null
@@ -0,0 +1,26 @@
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {RequirementsEditorComponent} from "./requirements-editor.component";
+import {FormsModule} from "@angular/forms";
+import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module";
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index";
+
+@NgModule({
+    declarations: [
+        RequirementsEditorComponent
+    ],
+    imports: [CommonModule,
+        FormsModule,
+        FormElementsModule,
+        TranslateModule,
+        SdcUiComponentsModule
+    ],
+    exports: [],
+    entryComponents: [
+        RequirementsEditorComponent
+    ],
+    providers: []
+})
+export class RequirementsEditorModule {
+}
\ No newline at end of file
index 41bfc4e..46dfe01 100644 (file)
@@ -25,7 +25,8 @@ 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} from "app/models";
+    PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement
+} from "app/models";
 import {downgradeInjectable} from '@angular/upgrade/static';
 import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils";
 import {ComponentGenericResponse} from "../responses/component-generic-response";
@@ -207,6 +208,68 @@ export class ComponentServiceNg2 {
         return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]);
     }
 
+    createCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> {
+        let capBEObj = {
+            'capabilities': {
+                [capabilityData.type]: [capabilityData]
+            }
+        };
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    updateCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> {
+        let capBEObj = {
+            'capabilities': {
+                [capabilityData.type]: [capabilityData]
+            }
+        };
+        return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    deleteCapability(component: Component, capId: string): Observable<Capability> {
+        return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities/' + capId)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    createRequirement(component: Component, requirementData: Requirement): Observable<any> {
+        let reqBEObj = {
+            'requirements': {
+                [requirementData.capability]: [requirementData]
+            }
+        };
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    updateRequirement(component: Component, requirementData: Requirement): Observable<any> {
+        let reqBEObj = {
+            'requirements': {
+                [requirementData.capability]: [requirementData]
+            }
+        };
+        return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    deleteRequirement(component: Component, reqId: string): Observable<Requirement> {
+        return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements/' + reqId)
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
     getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]);
     }
diff --git a/catalog-ui/src/app/ng2/services/tosca-types.service.ts b/catalog-ui/src/app/ng2/services/tosca-types.service.ts
new file mode 100644 (file)
index 0000000..66826c0
--- /dev/null
@@ -0,0 +1,55 @@
+/*!
+ * 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 {Injectable, Inject} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {HttpService} from './http.service';
+import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config";
+import {CapabilityTypesMap, NodeTypesMap, RelationshipTypesMap} from "app/models";
+import {Response} from '@angular/http';
+
+declare var angular: angular.IAngularStatic;
+
+@Injectable()
+export class ToscaTypesServiceNg2 {
+
+    protected baseUrl;
+
+    constructor(protected http: HttpService, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) {
+        this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root;
+    }
+
+    fetchRelationshipTypes(): Observable<RelationshipTypesMap> {
+        return this.http.get(this.baseUrl + 'relationshipTypes')
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    fetchNodeTypes(): Observable<NodeTypesMap> {
+        return this.http.get(this.baseUrl + 'nodeTypes')
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+
+    fetchCapabilityTypes(): Observable<CapabilityTypesMap> {
+        return this.http.get(this.baseUrl + 'capabilityTypes')
+            .map((res: Response) => {
+                return res.json();
+            });
+    }
+}
index 379f2f1..104b5dc 100644 (file)
@@ -264,6 +264,7 @@ export class States {
     public static WORKSPACE_DISTRIBUTION = 'workspace.distribution';
     public static WORKSPACE_PROPERTIES_ASSIGNMENT = 'workspace.properties_assignment';
     public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES = 'workspace.reqAndCap';
+    public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE = 'workspace.reqAndCapEditable';
     public static WORKSPACE_PLUGINS = 'workspace.plugins';
     public static WORKSPACE_NG2 = 'workspace.ng2';
 }
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html
new file mode 100644 (file)
index 0000000..14bc49e
--- /dev/null
@@ -0,0 +1,197 @@
+<!--
+  ~ 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.
+-->
+
+<div class="workspace-req-and-cap-editable">
+    <loader data-display="isLoading"></loader>
+
+    <div class="tabs-header">
+        <div class="req-and-cap-tabs">
+            <div data-tests-id="req-tab" data-ng-click="onSwitchTab()" class="tab"
+                 data-ng-class="{'selected':mode=='requirements'}">Requirements
+            </div>
+            <div data-tests-id="cap-tab" data-ng-click="onSwitchTab()" class="tab"
+                 data-ng-class="{'selected':mode=='capabilities'}">Capabilities
+            </div>
+        </div>
+        <div class="buttons-in-right" data-ng-if="!isListEmpty()">
+            <div class="search">
+                <input id="search-box" data-ng-if="filter.show" data-tests-id="search-box" placeholder="Search"
+                       data-ng-model-options="{debounce: 200}" data-ng-model="filter.txt" data-ng-change="onFilter()"/>
+                <div class="search-icon-container" data-tests-id="search-icon">
+                    <svg-icon
+                            class="hand"
+                            [name]="'search-o'"
+                            [mode]="'primary'"
+                            [size]="'small'"
+                            [clickable]="'true'"
+                            data-ng-click="onSearchIconClick()">
+                    </svg-icon>
+                </div>
+            </div>
+            <div class="add-button-icon-and-label" data-ng-if="isEditable" data-ng-click="onAddBtnClicked()"
+                 data-ng-class="{'disabled': isReadonly()}" data-tests-id="add-button">
+                <svg-icon
+                        name="plus"
+                        mode="primary"
+                        size="small"
+                        clickable="true"
+                        [disabled]="isReadonly()"
+                        labelPlacement="top">
+                </svg-icon>
+                <span class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</span>
+            </div>
+        </div>
+    </div>
+
+    <div class="empty-list-container" data-ng-if="isListEmpty() && !isLoading" data-tests-id="empty-list-container">
+        <div class="empty-list-add-btn add-button-icon-and-label" data-ng-class="{'disabled': isReadonly()}"
+             data-ng-click="onAddBtnClicked()" data-tests-id="empty-list-add-btn">
+            <svg-icon
+                    name="plus-circle"
+                    mode="primary"
+                    size="x_large"
+                    clickable="true"
+                    [disabled]="isReadonly()">
+            </svg-icon>
+            <div class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</div>
+        </div>
+    </div>
+
+    <div class="table-container-flex requirements-table" data-ng-if="mode=='requirements' && !isListEmpty()">
+        <div class="table" data-ng-class="{'view-mode': isViewMode()}" data-tests-id="requirement-table">
+            <div class="head flex-container">
+                <div data-ng-repeat="header in editableRequirementsTableHeadersList track by $index"
+                     data-ng-click="sort(header.property, requirementsSortTableDefined)"
+                     class="table-header head-row hand flex-item {{header.property}}"
+                     data-tests-id="table-header-{{header.property}}">
+                    {{header.title}}
+                    <span data-ng-if="requirementsSortTableDefined.sortByField === header.property"
+                          class="table-header-sort-arrow" data-tests-id="table-header-sort-arrow"
+                          data-ng-class="{'down': requirementsSortTableDefined.reverse, 'up':!requirementsSortTableDefined.reverse}"> </span>
+                </div>
+            </div>
+
+            <div class="body">
+                <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data">
+                    <div data-ng-if="filteredRequirementsList.length === 0" class="no-row-text"
+                         data-tests-id="no-rows-in-table">
+                        There are no requirements to display
+
+                    </div>
+                    <div data-ng-repeat="req in filteredRequirementsList | orderBy:requirementsSortTableDefined.sortByField:requirementsSortTableDefined.reverse  track by $index"
+                         data-tests-id="reqRow">
+                        <div class="flex-container data-row" data-ng-class="{'editable-row': req.isCreatedManually}"
+                             data-ng-click="req.isCreatedManually && onEditRequirement(req)">
+                            <div class="table-col-general flex-item text ellipsis-text" tooltips
+                                 tooltip-content="{{req.name}}">
+                                <span data-tests-id="{{req.name}}">{{req.name}}</span>
+                            </div>
+                            <div class="table-col-general flex-item text ellipsis-text" tooltips
+                                 tooltip-content="{{req.capability}}">
+                                <span data-tests-id="{{req.capability}}">{{req.capability && cutToscaTypePrefix(req.capability, 'capabilities.')}}</span>
+                            </div>
+                            <div class="table-col-general flex-item text ellipsis-text" tooltips
+                                 tooltip-content="{{req.node}}">
+                                <span data-tests-id="{{req.node}}">{{req.node && cutToscaTypePrefix(req.node, "nodes.")}}</span>
+                            </div>
+                            <div class="table-col-general flex-item text ellipsis-text" tooltips
+                                 tooltip-content="{{req.relationship}}">
+                                <span data-tests-id="{{req.relationship}}">{{req.relationship && cutToscaTypePrefix(req.relationship, "relationships.")}}</span>
+                            </div>
+                            <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips
+                                 tooltip-content="{{req.minOccurrences}} - {{req.maxOccurrences}}">
+                                <span data-tests-id="{{req.minOccurrences}} - {{req.maxOccurrences}}">{{req.minOccurrences}} - {{req.maxOccurrences}}</span>
+                            </div>
+                            <div class="table-col-general flex-item text other-col" data-tests-id="delete-req"
+                                 data-ng-class="{'disabled': isReadonly()}">
+                                <svg-icon name="trash-o" class="trash-icon" size="small"
+                                          data-ng-if="req.isCreatedManually && !isReadonly()"
+                                          data-ng-click="onDeleteReq($event, req)"></svg-icon>
+                            </div>
+                        </div>
+                    </div>
+                </perfect-scrollbar>
+            </div>
+
+        </div>
+    </div>
+    <div class="table-container-flex capabilities-table" data-ng-if="mode=='capabilities' && !isListEmpty()"
+         data-tests-id="capabilities-table">
+        <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+            <div class="head flex-container">
+                <div data-ng-repeat="header in editableCapabilitiesTableHeadersList track by $index"
+                     data-ng-click="sort(header.property, capabilitiesSortTableDefined)"
+                     class="table-header head-row hand flex-item {{header.property}}"
+                     data-tests-id="header-{{header.property}}">
+                    {{header.title}}
+                    <span data-ng-if="capabilitiesSortTableDefined.sortByField === header.property"
+                          class="table-header-sort-arrow" data-tests-id=="table-header-sort-arrow"
+                          data-ng-class="{'down': capabilitiesSortTableDefined.reverse, 'up':!capabilitiesSortTableDefined.reverse}"> </span>
+                </div>
+            </div>
+
+            <div class="body">
+                <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data">
+                    <div data-ng-if="filteredCapabilitiesList.length === 0" class="no-row-text"
+                         data-tests-id="no-rows-in-table">
+                        There are no capabilities to display
+
+                    </div>
+                    <div data-ng-repeat="capability in filteredCapabilitiesList | orderBy:capabilitiesSortTableDefined.sortByField:capabilitiesSortTableDefined.reverse  track by $index"
+                         class="flex-container data-row"
+                         data-ng-class="{'selected': capability.selected, 'editable-row': capability.isCreatedManually}"
+                         data-ng-click="capability.isCreatedManually && onEditCapability(capability)"
+                         data-tests-id="capabilities-table-row">
+
+                        <div class="table-col-general flex-item text ellipsis-text" tooltips
+                             tooltip-content="{{capability.name}}">
+                            <span data-tests-id="{{capability.name}}">{{capability.name}}</span>
+                        </div>
+                        <div class="table-col-general flex-item text ellipsis-text" tooltips
+                             tooltip-content="{{capability.type}}">
+                            <span data-tests-id="{{capability.type}}">{{capability.type && cutToscaTypePrefix(capability.type, 'capabilities.')}}</span>
+                        </div>
+
+                        <div class="table-col-general flex-item text description-col">
+                            <div data-tests-id="{{capability.description}}" class="multiline-ellipsis"
+                                 ellipsis="capability.description" max-chars="60">{{capability.description}}
+                            </div>
+                        </div>
+
+                        <div class="table-col-general flex-item text ellipsis-text" tooltips
+                             tooltip-content="{{capability.validSourceTypes.join(',')}}">
+                            <span data-tests-id="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span>
+                        </div>
+
+                        <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips
+                             tooltip-content="{{capability.minOccurrences}} - {{capability.maxOccurrences}}">
+                            <span data-tests-id="{{capability.minOccurrences}} - {{capability.maxOccurrences}}">{{capability.minOccurrences}} - {{capability.maxOccurrences}}</span>
+                        </div>
+
+                        <div class="table-col-general flex-item text other-col" data-tests-id="delete-cap"
+                             data-ng-class="{'disabled': isReadonly()}">
+                            <svg-icon name="trash-o" class="trash-icon" size="small"
+                                      data-ng-if="capability.isCreatedManually && !isReadonly()"
+                                      data-ng-click="onDeleteCap($event, capability)"></svg-icon>
+                        </div>
+                    </div>
+                </perfect-scrollbar>
+            </div>
+
+        </div>
+    </div>
+</div>
+
index 6eaae44..165578d 100644 (file)
  */
 'use strict';
 import * as _ from "lodash";
+import {ComponentRef} from '@angular/core';
 import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model";
-import {ModalsHandler} from "app/utils";
-import {Capability, PropertyModel, Requirement} from "app/models";
-import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response";
-import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service";
+import {ModalsHandler, ResourceType} from "app/utils";
+import {ComponentType} from "app/utils/constants";
+import {
+    Capability, PropertyModel, Requirement, Resource,
+    RelationshipTypesMap, NodeTypesMap, CapabilityTypesMap
+} from "app/models";
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service";
+import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
+import {ModalService} from 'app/ng2/services/modal.service';
+import {RequirementsEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component';
+import {CapabilitiesEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component';
+import {ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service";
+import {IModalConfig} from "sdc-ui/lib/angular/modals/models/modal-config";
+import {ModalButtonComponent} from "sdc-ui/lib/angular/components";
 
 export class SortTableDefined {
     reverse:boolean;
     sortByField:string;
 }
 
+class RequirementUI extends Requirement {
+    isCreatedManually: boolean;
+
+    constructor(input: Requirement, componentUniqueId: string) {
+        super(input);
+        this.isCreatedManually = input.ownerId === componentUniqueId;
+    }
+}
+class CapabilityUI extends Capability {
+    isCreatedManually: boolean;
+
+    constructor(input: Capability, componentUniqueId: string) {
+        super(input);
+        this.isCreatedManually = input.ownerId === componentUniqueId;
+    }
+}
+
 interface IReqAndCapabilitiesViewModelScope extends IWorkspaceViewModelScope {
     requirementsTableHeadersList:Array<any>;
+    editableRequirementsTableHeadersList: Array<any>;
     capabilitiesTableHeadersList:Array<any>;
+    editableCapabilitiesTableHeadersList: Array<any>;
     capabilityPropertiesTableHeadersList:Array<any>;
     requirementsSortTableDefined:SortTableDefined;
     capabilitiesSortTableDefined:SortTableDefined;
     propertiesSortTableDefined:SortTableDefined;
-    requirements:Array<Requirement>;
-    capabilities:Array<Capability>;
+    requirements: Array<RequirementUI>;
+    filteredRequirementsList: Array<RequirementUI>;
+    capabilities: Array<CapabilityUI>;
+    filteredCapabilitiesList: Array<CapabilityUI>;
     mode:string;
     filteredProperties:Array<Array<PropertyModel>>;
     searchText:string;
+    isEditable: boolean;
+    modalInstance: ComponentRef<ModalComponent>;
+    filter: {txt: string; show: boolean};
 
     sort(sortBy:string, sortByTableDefined:SortTableDefined):void;
+    sortByIsCreatedManually(arrToSort: Array<RequirementUI|CapabilityUI>): Array<any>;
     updateProperty(property:PropertyModel, indexInFilteredProperties:number):void;
     allCapabilitiesSelected(selected:boolean):void;
+    onAddBtnClicked(): void;
+    onEditRequirement(req: RequirementUI): void;
+    onEditCapability(cap: CapabilityUI): void;
+    onDeleteReq(event, req: RequirementUI): void;
+    onDeleteCap(event, cap: CapabilityUI): void;
+    onFilter(): void;
+    isListEmpty(): boolean;
+    onSwitchTab(): void;
+    onSearchIconClick(): void;
+    cutToscaTypePrefix(valToCut: string, textToStartCut: string): string;
+    isReadonly(): boolean;
 }
 
 export class ReqAndCapabilitiesViewModel {
@@ -58,33 +107,37 @@ export class ReqAndCapabilitiesViewModel {
         '$scope',
         '$filter',
         'ModalsHandler',
-        'ComponentServiceNg2'
+        'ComponentServiceNg2',
+        'ToscaTypesServiceNg2',
+        'ModalServiceNg2',
+        'ModalServiceSdcUI'
     ];
 
 
     constructor(private $scope:IReqAndCapabilitiesViewModelScope,
                 private $filter:ng.IFilterService,
                 private ModalsHandler:ModalsHandler,
-                private ComponentServiceNg2: ComponentServiceNg2) {
+                private ComponentServiceNg2: ComponentServiceNg2,
+                private ToscaTypesServiceNg2: ToscaTypesServiceNg2,
+                private ModalServiceNg2: ModalService,
+                private ModalServiceSdcUI: ModalServiceSdcUI) {
 
         this.initCapabilitiesAndRequirements();
+        this.fetchCapabilitiesRelatedData();
     }
 
     private initCapabilitiesAndRequirements = (): void => {
 
-        if(!this.$scope.component.capabilities || !this.$scope.component.requirements) {
-            this.$scope.isLoading = true;
-            this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response:ComponentGenericResponse) => {
-                this.$scope.component.capabilities = response.capabilities;
-                this.$scope.component.requirements = response.requirements;
-                this.initScope();
-                this.$scope.isLoading = false;
-            }, () => {
-                this.$scope.isLoading = false;
-            });
-        } else {
+        this.$scope.isEditable = this.getIsEditableByComponentType();
+        this.$scope.isLoading = true;
+        this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response: ComponentGenericResponse) => {
+            this.$scope.component.capabilities = response.capabilities;
+            this.$scope.component.requirements = response.requirements;
             this.initScope();
-        }
+            this.$scope.isLoading = false;
+        }, () => {
+            this.$scope.isLoading = false;
+        });
 
     }
 
@@ -98,15 +151,18 @@ export class ReqAndCapabilitiesViewModel {
         });
     };
 
-    private initScope = ():void => {
-
+    private initScope = (currentMode = 'requirements'): void => {
+        this.$scope.isReadonly = (): boolean => {
+            return this.$scope.isViewMode() || !this.$scope.isDesigner();
+        };
+        this.$scope.filter = {txt: '', show: false};
         this.$scope.requirementsSortTableDefined = {
             reverse: false,
-            sortByField: 'name'
+            sortByField: this.$scope.isEditable ? 'other' : 'name'
         };
         this.$scope.capabilitiesSortTableDefined = {
             reverse: false,
-            sortByField: 'name'
+            sortByField: this.$scope.isEditable ? 'other' : 'name'
         };
         this.$scope.propertiesSortTableDefined = {
             reverse: false,
@@ -129,6 +185,22 @@ export class ReqAndCapabilitiesViewModel {
             {title: 'Valid Source', property: ''},
             {title: 'Occurrences', property: ''}
         ];
+        this.$scope.editableRequirementsTableHeadersList = [
+            {title: 'Name', property: 'name'},
+            {title: 'Capability', property: 'capability'},
+            {title: 'Node', property: 'node'},
+            {title: 'Relationship', property: 'relationship'},
+            {title: 'Occurrences', property: 'occurrences'},
+            {title: '●●●', property: 'other'}
+        ];
+        this.$scope.editableCapabilitiesTableHeadersList = [
+            {title: 'Name', property: 'name'},
+            {title: 'Type', property: 'type'},
+            {title: 'Description', property: 'description'},
+            {title: 'Valid Sources', property: 'valid-sources'},
+            {title: 'Occurrences', property: 'occurrences'},
+            {title: '●●●', property: 'other'}
+        ];
         this.$scope.capabilityPropertiesTableHeadersList = [
             {title: 'Name', property: 'name'},
             {title: 'Type', property: 'type'},
@@ -137,17 +209,26 @@ export class ReqAndCapabilitiesViewModel {
         ];
         this.$scope.filteredProperties = [];
 
-        this.$scope.mode = 'requirements';
+        this.$scope.mode = currentMode;
         this.$scope.requirements = [];
         _.forEach(this.$scope.component.requirements, (req:Array<Requirement>, capName)=> {
-            this.$scope.requirements = this.$scope.requirements.concat(req);
+            let reqUIList: Array<RequirementUI> = _.map(req, reqObj => new RequirementUI(reqObj, this.$scope.component.uniqueId));
+            this.$scope.requirements = this.$scope.requirements.concat(reqUIList);
         });
+        this.$scope.filteredRequirementsList = this.$scope.requirements;
 
         this.$scope.capabilities = [];
         _.forEach(this.$scope.component.capabilities, (cap:Array<Capability>, capName)=> {
-            this.$scope.capabilities = this.$scope.capabilities.concat(cap);
+            let capUIList: Array<CapabilityUI> = _.map(cap, capObj => new CapabilityUI(capObj, this.$scope.component.uniqueId));
+            this.$scope.capabilities = this.$scope.capabilities.concat(capUIList);
         });
 
+        this.$scope.sortByIsCreatedManually = (arrToSort: Array<RequirementUI|CapabilityUI>): Array<any> => {
+            return arrToSort.sort((elem1: RequirementUI|CapabilityUI, elem2: RequirementUI|CapabilityUI) => +elem2.isCreatedManually - (+elem1.isCreatedManually));
+        };
+        this.$scope.filteredCapabilitiesList = this.$scope.sortByIsCreatedManually(this.$scope.capabilities);
+        this.$scope.filteredRequirementsList = this.$scope.sortByIsCreatedManually(this.$scope.requirements);
+
         this.$scope.sort = (sortBy:string, sortByTableDefined:SortTableDefined):void => {
             sortByTableDefined.reverse = (sortByTableDefined.sortByField === sortBy) ? !sortByTableDefined.reverse : false;
             sortByTableDefined.sortByField = sortBy;
@@ -162,6 +243,226 @@ export class ReqAndCapabilitiesViewModel {
                 cap.selected = selected;
             });
         };
+        this.$scope.onAddBtnClicked = (): void => {
+            switch (this.$scope.mode) {
+                case 'requirements':
+                    this.openRequirementsModal();
+                    break;
+                case 'capabilities':
+                    this.openCapabilitiesModal();
+                    break;
+            }
+        };
+        this.$scope.onEditRequirement = (req: RequirementUI): void => {
+            this.openRequirementsModal(req);
+        };
+        this.$scope.onEditCapability = (cap: CapabilityUI): void => {
+            this.openCapabilitiesModal(cap);
+        };
+        this.$scope.onDeleteReq = (event: Event, req: RequirementUI): void => {
+            event.stopPropagation();
+            this.ModalServiceSdcUI.openAlertModal('Delete Requirement',
+                `Are you sure you want to delete requirement: ${req.name}?`, 'OK', () => this.deleteRequirement(req), 'Cancel');
+        };
+        this.$scope.onDeleteCap = (event: Event, cap: CapabilityUI): void => {
+            event.stopPropagation();
+            this.ModalServiceSdcUI.openAlertModal('Delete Capability',
+                `Are you sure you want to delete capability: ${cap.name}?`, 'OK', () => this.deleteCapability(cap), 'Cancel');
+        };
+        this.$scope.onSearchIconClick = (): void => {
+            this.$scope.filter.show = !!this.$scope.filter.txt || !this.$scope.filter.show;
+        };
+        this.$scope.onFilter = (): void => {
+            switch (this.$scope.mode) {
+                case 'requirements':
+                    this.$scope.filteredRequirementsList = _.filter(this.$scope.requirements, req => req.name.includes(this.$scope.filter.txt));
+                    break;
+                case 'capabilities':
+                    this.$scope.filteredCapabilitiesList = _.filter(this.$scope.capabilities, cap => cap.name.includes(this.$scope.filter.txt));
+                    break;
+            }
+        };
+        this.$scope.isListEmpty = (): boolean => {
+            switch (this.$scope.mode) {
+                case 'requirements':
+                    return this.$scope.requirements.length === 0;
+                case 'capabilities':
+                    return this.$scope.capabilities.length === 0;
+            }
+        };
+        this.$scope.onSwitchTab = (): void => {
+            this.$scope.mode = this.$scope.mode === 'requirements' ? 'capabilities' : 'requirements';
+            this.$scope.filter.txt = '';
+            this.$scope.filter.show = false;
+            this.$scope.filteredRequirementsList = this.$scope.requirements;
+            this.$scope.filteredCapabilitiesList = this.$scope.capabilities;
+        };
+        this.$scope.cutToscaTypePrefix = (valToCut: string, textToStartCut: string): string => {
+            let index = valToCut.indexOf(textToStartCut);
+            return index !== -1 ? valToCut.substr(index + textToStartCut.length) : valToCut;
+        };
+    };
+
+    private getIsEditableByComponentType() {
+        if (this.$scope.componentType === ComponentType.SERVICE) {
+            return true;
+        }
+        if (this.$scope.component.isResource()) {
+            let componentAsResource: Resource = <Resource>this.$scope.component;
+            return componentAsResource.resourceType === ResourceType.VF ||
+                componentAsResource.resourceType === ResourceType.PNF;
+        }
+        return false;
+    };
+
+    private fetchCapabilitiesRelatedData() {
+        if (this.$scope.isEditable) {
+            this.$scope.capabilityTypesList = [];
+            this.ToscaTypesServiceNg2.fetchCapabilityTypes().subscribe((result: CapabilityTypesMap) => {
+                _.forEach(result, capabilityType => this.$scope.capabilityTypesList.push(capabilityType));
+            });
+            this.$scope.nodeTypesList = [];
+            this.ToscaTypesServiceNg2.fetchNodeTypes().subscribe((result: NodeTypesMap) => {
+                _.forEach(result, nodeType => this.$scope.nodeTypesList.push(nodeType));
+            });
+            this.$scope.relationshipTypesList = [];
+            this.ToscaTypesServiceNg2.fetchRelationshipTypes().subscribe((result: RelationshipTypesMap) => {
+                _.forEach(result, relshipType => this.$scope.relationshipTypesList.push(relshipType));
+            });
+        }
+    }
+
+    private openRequirementsModal(req?: RequirementUI) {
+        let modalConfig: IModalConfig = {
+            size: 'md',
+            title: (req ? 'Update' : 'Add') + ' Requirement',
+            type: 'custom',
+            buttons: [
+                {
+                    id: 'saveButton',
+                    text: (req ? 'Update' : 'Create'),
+                    size: "'x-small'",
+                    callback: () => this.createOrUpdateRequirement(),
+                    closeModal: true
+                },
+                {text: "Cancel", size: "'x-small'", closeModal: true}]
+        };
+        let modalInputs = {
+            requirement: req,
+            relationshipTypesList: this.$scope.relationshipTypesList,
+            nodeTypesList: this.$scope.nodeTypesList,
+            capabilityTypesList: this.$scope.capabilityTypesList,
+            isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(),
+            validityChangedCallback: this.getDisabled
+        };
+
+        this.ModalServiceSdcUI.openCustomModal(modalConfig, RequirementsEditorComponent, {input: modalInputs});
+    }
+
+    private openCapabilitiesModal(cap?: CapabilityUI) {
+        let modalConfig: IModalConfig = {
+            size: 'md',
+            title: (cap ? 'Update' : 'Add') + ' Capability',
+            type: 'custom',
+            buttons: [
+                {
+                    id: 'saveButton',
+                    text: (cap ? 'Update' : 'Create'),
+                    size: "'x-small'",
+                    callback: () => this.createOrUpdateCapability(),
+                    closeModal: true
+                },
+                {text: "Cancel", size: "'x-small'", closeModal: true}]
+        };
+        let modalInputs = {
+            capability: cap,
+            capabilityTypesList: this.$scope.capabilityTypesList,
+            isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(),
+            validityChangedCallback: this.getDisabled
+        };
+
+        this.ModalServiceSdcUI.openCustomModal(modalConfig, CapabilitiesEditorComponent, {input: modalInputs});
+    }
+
+    getDisabled = (shouldEnable: boolean): void => {
+        let saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton');
+        saveButton.disabled = this.$scope.isViewMode() || !this.$scope.isDesigner() || !shouldEnable;
+    };
+
+    private createOrUpdateRequirement() {
+        let requirement = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.requirementData;
+        this.$scope.isLoading = true;
+        if (!requirement.uniqueId) {
+            this.ComponentServiceNg2.createRequirement(this.$scope.component, requirement).subscribe(result => {
+                this.$scope.requirements.unshift(new RequirementUI(result[0], this.$scope.component.uniqueId));
+                this.$scope.isLoading = false;
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }
+        else {
+            this.ComponentServiceNg2.updateRequirement(this.$scope.component, requirement).subscribe(result => {
+                let index = this.$scope.requirements.findIndex(req => result[0].uniqueId === req.uniqueId);
+                this.$scope.requirements[index] = new RequirementUI(result[0], this.$scope.component.uniqueId);
+                this.$scope.isLoading = false;
+                this.$scope.$apply();
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }
+    }
+
+    private createOrUpdateCapability() {
+        let capability = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.capabilityData;
+        this.$scope.isLoading = true;
+        if (!capability.uniqueId) {
+            this.ComponentServiceNg2.createCapability(this.$scope.component, capability).subscribe(result => {
+                this.$scope.capabilities.unshift(new CapabilityUI(result[0], this.$scope.component.uniqueId));
+                this.$scope.isLoading = false;
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }
+        else {
+            this.ComponentServiceNg2.updateCapability(this.$scope.component, capability).subscribe(result => {
+                let index = this.$scope.capabilities.findIndex(cap => result[0].uniqueId === cap.uniqueId);
+                this.$scope.capabilities[index] = new CapabilityUI(result[0], this.$scope.component.uniqueId);
+                this.$scope.isLoading = false;
+                this.$scope.$apply();
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }
+    }
+
+    private deleteRequirement(req) {
+        this.$scope.isLoading = true;
+        this.ComponentServiceNg2.deleteRequirement(this.$scope.component, req.uniqueId).subscribe(() => {
+            this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => {
+                this.$scope.component.requirements = response.requirements;
+                this.initScope('requirements');
+                this.$scope.isLoading = false;
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }, () => {
+            this.$scope.isLoading = false;
+        });
+    }
+
+    private deleteCapability(cap) {
+        this.$scope.isLoading = true;
+        this.ComponentServiceNg2.deleteCapability(this.$scope.component, cap.uniqueId).subscribe(() => {
+            this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => {
+                this.$scope.component.capabilities = response.capabilities;
+                this.initScope('capabilities');
+                this.$scope.isLoading = false;
+            }, () => {
+                this.$scope.isLoading = false;
+            });
+        }, () => {
+            this.$scope.isLoading = false;
+        });
     }
 }
 
index 9b52fad..fa6623f 100644 (file)
             }
         }
     }
-    .expand-collapse-buttons{
+  .add-button {
+    color: @main_color_a;
+  }
+  .add-button, .expand-collapse-buttons {
         float: right;
-        width: 44px;
         margin-left: 11px;
         margin-top: 10px;
-        span{
-            vertical-align: bottom;
+    &, span {
             .hand;
         }
     }
             white-space: nowrap;
         }
 
+      .editable-table-data {
+        max-height: 430px;
+      }
+
+      .data-row {
+        &:not(.editable-row) {
+          background: @tlv_color_t;
+          color: @main_color_n;
+        }
+        &.editable-row {
+          cursor: pointer;
+        }
+        .sprite-new.delete-icon {
+          visibility: hidden;
+        }
+        &:hover {
+          .sprite-new.delete-icon {
+            visibility: visible;
+          }
+        }
+      }
+
         &.requirements-table{
             border-top: 4px solid @main_color_a;
             .flex-item:nth-child(1) {
     }
 
 }
+
+.workspace-req-and-cap-editable {
+  .tabs-header {
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 1px solid @main_color_o;
+    .req-and-cap-tabs {
+      display: flex;
+      .tab {
+        font-family: @font-opensans-regular;
+        font-size: 22px;
+        padding: 5px;
+        .hand;
+        &:first-of-type {
+          margin-right: 35px;
+        }
+        &.selected {
+          color: @main_color_a;
+          border-bottom: 2px solid @main_color_a;
+        }
+      }
+    }
+    .buttons-in-right {
+      display: flex;
+      .search {
+        display: flex;
+        height: min-content;
+        margin-top: 10px;
+        padding-right: 11px;
+        border-right: 1px solid @main_color_o;
+        #search-box {
+          border: none;
+          border-bottom: 1px solid @main_color_o;
+          text-indent: 10px;
+          &:focus {
+            outline: none;
+          }
+        }
+        .search-icon-container {
+          margin-top: 3px;
+          padding-top: 4px;
+        }
+
+      }
+      .add-button-icon-and-label {
+        font-size: 14px;
+        margin-left: 11px;
+        margin-top: 10px;
+        padding-top: 5px;
+        /deep/ svg-icon {
+          vertical-align: bottom;
+        }
+        &:hover {
+          &:not(.disabled) {
+            cursor: pointer;
+            color: @sdcui_color_light-blue;
+          }
+        }
+      }
+    }
+  }
+  .add-button-icon-and-label {
+    .icon-label-txt {
+      text-transform: uppercase;
+      font-family: @font-opensans-medium;
+      color: @main_color_a;
+      &:hover {
+        &:not(.disabled) {
+          color: @sdcui_color_light-blue;
+        }
+      }
+    }
+  }
+  .empty-list-container {
+    width: 100%;
+    display: flex;
+    justify-content: center;
+
+    .empty-list-add-btn {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      border: 1px solid @main_color_o;
+      margin-top: 50px;
+      height: 229px;
+      width: 480px;
+      &.disabled {
+        pointer-events: none;
+      }
+      &:hover {
+        &:not(.disabled) {
+          border: 1px solid @main_color_a;
+          cursor: pointer;
+        }
+      }
+      .icon-label-txt {
+        margin-top: 15px;
+        font-size: 16px;
+      }
+    }
+  }
+  .table-container-flex .table .head .head-row {
+    text-align: left;
+    &.description {
+      flex: 2;
+    }
+    &.other {
+      flex: 0.25;
+      text-align: center;
+    }
+    &.occurrences {
+      flex: 0.75;
+    }
+  }
+  .data-row {
+    .ellipsis-text {
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+    &:not(.editable-row) {
+      background: @tlv_color_t;
+      cursor: default;
+      color: @main_color_n;
+    }
+    &.editable-row {
+      cursor: pointer;
+      .table-col-general:hover {
+        color: @main_color_b;
+      }
+    }
+    .description-col {
+      flex: 2;
+    }
+    .occurrences-col {
+      flex: 0.75;
+    }
+    .other-col {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex: 0.25;
+      .trash-icon {
+        visibility: hidden;
+      }
+    }
+    &:hover {
+      .trash-icon {
+        visibility: visible;
+      }
+    }
+    .multiline-ellipsis {
+      line-height: 1.5em;
+      padding: 1px 0 1px 0;
+      /deep/ .ellipsis-directive-more-less {
+        float: none;
+        margin-left: 5px;
+        color: @main_color_a;
+      }
+    }
+  }
+}
index 676a2d3..9429022 100644 (file)
  */
 'use strict';
 import * as _ from "lodash";
-import {IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions} from "app/models";
+import {
+    IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions,
+    RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel
+} from "app/models";
 import {
     WorkspaceMode, ComponentFactory, ChangeLifecycleStateHandler, Role, ComponentState, MenuItemGroup, MenuHandler,
     MenuItem, ModalsHandler, States, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ResourceType, PREVIOUS_CSAR_COMPONENT
@@ -78,6 +81,9 @@ export interface IWorkspaceViewModelScope extends ng.IScope {
     unsavedChanges:boolean;
     unsavedChangesCallback:Function;
     unsavedFile:boolean;
+    capabilityTypesList: Array<CapabilityTypeModel>;
+    relationshipTypesList: Array<RelationshipTypeModel>;
+    nodeTypesList: Array<NodeTypeModel>;
 
 
     startProgress(message:string):void;
index 41f543a..b81dadf 100644 (file)
     "SERVICE_CERTIFICATION_STATUS_TEXT": "Service {{serviceName}} was successfully certified",
     "SERVICE_AUTOMATED_UPGRADE_WITH_COMPONENTS_TO_UPGRADE":  "The following services reference <b>{{vspName}}</b>.<br/> One or more of the services were not yet upgraded with the most recently certified version of <b>{{vspName}}</b>.</br>Select services from the list to upgrade them with <b>{{vspName}} {{vspVersion}}</b>.",
     "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_LOCKED": "The listed services reference <b>{{vspName}}</b>.<br/> These services were not upgraded with the most recently certified version of <b>{{vspName}}</b>. Currently they are locked from being upgraded with <b>{{vspName}} {{vspVersion}}</b>",
-    "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of  the <b>{{vspName}} {{vspVersion}}</b>"
+  "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of  the <b>{{vspName}} {{vspVersion}}</b>",
+  "=========== REQUIREMENTS AND CAPABILITIES ===========": "",
+  "REQ_NAME": "Requirement Name",
+  "REQ_RELATED_CAPABILITY": "Related Capability",
+  "REQ_NODE": "Node",
+  "REQ_RELATIONSHIP": "Relationship",
+  "REQ_CAP_OCCURRENCES": "Occurrences",
+  "REQ_CAP_OCCURRENCES_UNBOUNDED": "UNBOUNDED",
+  "REQ_CAP_OCCURRENCES_MIN": "Min",
+  "REQ_CAP_OCCURRENCES_MAX": "Max",
+  "CAP_NAME": "Capability Name",
+  "CAP_TYPE": "Capability Type",
+  "CAP_DESCRIPTION": "Description",
+  "CAP_VALID_SOURCE": "Valid Sources"
 }
index 383a830..5883094 100644 (file)
 .plus-icon                            { background-position: -50px  -231px; width: 12px; height: 12px;}
 .plus-icon-hover                      { background-position: -100px -231px; width: 12px; height: 12px;}
 
+.plus-icon-circle {
+  background-position: -49px  -959px;
+  width: 20px;
+  height: 20px;
+}
+
 .delete-icon                            { background-position: -675px -231px; width: 11px; height: 13px;}
 .delete-icon-hover                      { background-position: -702px -231px; width: 11px; height: 13px;}