Implement Attributes/Outputs FE 61/117061/13
authorvasraz <vasyl.razinkov@est.tech>
Tue, 16 Feb 2021 17:37:57 +0000 (17:37 +0000)
committerChristophe Closset <christophe.closset@intl.att.com>
Wed, 17 Feb 2021 15:57:55 +0000 (15:57 +0000)
Change-Id: I014bb0ebc07f3fea4266a4f295172eadee546705
Signed-off-by: Vasyl Razinkov <vasyl.razinkov@est.tech>
Issue-ID: SDC-3448

70 files changed:
catalog-fe/sdc-frontend/chef-repo/cookbooks/sdc-catalog-fe/files/default/FE-workspace-configuration.yaml
catalog-ui/configurations/menu.js
catalog-ui/src/app/app.ts
catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.ts
catalog-ui/src/app/models.ts
catalog-ui/src/app/models/attributes-outputs/attribute-be-model.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/attribute-declare-api-model.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/attribute-fe-map.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/attribute-fe-model.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/attribute-output-detail.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/derived-fe-attribute.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/output-be-model.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/output-fe-model.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes-outputs/simple-flat-attribute.ts [new file with mode: 0644]
catalog-ui/src/app/models/attributes.ts
catalog-ui/src/app/models/data-type-properties.ts
catalog-ui/src/app/models/data-types.ts
catalog-ui/src/app/models/input-property-base.ts
catalog-ui/src/app/models/inputs.ts
catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts
catalog-ui/src/app/models/properties-inputs/input-fe-model.ts
catalog-ui/src/app/models/properties-inputs/property-be-model.ts
catalog-ui/src/app/models/properties-inputs/property-fe-model.ts
catalog-ui/src/app/models/properties.ts
catalog-ui/src/app/models/schema-attribute.ts
catalog-ui/src/app/models/schema-property.ts [moved from catalog-ui/src/app/models/aschema-property.ts with 100% similarity]
catalog-ui/src/app/modules/directive-module.ts
catalog-ui/src/app/ng2/app.module.ts
catalog-ui/src/app/ng2/components/logic/attributes-table/attribute-table.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.spec.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/attributes-table/pipes/filterChildAttributes.pipe.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.ts
catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/services/attributes.utils.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/services/hierarchy-nav.service.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/attributes-outputs/services/outputs.utils.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts
catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.component.ts
catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.ts
catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts [deleted file]
catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.module.ts
catalog-ui/src/app/ng2/services/attributes.service.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts
catalog-ui/src/app/ng2/services/component-services/component.service.ts
catalog-ui/src/app/ng2/services/component-services/service.service.ts
catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts
catalog-ui/src/app/ng2/services/data-type.service.ts
catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
catalog-ui/src/app/utils/common-utils.ts
catalog-ui/src/app/utils/constants.ts
catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes.component.html [moved from catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.html with 100% similarity]
catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes.component.less [moved from catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.less with 100% similarity]
catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes.component.ts [moved from catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.ts with 93% similarity]

index 33643d1..f05b28c 100644 (file)
 # in addition, they can also be disabled for specific roles.
 workspaceMenuConfiguration:
   VFC:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - text: Properties
-    action: onMenuItemPressed
-    state: workspace.properties
-  - text: Attributes
-    action: onMenuItemPressed
-    state: workspace.attributes
-  - text: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCap
-  - text: Activity Log
-    action: onMenuItemPressed
-    state: workspace.activity_log
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - text: Properties
+      action: onMenuItemPressed
+      state: workspace.properties
+    - text: Attributes
+      action: onMenuItemPressed
+      state: workspace.attributes
+    - text: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCap
+    - text: Activity Log
+      action: onMenuItemPressed
+      state: workspace.activity_log
   VL:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - text: Properties
-    action: onMenuItemPressed
-    state: workspace.properties
-  - text: Attributes
-    action: onMenuItemPressed
-    state: workspace.attributes
-  - text: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCap
-  - text: Activity Log
-    action: onMenuItemPressed
-    state: workspace.activity_log
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - text: Properties
+      action: onMenuItemPressed
+      state: workspace.properties
+    - text: Attributes
+      action: onMenuItemPressed
+      state: workspace.attributes
+    - text: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCap
+    - text: Activity Log
+      action: onMenuItemPressed
+      state: workspace.activity_log
   CP:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - text: Properties
-    action: onMenuItemPressed
-    state: workspace.properties
-  - text: Attributes
-    action: onMenuItemPressed
-    state: workspace.attributes
-  - text: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCap
-  - text: Activity Log
-    action: onMenuItemPressed
-    state: workspace.activity_log
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - text: Properties
+      action: onMenuItemPressed
+      state: workspace.properties
+    - text: Attributes
+      action: onMenuItemPressed
+      state: workspace.attributes
+    - text: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCap
+    - text: Activity Log
+      action: onMenuItemPressed
+      state: workspace.activity_log
   VF:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - 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: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCapEditable
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - 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: Attributes & Outputs
+      action: onMenuItemPressed
+      state: workspace.attributes_outputs
+    - text: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCapEditable
   PNF:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - 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: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCapEditable
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - 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: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCapEditable
   CR:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: Deployment Artifact
-    action: onMenuItemPressed
-    state: workspace.deployment_artifacts
-  - text: Information Artifact
-    action: onMenuItemPressed
-    state: workspace.information_artifacts
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - text: Composition
-    action: onMenuItemPressed
-    state: workspace.composition.details
-  - text: Activity Log
-    action: onMenuItemPressed
-    state: workspace.activity_log
-  - text: Properties Assignment
-    action: onMenuItemPressed
-    state: workspace.properties_assignment
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: Deployment Artifact
+      action: onMenuItemPressed
+      state: workspace.deployment_artifacts
+    - text: Information Artifact
+      action: onMenuItemPressed
+      state: workspace.information_artifacts
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - text: Composition
+      action: onMenuItemPressed
+      state: workspace.composition.details
+    - text: Activity Log
+      action: onMenuItemPressed
+      state: workspace.activity_log
+    - text: Properties Assignment
+      action: onMenuItemPressed
+      state: workspace.properties_assignment
   SERVICE:
-  - text: General
-    action: onMenuItemPressed
-    state: workspace.general
-  - text: TOSCA Artifacts
-    action: onMenuItemPressed
-    state: workspace.tosca_artifacts
-  - 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: 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: Outputs
-    action: onMenuItemPressed
-    state: workspace.outputs_assignment
-  - text: Req. & Capabilities
-    action: onMenuItemPressed
-    state: workspace.reqAndCapEditable
+    - text: General
+      action: onMenuItemPressed
+      state: workspace.general
+    - text: TOSCA Artifacts
+      action: onMenuItemPressed
+      state: workspace.tosca_artifacts
+    - 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: 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: Outputs
+      action: onMenuItemPressed
+      state: workspace.outputs_assignment
+    - text: Req. & Capabilities
+      action: onMenuItemPressed
+      state: workspace.reqAndCapEditable
index 4569b26..beecfd1 100644 (file)
@@ -261,6 +261,7 @@ const SDC_MENU_CONFIG = {
             {"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": "Attributes & Outputs", "action": "onMenuItemPressed", "state": "workspace.attributes_outputs"},
             {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"}
         ],
         "PNF": [
@@ -295,10 +296,11 @@ const SDC_MENU_CONFIG = {
             {"text": "Network Call Flow ", "action": "onMenuItemPressed", "state": "workspace.network_call_flow"},
             {"text": "Distribution","action": "onMenuItemPressed","state": "workspace.distribution","disabledRoles": ["ADMIN"]},
             {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"},
-            {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"}
+            {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"},
+            {"text": "Attributes & Outputs", "action": "onMenuItemPressed", "state": "workspace.attributes_outputs"}
         ]
     }
 
-}
+};
 
 module.exports = SDC_MENU_CONFIG;
index f2ad738..5cb4e8d 100644 (file)
@@ -30,11 +30,10 @@ import './modules/service-module';
 import './modules/view-model-module.ts';
 import {SdcUiCommon, SdcUiComponents, SdcUiServices} from 'onap-ui-angular';
 import {
-    AngularJSBridge,
-    CookieService,
-    DataTypesService,
-    EcompHeaderService,
-    LeftPaletteLoaderService
+  CookieService,
+  DataTypesService,
+  EcompHeaderService,
+  LeftPaletteLoaderService
 } from "./services";
 import {CacheService, CatalogService, HomeService} from "./services-ng2";
 import {AuthenticationService} from "app/ng2/services/authentication.service";
@@ -45,540 +44,559 @@ import {Component} from "./models/components/component";
 import {IUserProperties} from "./models/user";
 import {WorkspaceService} from "./ng2/pages/workspace/workspace.service";
 
-let moduleName:string = 'sdcApp';
-let viewModelsModuleName:string = 'Sdc.ViewModels';
-let directivesModuleName:string = 'Sdc.Directives';
-let servicesModuleName:string = 'Sdc.Services';
-let filtersModuleName:string = 'Sdc.Filters';
-let utilsModuleName:string = 'Sdc.Utils';
+let moduleName: string = 'sdcApp';
+let viewModelsModuleName: string = 'Sdc.ViewModels';
+let directivesModuleName: string = 'Sdc.Directives';
+let servicesModuleName: string = 'Sdc.Services';
+let filtersModuleName: string = 'Sdc.Filters';
+let utilsModuleName: string = 'Sdc.Utils';
 
 // Load configuration according to environment.
-declare var __ENV__:string;
-let sdcConfig:IAppConfigurtaion;
-let sdcMenu:IAppMenu;
-let pathPrefix:string = '';
+declare var __ENV__: string;
+let sdcConfig: IAppConfigurtaion;
+let sdcMenu: IAppMenu;
+let pathPrefix: string = '';
 if (__ENV__ === 'dev') {
-    sdcConfig = require('./../../configurations/dev.js');
+  sdcConfig = require('./../../configurations/dev.js');
 } else if (__ENV__ === 'prod') {
-    sdcConfig = require('./../../configurations/prod.js');
-    pathPrefix = 'sdc1/';
+  sdcConfig = require('./../../configurations/prod.js');
+  pathPrefix = 'sdc1/';
 } else {
-    console.log("ERROR: Environment configuration not found!");
+  console.log("ERROR: Environment configuration not found!");
 }
 sdcMenu = require('./../../configurations/menu.js');
 
-let dependentModules:Array<string> = [
-    'ui.router',
-    'ui.bootstrap',
-    'ui.bootstrap.tpls',
-    'ngDragDrop',
-    'ui-notification',
-    'ngResource',
-    'ngSanitize',
-    'naif.base64',
-    'base64',
-    'uuid4',
-    'checklist-model',
-    'angular.filter',
-    'pascalprecht.translate',
-    '720kb.tooltips',
-    'restangular',
-    'angular-clipboard',
-    'angularResizable',
-    'infinite-scroll',
-    viewModelsModuleName,
-    directivesModuleName,
-    servicesModuleName,
-    filtersModuleName,
-    utilsModuleName
+let dependentModules: Array<string> = [
+  'ui.router',
+  'ui.bootstrap',
+  'ui.bootstrap.tpls',
+  'ngDragDrop',
+  'ui-notification',
+  'ngResource',
+  'ngSanitize',
+  'naif.base64',
+  'base64',
+  'uuid4',
+  'checklist-model',
+  'angular.filter',
+  'pascalprecht.translate',
+  '720kb.tooltips',
+  'restangular',
+  'angular-clipboard',
+  'angularResizable',
+  'infinite-scroll',
+  viewModelsModuleName,
+  directivesModuleName,
+  servicesModuleName,
+  filtersModuleName,
+  utilsModuleName
 ];
 
 // ===================== Hosted applications section ====================
 // Define here new hosted apps
-let hostedApplications:Array<IHostedApplication> = [
-    {
-        "moduleName": "dcaeApp",
-        "navTitle": "DCAE",
-        "defaultState": 'dcae.app.home',
-        "state": {
-            "name": "dcae",
-            "url": "/dcae",
-            "relativeHtmlPath": 'dcae-app/dcae-app-view.html',
-            "controllerName": '.DcaeAppViewModel'
-        }
+let hostedApplications: Array<IHostedApplication> = [
+  {
+    "moduleName": "dcaeApp",
+    "navTitle": "DCAE",
+    "defaultState": 'dcae.app.home',
+    "state": {
+      "name": "dcae",
+      "url": "/dcae",
+      "relativeHtmlPath": 'dcae-app/dcae-app-view.html',
+      "controllerName": '.DcaeAppViewModel'
     }
+  }
 ];
 
 // Check if module exists (in case the javascript was not loaded).
-let isModuleExists = (moduleName:string):boolean => {
-    try {
-        angular.module(moduleName);
-        dependentModules.push(moduleName);
-        return true;
-    } catch (e) {
-        console.log('Module ' + moduleName + ' does not exists');
-        return false;
-    }
+let isModuleExists = (moduleName: string): boolean => {
+  try {
+    angular.module(moduleName);
+    dependentModules.push(moduleName);
+    return true;
+  } catch (e) {
+    console.log('Module ' + moduleName + ' does not exists');
+    return false;
+  }
 };
 
 // Check which hosted applications exists
-_.each(hostedApplications, (hostedApp)=> {
-    if (isModuleExists(hostedApp.moduleName)) {
-        hostedApp['exists'] = true;
-    }
+_.each(hostedApplications, (hostedApp) => {
+  if (isModuleExists(hostedApp.moduleName)) {
+    hostedApp['exists'] = true;
+  }
 });
 // ===================== Hosted applications section ====================
 
-export const ng1appModule:ng.IModule = angular.module(moduleName, dependentModules);
+export const ng1appModule: ng.IModule = angular.module(moduleName, dependentModules);
 
 ng1appModule.config([
-    '$stateProvider',
-    '$translateProvider',
-    '$urlRouterProvider',
-    '$httpProvider',
-    'tooltipsConfigProvider',
-    'NotificationProvider',
-    ($stateProvider:any,
-     $translateProvider:any,
-     $urlRouterProvider:ng.ui.IUrlRouterProvider,
-     $httpProvider:ng.IHttpProvider,
-     tooltipsConfigProvider:any,
-     NotificationProvider:any):void => {
-
-        NotificationProvider.setOptions({
-            delay: 5000,
-            startTop: 10,
-            startRight: 10,
-            closeOnClick: true,
-            verticalSpacing: 20,
-            horizontalSpacing: 20,
-            positionX: 'right',
-            positionY: 'top'
-        });
-        NotificationProvider.options.templateUrl = 'notification-custom-template.html';
-
-        $translateProvider.useStaticFilesLoader({
-            prefix: pathPrefix + 'assets/languages/',
-            langKey: '',
-            suffix: '.json?d=' + (new Date()).getTime()
+  '$stateProvider',
+  '$translateProvider',
+  '$urlRouterProvider',
+  '$httpProvider',
+  'tooltipsConfigProvider',
+  'NotificationProvider',
+  ($stateProvider: any,
+   $translateProvider: any,
+   $urlRouterProvider: ng.ui.IUrlRouterProvider,
+   $httpProvider: ng.IHttpProvider,
+   tooltipsConfigProvider: any,
+   NotificationProvider: any): void => {
+
+    NotificationProvider.setOptions({
+      delay: 5000,
+      startTop: 10,
+      startRight: 10,
+      closeOnClick: true,
+      verticalSpacing: 20,
+      horizontalSpacing: 20,
+      positionX: 'right',
+      positionY: 'top'
+    });
+    NotificationProvider.options.templateUrl = 'notification-custom-template.html';
+
+    $translateProvider.useStaticFilesLoader({
+      prefix: pathPrefix + 'assets/languages/',
+      langKey: '',
+      suffix: '.json?d=' + (new Date()).getTime()
+    });
+    $translateProvider.useSanitizeValueStrategy('escaped');
+    $translateProvider.preferredLanguage('en_US');
+
+    $httpProvider.interceptors.push('Sdc.Services.HeaderInterceptor');
+    $urlRouterProvider.otherwise('dashboard');
+
+    $stateProvider.state(
+        'dashboard', {
+          url: '/dashboard?show&folder&filter.term&filter.status&filter.distributed',
+          template: '<home-page></home-page>',
+          permissions: ['DESIGNER']
+        },
+    );
+
+
+    let componentsParam: Array<any> = ['$stateParams', 'HomeService', 'CatalogService', 'Sdc.Services.CacheService', ($stateParams: any, HomeService: HomeService, CatalogService: CatalogService, cacheService: CacheService) => {
+      if (cacheService.get('breadcrumbsComponentsState') === $stateParams.previousState) {
+        const breadcrumbsComponents = cacheService.get('breadcrumbsComponents');
+        if (breadcrumbsComponents) {
+          return breadcrumbsComponents;
+        }
+      } else {
+        let breadcrumbsComponentsObservable;
+        if ($stateParams.previousState === 'dashboard') {
+          breadcrumbsComponentsObservable = HomeService.getAllComponents(true);
+        } else if ($stateParams.previousState === 'catalog') {
+          breadcrumbsComponentsObservable = CatalogService.getCatalog();
+        } else {
+          cacheService.remove('breadcrumbsComponentsState');
+          cacheService.remove('breadcrumbsComponents');
+          return [];
+        }
+        breadcrumbsComponentsObservable.subscribe((components) => {
+          cacheService.set('breadcrumbsComponentsState', $stateParams.previousState);
+          cacheService.set('breadcrumbsComponents', components);
         });
-        $translateProvider.useSanitizeValueStrategy('escaped');
-        $translateProvider.preferredLanguage('en_US');
-
-        $httpProvider.interceptors.push('Sdc.Services.HeaderInterceptor');
-        $urlRouterProvider.otherwise('dashboard');
-
-        $stateProvider.state(
-            'dashboard', {
-                url: '/dashboard?show&folder&filter.term&filter.status&filter.distributed',
-                template: '<home-page></home-page>',
-                permissions: ['DESIGNER']
-            },
-
-        );
-
-
-        let componentsParam:Array<any> = ['$stateParams', 'HomeService', 'CatalogService', 'Sdc.Services.CacheService', ($stateParams:any, HomeService:HomeService, CatalogService:CatalogService, cacheService:CacheService) => {
-            if (cacheService.get('breadcrumbsComponentsState') === $stateParams.previousState) {
-                const breadcrumbsComponents = cacheService.get('breadcrumbsComponents');
-                if (breadcrumbsComponents) {
-                    return breadcrumbsComponents;
-                }
-            } else {
-                let breadcrumbsComponentsObservable;
-                if ($stateParams.previousState === 'dashboard') {
-                    breadcrumbsComponentsObservable = HomeService.getAllComponents(true);
-                } else if ($stateParams.previousState === 'catalog') {
-                    breadcrumbsComponentsObservable = CatalogService.getCatalog();
-                } else {
-                    cacheService.remove('breadcrumbsComponentsState');
-                    cacheService.remove('breadcrumbsComponents');
-                    return [];
-                }
-                breadcrumbsComponentsObservable.subscribe((components) => {
-                    cacheService.set('breadcrumbsComponentsState', $stateParams.previousState);
-                    cacheService.set('breadcrumbsComponents', components);
-                });
-                return breadcrumbsComponentsObservable;
-            }
-        }];
-
-        const oldWorkspaceController:Array<any> = ['$location', ($location:ng.ILocationService) => {
-            // redirect old /workspace/* urls to /catalog/workspace/* url
-            const newUrl = '/catalog' + $location.url();
-            console.log('old workspace path - redirecting to:', newUrl);
-            $location.url(newUrl);
-        }];
-
-        $stateProvider.state(
-            'workspace-old', {
-                url: '/workspace/:id/:type/*workspaceInnerPath',
-                controller: oldWorkspaceController
-            }
-        );
-
-        $stateProvider.state(
-            'workspace', {
-                url: '/:previousState/workspace/:id/:type/',
-                params: {'importedFile': null, 'componentCsar': null, 'resourceType': null, 'disableButtons': null},
-                templateUrl: './view-models/workspace/workspace-view.html',
-                controller: viewModelsModuleName + '.WorkspaceViewModel',
-                resolve: {
-                    injectComponent: ['$stateParams', 'ComponentFactory', 'workspaceService', 'Sdc.Services.CacheService', function ($stateParams, ComponentFactory:ComponentFactory, workspaceService:WorkspaceService, cacheService: CacheService) {
-
-                        if ($stateParams.id && $stateParams.id.length) { //need to check length in case ID is an empty string
-                            return ComponentFactory.getComponentWithMetadataFromServer($stateParams.type.toUpperCase(), $stateParams.id).then(
-                                (component:Component)=> {
-                                    if ($stateParams.componentCsar && component.isResource()){
-                                        if((<Resource>component).csarVersion != $stateParams.componentCsar.csarVersion) {
-                                            cacheService.set(PREVIOUS_CSAR_COMPONENT, angular.copy(component));
-                                        }
-                                        component = ComponentFactory.updateComponentFromCsar($stateParams.componentCsar, <Resource>component);
-                                    }
-                                    workspaceService.setComponentMetadata(component.componentMetadata);
-                                    return component;
-                                });
-                        } else if ($stateParams.componentCsar && $stateParams.componentCsar.csarUUID) {
-                            return $stateParams.componentCsar;
-                        } else {
-                            let emptyComponent = ComponentFactory.createEmptyComponent($stateParams.type.toUpperCase());
-                            if (emptyComponent.isResource() && $stateParams.resourceType) {
-                                // Set the resource type
-                                (<Resource>emptyComponent).resourceType = $stateParams.resourceType;
-                            }
-                            if ($stateParams.importedFile) {
-                                (<Resource>emptyComponent).importedFile = $stateParams.importedFile;
-                            }
-                            return emptyComponent;
+        return breadcrumbsComponentsObservable;
+      }
+    }];
+
+    const oldWorkspaceController: Array<any> = ['$location', ($location: ng.ILocationService) => {
+      // redirect old /workspace/* urls to /catalog/workspace/* url
+      const newUrl = '/catalog' + $location.url();
+      console.log('old workspace path - redirecting to:', newUrl);
+      $location.url(newUrl);
+    }];
+
+    $stateProvider.state(
+        'workspace-old', {
+          url: '/workspace/:id/:type/*workspaceInnerPath',
+          controller: oldWorkspaceController
+        }
+    );
+
+    $stateProvider.state(
+        'workspace', {
+          url: '/:previousState/workspace/:id/:type/',
+          params: {
+            'importedFile': null,
+            'componentCsar': null,
+            'resourceType': null,
+            'disableButtons': null
+          },
+          templateUrl: './view-models/workspace/workspace-view.html',
+          controller: viewModelsModuleName + '.WorkspaceViewModel',
+          resolve: {
+            injectComponent: ['$stateParams', 'ComponentFactory', 'workspaceService', 'Sdc.Services.CacheService', function ($stateParams, ComponentFactory: ComponentFactory, workspaceService: WorkspaceService, cacheService: CacheService) {
+
+              if ($stateParams.id && $stateParams.id.length) { //need to check length in case ID is an empty string
+                return ComponentFactory.getComponentWithMetadataFromServer($stateParams.type.toUpperCase(), $stateParams.id).then(
+                    (component: Component) => {
+                      if ($stateParams.componentCsar && component.isResource()) {
+                        if ((<Resource>component).csarVersion != $stateParams.componentCsar.csarVersion) {
+                          cacheService.set(PREVIOUS_CSAR_COMPONENT, angular.copy(component));
                         }
-                    }],
-                    components: componentsParam
+                        component = ComponentFactory.updateComponentFromCsar($stateParams.componentCsar, <Resource>component);
+                      }
+                      workspaceService.setComponentMetadata(component.componentMetadata);
+                      return component;
+                    });
+              } else if ($stateParams.componentCsar && $stateParams.componentCsar.csarUUID) {
+                return $stateParams.componentCsar;
+              } else {
+                let emptyComponent = ComponentFactory.createEmptyComponent($stateParams.type.toUpperCase());
+                if (emptyComponent.isResource() && $stateParams.resourceType) {
+                  // Set the resource type
+                  (<Resource>emptyComponent).resourceType = $stateParams.resourceType;
                 }
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_GENERAL, {
-                url: 'general',
-                parent: 'workspace',
-                controller: viewModelsModuleName + '.GeneralViewModel',
-                templateUrl: './view-models/workspace/tabs/general/general-view.html',
-                data: {unsavedChanges: false, bodyClass: 'general'}
-            }
-        );
-
-
-        $stateProvider.state(
-            States.WORKSPACE_INFORMATION_ARTIFACTS, {
-                url: 'information_artifacts',
-                parent: 'workspace',
-                template:'<information-artifact-page></information-artifact-page>'
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_TOSCA_ARTIFACTS, {
-                url: 'tosca_artifacts',
-                parent: 'workspace',
-                template:'<tosca-artifact-page></tosca-artifact-page>'
-            }
-        );
-
-
-        $stateProvider.state(
-            States.WORKSPACE_DEPLOYMENT_ARTIFACTS, {
-                url: 'deployment_artifacts',
-                parent: 'workspace',
-                template:'<deployment-artifact-page></deployment-artifact-page>'
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_PROPERTIES, {
-                url: 'properties',
-                parent: 'workspace',
-                controller: viewModelsModuleName + '.PropertiesViewModel',
-                templateUrl: './view-models/workspace/tabs/properties/properties-view.html',
-                data: {
-                    bodyClass: 'properties'
+                if ($stateParams.importedFile) {
+                  (<Resource>emptyComponent).importedFile = $stateParams.importedFile;
                 }
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_PROPERTIES_ASSIGNMENT, {
-                url: 'properties_assignment',
-                params: {'component': null},
-                template: '<properties-assignment></properties-assignment>',
-                parent: 'workspace',
-                resolve: {
-                    componentData: ['injectComponent', '$stateParams', function (injectComponent:Component, $stateParams) {
-                        //injectComponent.componentService = null; // this is for not passing the service so no one will use old api and start using new api
-                        $stateParams.component = injectComponent;
-                        return injectComponent;
-                    }],
-                },
-                data: {
-                    bodyClass: 'properties-assignment'
-                }
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_ATTRIBUTES, {
-                url: 'attributes',
-                parent: 'workspace',
-                template: '<attributes></attributes>',
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES, {
-                url: 'req_and_capabilities',
-                parent: 'workspace',
-                template: '<req-and-capabilities></req-and-capabilities>',
-                data: {
-                    bodyClass: 'attributes'
-                }
-            }
-        );
-        $stateProvider.state(
-            States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE, {
-                url: 'req_and_capabilities_editable',
-                parent: 'workspace',
-                template: '<req-and-capabilities></req-and-capabilities>',
-                data: {
-                    bodyClass: 'attributes'
-                }
-            }
-        );
-
-
-        $stateProvider.state(
-            States.WORKSPACE_MANAGEMENT_WORKFLOW, {
-                parent: 'workspace',
-                url: 'management_workflow',
-                templateUrl: './view-models/workspace/tabs/management-workflow/management-workflow-view.html',
-                controller: viewModelsModuleName + '.ManagementWorkflowViewModel'
-            }
-        );
-
-        $stateProvider.state(
-            States.WORKSPACE_NETWORK_CALL_FLOW, {
-                parent: 'workspace',
-                url: 'network_call_flow',
-                templateUrl: './view-models/workspace/tabs/network-call-flow/network-call-flow-view.html',
-                controller: viewModelsModuleName + '.NetworkCallFlowViewModel'
-            }
-        );
-
-
-        $stateProvider.state(
-            States.WORKSPACE_COMPOSITION, {
-                url: 'composition/',
-                params: {'component': null},
-                parent: 'workspace',
-                template: '<composition-page></composition-page>',
-                resolve: {
-                    componentData: ['injectComponent', '$stateParams', function (injectComponent:Component, $stateParams) {
-                        //injectComponent.componentService = null; // this is for not passing the service so no one will use old api and start using new api
-                        $stateParams.component = injectComponent;
-                        return injectComponent;
-                    }],
-                },
-                data: {
-                    bodyClass: 'composition'
-                }
-            }
-        );
+                return emptyComponent;
+              }
+            }],
+            components: componentsParam
+          }
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_GENERAL, {
+          url: 'general',
+          parent: 'workspace',
+          controller: viewModelsModuleName + '.GeneralViewModel',
+          templateUrl: './view-models/workspace/tabs/general/general-view.html',
+          data: {unsavedChanges: false, bodyClass: 'general'}
+        }
+    );
 
-        $stateProvider.state(
-            States.WORKSPACE_ACTIVITY_LOG, {
-                url: 'activity_log/',
-                parent: 'workspace',
-                template: '<activity-log></activity-log>',
-            }
 
-        );
+    $stateProvider.state(
+        States.WORKSPACE_INFORMATION_ARTIFACTS, {
+          url: 'information_artifacts',
+          parent: 'workspace',
+          template: '<information-artifact-page></information-artifact-page>'
+        }
+    );
 
-        $stateProvider.state(
-            States.WORKSPACE_DISTRIBUTION, {
-                url: 'distribution',
-                parent: 'workspace',
-                template: '<distribution></distribution>',
-            }
+    $stateProvider.state(
+        States.WORKSPACE_TOSCA_ARTIFACTS, {
+          url: 'tosca_artifacts',
+          parent: 'workspace',
+          template: '<tosca-artifact-page></tosca-artifact-page>'
+        }
+    );
 
-        );
 
-        $stateProvider.state(
-            States.WORKSPACE_DEPLOYMENT, {
-                url: 'deployment/',
-                parent: 'workspace',
-                template: '<deployment-page></deployment-page>',
+    $stateProvider.state(
+        States.WORKSPACE_DEPLOYMENT_ARTIFACTS, {
+          url: 'deployment_artifacts',
+          parent: 'workspace',
+          template: '<deployment-artifact-page></deployment-artifact-page>'
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_PROPERTIES, {
+          url: 'properties',
+          parent: 'workspace',
+          controller: viewModelsModuleName + '.PropertiesViewModel',
+          templateUrl: './view-models/workspace/tabs/properties/properties-view.html',
+          data: {
+            bodyClass: 'properties'
+          }
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_PROPERTIES_ASSIGNMENT, {
+          url: 'properties_assignment',
+          params: {'component': null},
+          template: '<properties-assignment></properties-assignment>',
+          parent: 'workspace',
+          resolve: {
+            componentData: ['injectComponent', '$stateParams', function (injectComponent: Component, $stateParams) {
+              //injectComponent.componentService = null; // this is for not passing the service so no one will use old api and start using new api
+              $stateParams.component = injectComponent;
+              return injectComponent;
+            }],
+          },
+          data: {
+            bodyClass: 'properties-assignment'
+          }
+        }
+    );
 
-            }
-        );
+    $stateProvider.state(
+        States.WORKSPACE_ATTRIBUTES, {
+          url: 'attributes',
+          parent: 'workspace',
+          template: '<attributes></attributes>',
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_ATTRIBUTES_OUTPUTS, {
+          url: 'attributes_outputs',
+          parent: 'workspace',
+          template: '<attributes-outputs></attributes-outputs>',
+          resolve: {
+            componentData: ['injectComponent', '$stateParams', function (injectComponent: Component, $stateParams) {
+              $stateParams.component = injectComponent;
+              return injectComponent;
+            }],
+          },
+          data: {
+            bodyClass: 'attributes-outputs'
+          }
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES, {
+          url: 'req_and_capabilities',
+          parent: 'workspace',
+          template: '<req-and-capabilities></req-and-capabilities>',
+          data: {
+            bodyClass: 'attributes'
+          }
+        }
+    );
+    $stateProvider.state(
+        States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE, {
+          url: 'req_and_capabilities_editable',
+          parent: 'workspace',
+          template: '<req-and-capabilities></req-and-capabilities>',
+          data: {
+            bodyClass: 'attributes'
+          }
+        }
+    );
 
-        $stateProvider.state(
-            'workspace.composition.details', {
-                url: 'details',
-                parent: 'workspace.composition',
-                resolve: {
-                    componentData: ['injectComponent', '$stateParams', function (injectComponent:Component, $stateParams) {
-                        $stateParams.component = injectComponent;
-                        return injectComponent;
-                    }],
-                }
 
-            }
-        );
+    $stateProvider.state(
+        States.WORKSPACE_MANAGEMENT_WORKFLOW, {
+          parent: 'workspace',
+          url: 'management_workflow',
+          templateUrl: './view-models/workspace/tabs/management-workflow/management-workflow-view.html',
+          controller: viewModelsModuleName + '.ManagementWorkflowViewModel'
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_NETWORK_CALL_FLOW, {
+          parent: 'workspace',
+          url: 'network_call_flow',
+          templateUrl: './view-models/workspace/tabs/network-call-flow/network-call-flow-view.html',
+          controller: viewModelsModuleName + '.NetworkCallFlowViewModel'
+        }
+    );
+
+
+    $stateProvider.state(
+        States.WORKSPACE_COMPOSITION, {
+          url: 'composition/',
+          params: {'component': null},
+          parent: 'workspace',
+          template: '<composition-page></composition-page>',
+          resolve: {
+            componentData: ['injectComponent', '$stateParams', function (injectComponent: Component, $stateParams) {
+              //injectComponent.componentService = null; // this is for not passing the service so no one will use old api and start using new api
+              $stateParams.component = injectComponent;
+              return injectComponent;
+            }],
+          },
+          data: {
+            bodyClass: 'composition'
+          }
+        }
+    );
 
-        $stateProvider.state(
-            'workspace.composition.properties', {
-                url: 'properties',
-                parent: 'workspace.composition'
-            }
-        );
+    $stateProvider.state(
+        States.WORKSPACE_ACTIVITY_LOG, {
+          url: 'activity_log/',
+          parent: 'workspace',
+          template: '<activity-log></activity-log>',
+        }
+    );
 
-        $stateProvider.state(
-            'workspace.composition.artifacts', {
-                url: 'artifacts',
-                parent: 'workspace.composition'
+    $stateProvider.state(
+        States.WORKSPACE_DISTRIBUTION, {
+          url: 'distribution',
+          parent: 'workspace',
+          template: '<distribution></distribution>',
+        }
+    );
 
-            }
-        );
+    $stateProvider.state(
+        States.WORKSPACE_DEPLOYMENT, {
+          url: 'deployment/',
+          parent: 'workspace',
+          template: '<deployment-page></deployment-page>',
 
-        $stateProvider.state(
-            'workspace.composition.relations', {
-                url: 'relations',
-                parent: 'workspace.composition'
-            }
-        );
+        }
+    );
+
+    $stateProvider.state(
+        'workspace.composition.details', {
+          url: 'details',
+          parent: 'workspace.composition',
+          resolve: {
+            componentData: ['injectComponent', '$stateParams', function (injectComponent: Component, $stateParams) {
+              $stateParams.component = injectComponent;
+              return injectComponent;
+            }],
+          }
 
-        $stateProvider.state(
-            'workspace.composition.structure', {
-                url: 'structure',
-                parent: 'workspace.composition'
-            }
-        );
-        $stateProvider.state(
-            'workspace.composition.lifecycle', {
-                url: 'lifecycle',
-                parent: 'workspace.composition'
-            }
-        );
+        }
+    );
 
-        $stateProvider.state(
-            'workspace.composition.api', {
-                url: 'api',
-                parent: 'workspace.composition'
-            }
-        );
-        $stateProvider.state(
-            'workspace.composition.deployment', {
-                url: 'deployment',
-                parent: 'workspace.composition'
-            }
-        );
+    $stateProvider.state(
+        'workspace.composition.properties', {
+          url: 'properties',
+          parent: 'workspace.composition'
+        }
+    );
 
-        $stateProvider.state(
-            States.WORKSPACE_INTERFACE_OPERATION, {
-                url: 'interface_operation',
-                parent: 'workspace',
-                controller: viewModelsModuleName + '.InterfaceOperationViewModel',
-                templateUrl: './view-models/workspace/tabs/interface-operation/interface-operation-view.html',
-                data: {
-                    bodyClass: 'interface_operation'
-                }
-            }
-        );
+    $stateProvider.state(
+        'workspace.composition.artifacts', {
+          url: 'artifacts',
+          parent: 'workspace.composition'
 
-        $stateProvider.state(
-            'workspace.plugins', {
-                url: 'plugins/*path',
-                parent: 'workspace',
-                template: '<plugin-context-view></plugin-context-view>',
-                resolve: {
-                    componentData: ['injectComponent', '$stateParams', function (injectComponent:Component, $stateParams) {
-                        $stateParams.component = injectComponent;
-                        return injectComponent;
-                    }],
-                }
+        }
+    );
 
-            }
-        );
+    $stateProvider.state(
+        'workspace.composition.relations', {
+          url: 'relations',
+          parent: 'workspace.composition'
+        }
+    );
 
-        $stateProvider.state(
-            'adminDashboard', {
-                url: '/adminDashboard',
-                templateUrl: './view-models/admin-dashboard/admin-dashboard-view.html',
-                controller: viewModelsModuleName + '.AdminDashboardViewModel',
-                permissions: ['ADMIN']
-            }
-        );
+    $stateProvider.state(
+        'workspace.composition.structure', {
+          url: 'structure',
+          parent: 'workspace.composition'
+        }
+    );
+    $stateProvider.state(
+        'workspace.composition.lifecycle', {
+          url: 'lifecycle',
+          parent: 'workspace.composition'
+        }
+    );
 
-        $stateProvider.state(
-            'onboardVendor', {
-                url: '/onboardVendor',
-                templateUrl: './view-models/onboard-vendor/onboard-vendor-view.html',
-                controller: viewModelsModuleName + '.OnboardVendorViewModel'
-            }
-        );
+    $stateProvider.state(
+        'workspace.composition.api', {
+          url: 'api',
+          parent: 'workspace.composition'
+        }
+    );
+    $stateProvider.state(
+        'workspace.composition.deployment', {
+          url: 'deployment',
+          parent: 'workspace.composition'
+        }
+    );
+
+    $stateProvider.state(
+        States.WORKSPACE_INTERFACE_OPERATION, {
+          url: 'interface_operation',
+          parent: 'workspace',
+          controller: viewModelsModuleName + '.InterfaceOperationViewModel',
+          templateUrl: './view-models/workspace/tabs/interface-operation/interface-operation-view.html',
+          data: {
+            bodyClass: 'interface_operation'
+          }
+        }
+    );
+
+    $stateProvider.state(
+        'workspace.plugins', {
+          url: 'plugins/*path',
+          parent: 'workspace',
+          template: '<plugin-context-view></plugin-context-view>',
+          resolve: {
+            componentData: ['injectComponent', '$stateParams', function (injectComponent: Component, $stateParams) {
+              $stateParams.component = injectComponent;
+              return injectComponent;
+            }],
+          }
 
-        $stateProvider.state(
-            'plugins', {
-                url: '/plugins/*path',
-                template: '<plugin-tab-view></plugin-tab-view>'
-            }
-        );
+        }
+    );
+
+    $stateProvider.state(
+        'adminDashboard', {
+          url: '/adminDashboard',
+          templateUrl: './view-models/admin-dashboard/admin-dashboard-view.html',
+          controller: viewModelsModuleName + '.AdminDashboardViewModel',
+          permissions: ['ADMIN']
+        }
+    );
 
-        // Build the states for all hosted apps dynamically
-        _.each(hostedApplications, (hostedApp)=> {
-            if (hostedApp.exists) {
-                $stateProvider.state(
-                    hostedApp.state.name, {
-                        url: hostedApp.state.url,
-                        templateUrl: './view-models/dcae-app/dcae-app-view.html',
-                        controller: viewModelsModuleName + hostedApp.state.controllerName
-                    }
-                );
-            }
-        });
+    $stateProvider.state(
+        'onboardVendor', {
+          url: '/onboardVendor',
+          templateUrl: './view-models/onboard-vendor/onboard-vendor-view.html',
+          controller: viewModelsModuleName + '.OnboardVendorViewModel'
+        }
+    );
 
-        $stateProvider.state(
-            'catalog', {
-                url: '/catalog?filter.components&filter.resourceSubTypes&filter.categories&filter.statuses&filter.order&filter.term&filter.active',
-                template: '<catalog-page></catalog-page>',
-                resolve: {
-                    auth: ["$q", "AuthenticationServiceNg2", ($q:any, authService:AuthenticationService) => {
-                        let userInfo:IUserProperties = authService.getLoggedinUser();
-                        if (userInfo) {
-                            return $q.when(userInfo);
-                        } else {
-                            return $q.reject({authenticated: false});
-                        }
-                    }]
-                }
-            }
-        );
+    $stateProvider.state(
+        'plugins', {
+          url: '/plugins/*path',
+          template: '<plugin-tab-view></plugin-tab-view>'
+        }
+    );
+
+    // Build the states for all hosted apps dynamically
+    _.each(hostedApplications, (hostedApp) => {
+      if (hostedApp.exists) {
+        $stateProvider.state(
+            hostedApp.state.name, {
+              url: hostedApp.state.url,
+              templateUrl: './view-models/dcae-app/dcae-app-view.html',
+              controller: viewModelsModuleName + hostedApp.state.controllerName
+            }
+        );
+      }
+    });
+
+    $stateProvider.state(
+        'catalog', {
+          url: '/catalog?filter.components&filter.resourceSubTypes&filter.categories&filter.statuses&filter.order&filter.term&filter.active',
+          template: '<catalog-page></catalog-page>',
+          resolve: {
+            auth: ["$q", "AuthenticationServiceNg2", ($q: any, authService: AuthenticationService) => {
+              let userInfo: IUserProperties = authService.getLoggedinUser();
+              if (userInfo) {
+                return $q.when(userInfo);
+              } else {
+                return $q.reject({authenticated: false});
+              }
+            }]
+          }
+        }
+    );
 
-        $stateProvider.state(
-            'error-403', {
-                url: '/error-403',
-                templateUrl: "./view-models/modals/error-modal/error-403-view.html",
-                controller: viewModelsModuleName + '.ErrorViewModel'
-            }
-        );
+    $stateProvider.state(
+        'error-403', {
+          url: '/error-403',
+          templateUrl: "./view-models/modals/error-modal/error-403-view.html",
+          controller: viewModelsModuleName + '.ErrorViewModel'
+        }
+    );
 
-        tooltipsConfigProvider.options({
-            side: 'bottom',
-            delay: '600',
-            class: 'tooltip-custom',
-            lazy: 0,
-            try: 0
-        });
+    tooltipsConfigProvider.options({
+      side: 'bottom',
+      delay: '600',
+      class: 'tooltip-custom',
+      lazy: 0,
+      try: 0
+    });
 
-    }
+  }
 ]);
 
 ng1appModule.value('ValidationPattern', /^[\s\w\&_.:-]{1,1024}$/);
@@ -606,210 +624,209 @@ ng1appModule.constant('sdcConfig', sdcConfig);
 ng1appModule.constant('sdcMenu', sdcMenu);
 
 ng1appModule.run([
-    '$http',
-    'Sdc.Services.CacheService',
-    'Sdc.Services.CookieService',
-    'AuthenticationServiceNg2',
-    '$state',
-    '$rootScope',
-    '$location',
-    'sdcMenu',
-    'Sdc.Services.EcompHeaderService',
-    'LeftPaletteLoaderService',
-    'Sdc.Services.DataTypesService',
-    'AngularJSBridge',
-    '$templateCache',
-    'ModalServiceSdcUI',
-    ($http:ng.IHttpService,
-     cacheService:CacheService,
-     cookieService:CookieService,
-     authService:AuthenticationService,
-     $state:ng.ui.IStateService,
-     $rootScope:ng.IRootScopeService,
-     $location:ng.ILocationService,
-     sdcMenu:IAppMenu,
-     ecompHeaderService:EcompHeaderService,
-     LeftPaletteLoaderService:LeftPaletteLoaderService,
-     DataTypesService:DataTypesService,
-     AngularJSBridge,
-     $templateCache:ng.ITemplateCacheService,
-     ModalServiceSdcUI:SdcUiServices.ModalService):void => {
-        $templateCache.put('notification-custom-template.html', require('./view-models/shared/notification-custom-template.html'));
-        $templateCache.put('notification-custom-template.html', require('./view-models/shared/notification-custom-template.html'));
-
-        // Add hosted applications to sdcConfig
-        sdcConfig.hostedApplications = hostedApplications;
-
-        //handle http config
-        $http.defaults.withCredentials = true;
-        // $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
-        $http.defaults.headers.common[cookieService.getUserIdSuffix()] = cookieService.getUserId();
-
-        DataTypesService.initDataTypes();
-
-        //handle stateChangeStart
-        let internalDeregisterStateChangeStartWatcher:Function = ():void => {
-            if (deregisterStateChangeStartWatcher) {
-                deregisterStateChangeStartWatcher();
-                deregisterStateChangeStartWatcher = null;
-            }
-            if (deregisterStateChangeSuccessWatcher) {
-                deregisterStateChangeSuccessWatcher();
-                deregisterStateChangeSuccessWatcher = null;
-            }
-        };
-
-        let removeLoader:Function = ():void => {
-            $(".sdc-loading-page .main-loader").addClass("animated fadeOut");
-            $(".sdc-loading-page .caption1").addClass("animated fadeOut");
-            $(".sdc-loading-page .caption2").addClass("animated fadeOut");
-            window.setTimeout(():void=> {
-                $(".sdc-loading-page .main-loader").css("display", "none");
-                $(".sdc-loading-page .caption1").css("display", "none");
-                $(".sdc-loading-page .caption2").css("display", "none");
-                $(".sdc-loading-page").addClass("animated fadeOut");
-            }, 1000);
-        };
-
-        let onNavigateOut:Function = (toState, toParams):void => {
-            let onOk:Function = ():void => {
-                $state.current.data.unsavedChanges = false;
+  '$http',
+  'Sdc.Services.CacheService',
+  'Sdc.Services.CookieService',
+  'AuthenticationServiceNg2',
+  '$state',
+  '$rootScope',
+  '$location',
+  'sdcMenu',
+  'Sdc.Services.EcompHeaderService',
+  'LeftPaletteLoaderService',
+  'Sdc.Services.DataTypesService',
+  'AngularJSBridge',
+  '$templateCache',
+  'ModalServiceSdcUI',
+  ($http: ng.IHttpService,
+   cacheService: CacheService,
+   cookieService: CookieService,
+   authService: AuthenticationService,
+   $state: ng.ui.IStateService,
+   $rootScope: ng.IRootScopeService,
+   $location: ng.ILocationService,
+   sdcMenu: IAppMenu,
+   ecompHeaderService: EcompHeaderService,
+   LeftPaletteLoaderService: LeftPaletteLoaderService,
+   DataTypesService: DataTypesService,
+   AngularJSBridge,
+   $templateCache: ng.ITemplateCacheService,
+   ModalServiceSdcUI: SdcUiServices.ModalService): void => {
+    $templateCache.put('notification-custom-template.html', require('./view-models/shared/notification-custom-template.html'));
+    $templateCache.put('notification-custom-template.html', require('./view-models/shared/notification-custom-template.html'));
+
+    // Add hosted applications to sdcConfig
+    sdcConfig.hostedApplications = hostedApplications;
+
+    //handle http config
+    $http.defaults.withCredentials = true;
+    // $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
+    $http.defaults.headers.common[cookieService.getUserIdSuffix()] = cookieService.getUserId();
+
+    DataTypesService.initDataTypes();
+
+    //handle stateChangeStart
+    let internalDeregisterStateChangeStartWatcher: Function = (): void => {
+      if (deregisterStateChangeStartWatcher) {
+        deregisterStateChangeStartWatcher();
+        deregisterStateChangeStartWatcher = null;
+      }
+      if (deregisterStateChangeSuccessWatcher) {
+        deregisterStateChangeSuccessWatcher();
+        deregisterStateChangeSuccessWatcher = null;
+      }
+    };
+
+    let removeLoader: Function = (): void => {
+      $(".sdc-loading-page .main-loader").addClass("animated fadeOut");
+      $(".sdc-loading-page .caption1").addClass("animated fadeOut");
+      $(".sdc-loading-page .caption2").addClass("animated fadeOut");
+      window.setTimeout((): void => {
+        $(".sdc-loading-page .main-loader").css("display", "none");
+        $(".sdc-loading-page .caption1").css("display", "none");
+        $(".sdc-loading-page .caption2").css("display", "none");
+        $(".sdc-loading-page").addClass("animated fadeOut");
+      }, 1000);
+    };
+
+    let onNavigateOut: Function = (toState, toParams): void => {
+      let onOk: Function = (): void => {
+        $state.current.data.unsavedChanges = false;
+        $state.go(toState.name, toParams);
+      };
+
+      let data = sdcMenu.alertMessages.exitWithoutSaving;
+      const okButton = {
+        testId: "OK",
+        text: sdcMenu.alertMessages.okButton,
+        type: SdcUiCommon.ButtonType.warning,
+        callback: onOk,
+        closeModal: true
+      } as SdcUiComponents.ModalButtonComponent;
+      //open notify to user if changes are not saved
+      ModalServiceSdcUI.openWarningModal(data.title,
+          data.message,
+          'navigate-modal',
+          [okButton]);
+    };
+
+    let onStateChangeStart: Function = (event, toState, toParams, fromState, fromParams): void => {
+      console.info((new Date()).getTime());
+      console.info('$stateChangeStart', toState.name);
+      if (toState.name !== 'error-403' && !authService.getLoggedinUser()) {
+
+
+        authService.authenticate().subscribe((userInfo: IUserProperties) => {
+          if (!doesUserHasAccess(toState, userInfo)) {
+            $state.go('error-403');
+            console.info('User has no permissions');
+            return;
+          }
+          authService.setLoggedinUser(userInfo);
+          setTimeout(function () {
+
+            removeLoader();
+
+            if (authService.getLoggedinUser().role === 'ADMIN') {
+              // toState.name = "adminDashboard";
+              $state.go("adminDashboard", toParams);
+              return;
+            }
+
+            // After user authorized init categories
+            window.setTimeout((): void => {
+              if ($state.current.name === '') {
                 $state.go(toState.name, toParams);
-            };
-
-            let data = sdcMenu.alertMessages.exitWithoutSaving;
-            const okButton = {
-                testId: "OK",
-                text: sdcMenu.alertMessages.okButton,
-                type: SdcUiCommon.ButtonType.warning,
-                callback: onOk,
-                closeModal: true
-            } as SdcUiComponents.ModalButtonComponent;
-            //open notify to user if changes are not saved
-            ModalServiceSdcUI.openWarningModal(data.title,
-                data.message,
-                'navigate-modal',
-                [okButton]);
-        };
-
-        let onStateChangeStart:Function = (event, toState, toParams, fromState, fromParams):void => {
-            console.info((new Date()).getTime());
-            console.info('$stateChangeStart', toState.name);
-            if (toState.name !== 'error-403' && !authService.getLoggedinUser()) {
-
-
-                authService.authenticate().subscribe((userInfo:IUserProperties) => {
-                    if (!doesUserHasAccess(toState, userInfo)) {
-                        $state.go('error-403');
-                        console.info('User has no permissions');
-                        return;
-                    }
-                    authService.setLoggedinUser(userInfo);
-                    setTimeout(function () {
-
-                        removeLoader();
-
-                        if (authService.getLoggedinUser().role === 'ADMIN') {
-                            // toState.name = "adminDashboard";
-                            $state.go("adminDashboard", toParams);
-                            return;
-                        }
-
-                        // After user authorized init categories
-                        window.setTimeout(():void=> {
-                            if ($state.current.name === '') {
-                                $state.go(toState.name, toParams);
-                            }
+              }
 
-                            console.log("------$state.current.name=" + $state.current.name);
+              console.log("------$state.current.name=" + $state.current.name);
 
-                        }, 1000);
-
-                    }, 0);
-
-                }, () => {
-                    $state.go('error-403');
-                });
-            }
-            else if (authService.getLoggedinUser()) {
-                let user:IUserProperties =  authService.getLoggedinUser();
-                if(!cacheService.contains('user')){
-                    cacheService.set('user', user);
-                }
-
-                if (!doesUserHasAccess(toState, authService.getLoggedinUser())) {
-                    event.preventDefault();
-                    $state.go('error-403');
-                    console.info('User has no permissions');
-                }
-
-                if (authService.getLoggedinUser().role === 'ADMIN') {
-                    // toState.name = "adminDashboard";
-                    $state.go("adminDashboard", toParams);
-                    return;
-                }
+            }, 1000);
 
+          }, 0);
 
-                //if form is dirty and not save  - notify to user
-                if (fromState.data && fromState.data.unsavedChanges && fromParams.id != toParams.id) {
-                    event.preventDefault();
-                    onNavigateOut(toState, toParams);
-                }
-            }
+        }, () => {
+          $state.go('error-403');
+        });
+      } else if (authService.getLoggedinUser()) {
+        let user: IUserProperties = authService.getLoggedinUser();
+        if (!cacheService.contains('user')) {
+          cacheService.set('user', user);
+        }
 
-            // if enetering workspace, set the previousState param
-            if (toState.name.indexOf('workspace') !== -1) {
-                if (!toParams.previousState) {
-                    const tmpPreviousState1 = fromParams && fromParams.previousState;
-                    const tmpPreviousState2 = (['dashboard', 'catalog'].indexOf(fromState.name) !== -1) ? fromState.name : 'catalog';
-                    toParams.previousState = tmpPreviousState1 || tmpPreviousState2;
-                }
-            }
+        if (!doesUserHasAccess(toState, authService.getLoggedinUser())) {
+          event.preventDefault();
+          $state.go('error-403');
+          console.info('User has no permissions');
+        }
 
-        };
+        if (authService.getLoggedinUser().role === 'ADMIN') {
+          // toState.name = "adminDashboard";
+          $state.go("adminDashboard", toParams);
+          return;
+        }
 
-        let onStateChangeSuccess:Function = (event, toState, toParams, fromState, fromParams):void => {
-            console.info('$stateChangeSuccess', toState.name);
 
-            // Workaround in case we are entering other state then workspace (user move to catalog)
-            // remove the changeComponentCsarVersion, user should open again the VSP list and select one for update.
-            if (toState.name.indexOf('workspace') === -1) {
-                if (cacheService.contains(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
-                    cacheService.remove(CHANGE_COMPONENT_CSAR_VERSION_FLAG);
-                }
-                if (cacheService.contains(PREVIOUS_CSAR_COMPONENT)){
-                    cacheService.remove(PREVIOUS_CSAR_COMPONENT);
-                }
-            }
+        //if form is dirty and not save  - notify to user
+        if (fromState.data && fromState.data.unsavedChanges && fromParams.id != toParams.id) {
+          event.preventDefault();
+          onNavigateOut(toState, toParams);
+        }
+      }
+
+      // if enetering workspace, set the previousState param
+      if (toState.name.indexOf('workspace') !== -1) {
+        if (!toParams.previousState) {
+          const tmpPreviousState1 = fromParams && fromParams.previousState;
+          const tmpPreviousState2 = (['dashboard', 'catalog'].indexOf(fromState.name) !== -1) ? fromState.name : 'catalog';
+          toParams.previousState = tmpPreviousState1 || tmpPreviousState2;
+        }
+      }
 
-            //set body class
-            $rootScope['bodyClass'] = 'default-class';
-            if (toState.data && toState.data.bodyClass) {
-                $rootScope['bodyClass'] = toState.data.bodyClass;
-            }
-        };
+    };
 
-        let doesUserHasAccess:Function = (toState, user):boolean => {
+    let onStateChangeSuccess: Function = (event, toState, toParams, fromState, fromParams): void => {
+      console.info('$stateChangeSuccess', toState.name);
 
-            let isUserHasAccess = true;
-            if (toState.permissions && toState.permissions.length > 0) {
-                isUserHasAccess = _.includes(toState.permissions, user.role);
-            }
-            return isUserHasAccess;
-        };
-        let deregisterStateChangeStartWatcher:Function;
-        let deregisterStateChangeSuccessWatcher:Function;
-
-        let registerStateChangeStartWatcher:Function = ():void => {
-            internalDeregisterStateChangeStartWatcher();
-            console.info('registerStateChangeStartWatcher $stateChangeStart');
-            deregisterStateChangeStartWatcher = $rootScope.$on('$stateChangeStart', (event, toState, toParams, fromState, fromParams):void => {
-                onStateChangeStart(event, toState, toParams, fromState, fromParams);
-            });
-            deregisterStateChangeSuccessWatcher = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams):void => {
-                onStateChangeSuccess(event, toState, toParams, fromState, fromParams);
-            });
-        };
-        registerStateChangeStartWatcher();
-    }]);
+      // Workaround in case we are entering other state then workspace (user move to catalog)
+      // remove the changeComponentCsarVersion, user should open again the VSP list and select one for update.
+      if (toState.name.indexOf('workspace') === -1) {
+        if (cacheService.contains(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+          cacheService.remove(CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+        }
+        if (cacheService.contains(PREVIOUS_CSAR_COMPONENT)) {
+          cacheService.remove(PREVIOUS_CSAR_COMPONENT);
+        }
+      }
+
+      //set body class
+      $rootScope['bodyClass'] = 'default-class';
+      if (toState.data && toState.data.bodyClass) {
+        $rootScope['bodyClass'] = toState.data.bodyClass;
+      }
+    };
+
+    let doesUserHasAccess: Function = (toState, user): boolean => {
+
+      let isUserHasAccess = true;
+      if (toState.permissions && toState.permissions.length > 0) {
+        isUserHasAccess = _.includes(toState.permissions, user.role);
+      }
+      return isUserHasAccess;
+    };
+    let deregisterStateChangeStartWatcher: Function;
+    let deregisterStateChangeSuccessWatcher: Function;
+
+    let registerStateChangeStartWatcher: Function = (): void => {
+      internalDeregisterStateChangeStartWatcher();
+      console.info('registerStateChangeStartWatcher $stateChangeStart');
+      deregisterStateChangeStartWatcher = $rootScope.$on('$stateChangeStart', (event, toState, toParams, fromState, fromParams): void => {
+        onStateChangeStart(event, toState, toParams, fromState, fromParams);
+      });
+      deregisterStateChangeSuccessWatcher = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams): void => {
+        onStateChangeSuccess(event, toState, toParams, fromState, fromParams);
+      });
+    };
+    registerStateChangeStartWatcher();
+  }]);
 
index b8f2615..bd8cbd3 100644 (file)
@@ -23,7 +23,7 @@
  */
 'use strict';
 import {DataTypesService} from "app/services/data-types-service";
-import {SchemaProperty} from "app/models/aschema-property";
+import {SchemaProperty} from "app/models/schema-property";
 import {ValidationUtils, PROPERTY_TYPES} from "app/utils";
 
 export interface ISelectTypeListScope extends ng.IScope {
index ad201e2..78be1e5 100644 (file)
@@ -28,7 +28,7 @@ export * from './models/app-config';
 export * from './models/validation-config';
 export * from './models/plugins-config';
 export * from './models/artifacts';
-export * from './models/aschema-property';
+export * from './models/schema-property';
 export * from './models/schema-attribute';
 export * from './models/attributes';
 export * from './models/capability';
diff --git a/catalog-ui/src/app/models/attributes-outputs/attribute-be-model.ts b/catalog-ui/src/app/models/attributes-outputs/attribute-be-model.ts
new file mode 100644 (file)
index 0000000..e06a807
--- /dev/null
@@ -0,0 +1,105 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {PROPERTY_DATA, PROPERTY_TYPES} from 'app/utils/constants';
+import {ToscaPresentationData} from '../tosca-presentation';
+import {AttributeOutputDetail} from "app/models/attributes-outputs/attribute-output-detail";
+import {SchemaAttribute, SchemaAttributeGroupModel} from "../schema-attribute";
+
+export enum DerivedAttributeType {
+  SIMPLE,
+  LIST,
+  MAP,
+  COMPLEX
+}
+
+export class AttributeBEModel {
+
+  constraints: any[];
+  defaultValue: string;
+  definition: boolean;
+  description: string;
+  fromDerived: boolean;
+  getOutputValues: AttributeOutputDetail[];
+  name: string;
+  origName: string;
+  parentUniqueId: string;
+  password: boolean;
+  required: boolean;
+  schema: SchemaAttributeGroupModel;
+  schemaType: string;
+  type: string;
+  uniqueId: string;
+  value: string;
+  parentAttributeType: string;
+  subAttributeOutputPath: string;
+  outputPath: string;
+  toscaPresentation: ToscaPresentationData;
+
+  constructor(attribute?: AttributeBEModel) {
+    if (attribute) {
+      this.constraints = attribute.constraints;
+      this.defaultValue = attribute.defaultValue;
+      this.description = attribute.description;
+      this.fromDerived = attribute.fromDerived;
+      this.name = attribute.name;
+      this.origName = attribute.origName;
+      this.parentUniqueId = attribute.parentUniqueId;
+      this.password = attribute.password;
+      this.required = attribute.required;
+      this.schema = attribute.schema;
+      this.schemaType = attribute.schemaType;
+      this.type = attribute.type;
+      this.uniqueId = attribute.uniqueId;
+      this.value = attribute.value;
+      this.definition = attribute.definition;
+      this.getOutputValues = attribute.getOutputValues;
+      this.parentAttributeType = attribute.parentAttributeType;
+      this.subAttributeOutputPath = attribute.subAttributeOutputPath;
+      this.toscaPresentation = attribute.toscaPresentation;
+      this.outputPath = attribute.outputPath;
+    }
+
+    if (!this.schema || !this.schema.property) {
+      this.schema = new SchemaAttributeGroupModel(new SchemaAttribute());
+    } else { // forcing creating new object, so editing different one than the object in the table
+      this.schema = new SchemaAttributeGroupModel(new SchemaAttribute(this.schema.property));
+    }
+  }
+
+  public toJSON = (): any => {
+    const temp = angular.copy(this);
+    temp.value = temp.value === '{}' || temp.value === '[]' ? undefined : temp.value;
+    temp.defaultValue = temp.defaultValue === '{}' || temp.defaultValue === '[]' ? undefined : temp.defaultValue;
+    return temp;
+  }
+
+  public getDerivedAttributeType = () => {
+    if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1) {
+      return DerivedAttributeType.SIMPLE;
+    } else if (this.type === PROPERTY_TYPES.LIST) {
+      return DerivedAttributeType.LIST;
+    } else if (this.type === PROPERTY_TYPES.MAP) {
+      return DerivedAttributeType.MAP;
+    } else {
+      return DerivedAttributeType.COMPLEX;
+    }
+  }
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/attribute-declare-api-model.ts b/catalog-ui/src/app/models/attributes-outputs/attribute-declare-api-model.ts
new file mode 100644 (file)
index 0000000..aa05b39
--- /dev/null
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+'use strict';
+import {AttributeBEModel} from "./attribute-be-model";
+import {AttributeFEModel} from "./attribute-fe-model";
+import {DerivedFEAttribute} from "./derived-fe-attribute";
+
+export class AttributeDeclareAPIModel extends AttributeBEModel {
+  output: AttributeBEModel;
+  attributesName: string;
+
+  constructor(attribute: AttributeFEModel, childAttribute?: DerivedFEAttribute) {
+    super(attribute);
+    if (childAttribute) {
+      this.output = childAttribute;
+      this.attributesName = childAttribute.attributesName;
+    }
+  }
+
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/attribute-fe-map.ts b/catalog-ui/src/app/models/attributes-outputs/attribute-fe-map.ts
new file mode 100644 (file)
index 0000000..e962e91
--- /dev/null
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+'use strict';
+
+import {AttributeBEModel} from "./attribute-be-model";
+import {AttributeFEModel} from "./attribute-fe-model";
+
+export class InstanceBeAttributesMap {
+  [instanceId: string]: Array<AttributeBEModel>;
+}
+
+export class InstanceFeAttributesMap {
+  [instanceId: string]: Array<AttributeFEModel>;
+}
+
+export class InstanceAttributesAPIMap {
+  componentInstanceAttributes: InstanceBeAttributesMap;
+  componentInstanceOutputsMap: InstanceBeAttributesMap;
+
+  constructor(outputsMapData: InstanceBeAttributesMap, attributesMapData: InstanceBeAttributesMap) {
+    this.componentInstanceOutputsMap = outputsMapData ? outputsMapData : new InstanceBeAttributesMap();
+    this.componentInstanceAttributes = attributesMapData ? attributesMapData : new InstanceBeAttributesMap();
+  }
+
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/attribute-fe-model.ts b/catalog-ui/src/app/models/attributes-outputs/attribute-fe-model.ts
new file mode 100644 (file)
index 0000000..a939783
--- /dev/null
@@ -0,0 +1,326 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {PROPERTY_DATA, PROPERTY_TYPES} from 'app/utils';
+import {
+  AttributeBEModel,
+  DerivedAttributeType
+} from "app/models/attributes-outputs/attribute-be-model";
+import {DerivedFEAttribute} from "app/models/attributes-outputs/derived-fe-attribute";
+
+export class AttributeFEModel extends AttributeBEModel {
+
+  expandedChildAttributeId: string;
+  flattenedChildren: Array<DerivedFEAttribute>;
+  isDeclared: boolean;
+  isDisabled: boolean;
+  isSelected: boolean;
+  isSimpleType: boolean; //for convenience only - we can really just check if derivedDataType == derivedPropertyTypes.SIMPLE to know if the attrib is simple
+  attributesName: string;
+  uniqueId: string;
+  valueObj: any; //this is the only value we relate to in the html templates
+  valueObjValidation: any;
+  valueObjIsValid: boolean;
+  valueObjOrig: any; //this is valueObj representation as saved in server
+  valueObjIsChanged: boolean;
+  derivedDataType: DerivedAttributeType;
+  origName: string;
+
+  constructor(attribute: AttributeBEModel) {
+    super(attribute);
+    this.value = attribute.value ? attribute.value : attribute.defaultValue;//In FE if a attribute doesn't have value - display the default value
+    this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1;
+    this.setNonDeclared();
+    this.derivedDataType = this.getDerivedAttributeType();
+    this.flattenedChildren = [];
+    this.attributesName = this.name;
+    this.valueObj = null;
+    this.updateValueObjOrig();
+    this.resetValueObjValidation();
+    this.origName = this.name;
+  }
+
+
+  public updateValueObj(valueObj: any, isValid: boolean) {
+    this.valueObj = AttributeFEModel.cleanValueObj(valueObj);
+    this.valueObjValidation = this.valueObjIsValid = isValid;
+    this.valueObjIsChanged = this.hasValueObjChanged();
+  }
+
+  public updateValueObjOrig() {
+    this.valueObjOrig = _.cloneDeep(this.valueObj);
+    this.valueObjIsChanged = false;
+  }
+
+  public calculateValueObjIsValid(valueObjValidation?: any) {
+    valueObjValidation = (valueObjValidation !== undefined) ? valueObjValidation : this.valueObjValidation;
+    if (valueObjValidation instanceof Array) {
+      return valueObjValidation.every((v) => this.calculateValueObjIsValid(v));
+    } else if (valueObjValidation instanceof Object) {
+      return Object.keys(valueObjValidation).every((k) => this.calculateValueObjIsValid(valueObjValidation[k]));
+    }
+    return Boolean(valueObjValidation);
+  }
+
+  public resetValueObjValidation() {
+    if (this.derivedDataType === DerivedAttributeType.SIMPLE) {
+      this.valueObjValidation = null;
+    } else if (this.derivedDataType === DerivedAttributeType.LIST) {
+      this.valueObjValidation = [];
+    } else {
+      this.valueObjValidation = {};
+    }
+    this.valueObjIsValid = true;
+  }
+
+  public getJSONValue = (): string => {
+    return AttributeFEModel.stringifyValueObj(this.valueObj, this.schema.property.type, this.derivedDataType);
+  }
+
+  public getValueObj = (): any => {
+    return AttributeFEModel.parseValueObj(this.value, this.type, this.derivedDataType, this.defaultValue);
+  }
+
+  public setNonDeclared = (childPath?: string): void => {
+    if (!childPath) { //un-declaring a child attrib
+      this.isDeclared = false;
+    } else {
+      let childProp: DerivedFEAttribute = this.flattenedChildren.find(child => child.attributesName == childPath);
+      childProp.isDeclared = false;
+    }
+  }
+
+  public setAsDeclared = (childNameToDeclare?: string): void => {
+    if (!childNameToDeclare) { //declaring a child attrib
+      this.isSelected = false;
+      this.isDeclared = true;
+    } else {
+      let childProp: DerivedFEAttribute = this.flattenedChildren.find(child => child.attributesName == childNameToDeclare);
+      if (!childProp) {
+        console.log("ERROR: Unabled to find child: " + childNameToDeclare, this);
+        return;
+      }
+      childProp.isSelected = false;
+      childProp.isDeclared = true;
+    }
+  }
+
+  //For expand-collapse functionality - used within HTML template
+  public updateExpandedChildAttributeId = (childAttributeId: string): void => {
+    if (childAttributeId.lastIndexOf('#') > -1) {
+      this.expandedChildAttributeId = (this.expandedChildAttributeId == childAttributeId) ? (childAttributeId.substring(0, childAttributeId.lastIndexOf('#'))) : childAttributeId;
+    } else {
+      this.expandedChildAttributeId = this.name;
+    }
+  }
+
+  public getIndexOfChild = (childPropName: string): number => {
+    return this.flattenedChildren.findIndex(attrib => attrib.attributesName.indexOf(childPropName) === 0);
+  }
+
+  public getCountOfChildren = (childPropName: string): number => {
+    let matchingChildren: Array<DerivedFEAttribute> = this.flattenedChildren.filter(attrib => attrib.attributesName.indexOf(childPropName) === 0) || [];
+    return matchingChildren.length;
+  }
+
+
+  /* Updates parent valueObj when a child attrib's value has changed */
+  public childPropUpdated = (childProp: DerivedFEAttribute): void => {
+    let parentNames = this.getParentNamesArray(childProp.attributesName, []);
+    if (parentNames.length) {
+      const childPropName = parentNames.join('.');
+      // unset value only if is null and valid, and not in a list
+      if (childProp.valueObj === null && childProp.valueObjIsValid) {
+        const parentChildProp = this.flattenedChildren.find((ch) => ch.attributesName === childProp.parentName) || this;
+        if (parentChildProp.derivedDataType !== DerivedAttributeType.LIST) {
+          _.unset(this.valueObj, childPropName);
+          this.valueObj = AttributeFEModel.cleanValueObj(this.valueObj);
+        } else {
+          _.set(this.valueObj, childPropName, null);
+        }
+      } else {
+        _.set(this.valueObj, childPropName, childProp.valueObj);
+      }
+      if (childProp.valueObjIsChanged) {
+        _.set(this.valueObjValidation, childPropName, childProp.valueObjIsValid);
+        this.valueObjIsValid = childProp.valueObjIsValid && this.calculateValueObjIsValid();
+        this.valueObjIsChanged = true;
+      } else {
+        _.unset(this.valueObjValidation, childPropName);
+        this.valueObjIsValid = this.calculateValueObjIsValid();
+        this.valueObjIsChanged = this.hasValueObjChanged();
+      }
+    }
+  };
+
+  childPropMapKeyUpdated = (childProp: DerivedFEAttribute, newMapKey: string, forceValidate: boolean = false) => {
+    if (!childProp.isChildOfListOrMap || childProp.derivedDataType !== DerivedAttributeType.MAP) {
+      return;
+    }
+
+    const childParentNames = this.getParentNamesArray(childProp.parentName);
+    const oldActualMapKey = childProp.getActualMapKey();
+
+    childProp.mapKey = newMapKey;
+    if (childProp.mapKey === null) {  // null -> remove map key
+      childProp.mapKeyError = null;
+    } else if (!childProp.mapKey) {
+      childProp.mapKeyError = 'Key cannot be empty.';
+    } else if (this.flattenedChildren
+    .filter((fch) => fch !== childProp && fch.parentName === childProp.parentName)  // filter sibling child props
+    .map((fch) => fch.mapKey)
+    .indexOf(childProp.mapKey) !== -1) {
+      childProp.mapKeyError = 'This key already exists.';
+    } else {
+      childProp.mapKeyError = null;
+    }
+    const newActualMapKey = childProp.getActualMapKey();
+    const newMapKeyIsValid = !childProp.mapKeyError;
+
+    // if mapKey was changed, then replace the old key with the new one
+    if (newActualMapKey !== oldActualMapKey) {
+      const oldChildPropNames = childParentNames.concat([oldActualMapKey]);
+      const newChildPropNames = (newActualMapKey) ? childParentNames.concat([newActualMapKey]) : null;
+
+      // add map key to valueObj and valueObjValidation
+      if (newChildPropNames) {
+        const newChildVal = _.get(this.valueObj, oldChildPropNames);
+        if (newChildVal !== undefined) {
+          _.set(this.valueObj, newChildPropNames, newChildVal);
+          _.set(this.valueObjValidation, newChildPropNames, _.get(this.valueObjValidation, oldChildPropNames, childProp.valueObjIsValid));
+        }
+      }
+
+      // remove map key from valueObj and valueObjValidation
+      _.unset(this.valueObj, oldChildPropNames);
+      _.unset(this.valueObjValidation, oldChildPropNames);
+
+      // force validate after map key change
+      forceValidate = true;
+    }
+
+    if (forceValidate) {
+      // add custom entry for map key validation:
+      const childMapKeyNames = childParentNames.concat(`%%KEY:${childProp.name}%%`);
+      if (newActualMapKey) {
+        _.set(this.valueObjValidation, childMapKeyNames, newMapKeyIsValid);
+      } else {
+        _.unset(this.valueObjValidation, childMapKeyNames);
+      }
+
+      this.valueObjIsValid = newMapKeyIsValid && this.calculateValueObjIsValid();
+      this.valueObjIsChanged = this.hasValueObjChanged();
+    }
+  };
+
+  /* Returns array of individual parents for given attrib path, with list/map UUIDs replaced with index/mapkey */
+  public getParentNamesArray = (parentPropName: string, parentNames?: Array<string>, noHashKeys: boolean = false): Array<string> => {
+    parentNames = parentNames || [];
+    if (parentPropName.indexOf("#") == -1) {
+      return parentNames;
+    } //finished recursing parents. return
+
+    let parentAttrib: DerivedFEAttribute = this.flattenedChildren.find(attrib => attrib.attributesName === parentPropName);
+    let nameToInsert: string = parentAttrib.name;
+
+    if (parentAttrib.isChildOfListOrMap) {
+      if (!noHashKeys && parentAttrib.derivedDataType == DerivedAttributeType.MAP) {
+        nameToInsert = parentAttrib.getActualMapKey();
+      } else { //LIST
+        let siblingProps = this.flattenedChildren.filter(attrib => attrib.parentName == parentAttrib.parentName).map(attrib => attrib.attributesName);
+        nameToInsert = siblingProps.indexOf(parentAttrib.attributesName).toString();
+      }
+    }
+
+    parentNames.splice(0, 0, nameToInsert); //add attrib name to array
+    return this.getParentNamesArray(parentAttrib.parentName, parentNames, noHashKeys); //continue recursing
+  }
+
+  public hasValueObjChanged() {
+    return !_.isEqual(this.valueObj, this.valueObjOrig);
+  }
+
+  static stringifyValueObj(valueObj: any, attributeType: PROPERTY_TYPES, derivedAttributeType: DerivedAttributeType): string {
+    // if valueObj is null, return null
+    if (valueObj === null || valueObj === undefined) {
+      return null;
+    }
+
+    //If type is JSON, need to try parsing it before we stringify it so that it appears property in TOSCA - change per Bracha due to AMDOCS
+    //TODO: handle this.derivedDataType == DerivedAttributeType.MAP
+    if (derivedAttributeType == DerivedAttributeType.LIST && attributeType == PROPERTY_TYPES.JSON) {
+      try {
+        return JSON.stringify(valueObj.map(item => (typeof item == 'string') ? JSON.parse(item) : item));
+      } catch (e) {
+      }
+    }
+
+    // if type is anything but string, then stringify valueObj
+    if ((typeof valueObj) !== 'string') {
+      return JSON.stringify(valueObj);
+    }
+
+    // return string value as is
+    return valueObj;
+  }
+
+  static parseValueObj(value: string, attributeType: PROPERTY_TYPES, derivedAttributeType: DerivedAttributeType, defaultValue?: string): any {
+    let valueObj;
+    if (derivedAttributeType === DerivedAttributeType.SIMPLE) {
+      valueObj = value || defaultValue || null;  // use null for empty value object
+      if (valueObj &&
+          attributeType !== PROPERTY_TYPES.STRING &&
+          attributeType !== PROPERTY_TYPES.JSON &&
+          PROPERTY_DATA.SCALAR_TYPES.indexOf(<string>attributeType) == -1) {
+        valueObj = JSON.parse(value);  // the value object contains the real value ans not the value as string
+      }
+    } else if (derivedAttributeType == DerivedAttributeType.LIST) {
+      valueObj = _.merge([], JSON.parse(defaultValue || '[]'), JSON.parse(value || '[]'));  // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined.
+    } else {
+      valueObj = _.merge({}, JSON.parse(defaultValue || '{}'), JSON.parse(value || '{}'));  // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined.
+    }
+    return valueObj;
+  }
+
+  static cleanValueObj(valueObj: any, unsetEmpty?: boolean): any {
+    // By default - unsetEmpty undefined - will make valueObj cleaned (no null or empty objects, but array will keep null or empty objects).
+    if (valueObj === undefined || valueObj === null || valueObj === '') {
+      return null;
+    }
+    if (valueObj instanceof Array) {
+      const cleanArr = valueObj.map((v) => AttributeFEModel.cleanValueObj(v)).filter((v) => v !== null);
+      valueObj.splice(0, valueObj.length, ...cleanArr)
+    } else if (valueObj instanceof Object) {
+      Object.keys(valueObj).forEach((k) => {
+        // clean each item in the valueObj (by default, unset empty objects)
+        valueObj[k] = AttributeFEModel.cleanValueObj(valueObj[k], unsetEmpty !== undefined ? unsetEmpty : true);
+        if (valueObj[k] === null) {
+          delete valueObj[k];
+        }
+      });
+      // if unsetEmpty flag is true and valueObj is empty
+      if (unsetEmpty && !Object.keys(valueObj).length) {
+        return null;
+      }
+    }
+    return valueObj;
+  }
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/attribute-output-detail.ts b/catalog-ui/src/app/models/attributes-outputs/attribute-output-detail.ts
new file mode 100644 (file)
index 0000000..e646878
--- /dev/null
@@ -0,0 +1,26 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+export class AttributeOutputDetail {
+  outputId: string;
+  outputName: string;
+  outputPath: string;
+  list: boolean;
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/derived-fe-attribute.ts b/catalog-ui/src/app/models/attributes-outputs/derived-fe-attribute.ts
new file mode 100644 (file)
index 0000000..e9fad4d
--- /dev/null
@@ -0,0 +1,99 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {SchemaAttribute, SchemaAttributeGroupModel} from '../../models';
+import {PROPERTY_TYPES} from 'app/utils';
+import {UUID} from "angular2-uuid";
+import {AttributeBEModel, DerivedAttributeType} from "./attribute-be-model";
+import {AttributeFEModel} from "./attribute-fe-model";
+
+export class DerivedFEAttribute extends AttributeBEModel {
+  valueObj: any;
+  valueObjIsValid: boolean;
+  valueObjOrig: any;
+  valueObjIsChanged: boolean;
+  parentName: string;
+  attributesName: string;
+  derivedDataType: DerivedAttributeType;
+  isDeclared: boolean;
+  isSelected: boolean;
+  isDisabled: boolean;
+  hidden: boolean;
+  isChildOfListOrMap: boolean;
+  canBeDeclared: boolean;
+  mapKey: string;
+  mapKeyError: string;
+
+  constructor(attribute: AttributeBEModel, parentName?: string, createChildOfListOrMap?: boolean, key?: string, value?: any) {
+    if (createChildOfListOrMap) { //creating a direct child of list or map (ie. Item that can be deleted, with UUID instead of name)
+      super(null);
+      this.isChildOfListOrMap = true;
+      this.canBeDeclared = false;
+      this.name = UUID.UUID();
+      this.parentName = parentName;
+      this.attributesName = parentName + '#' + this.name;
+
+      if (attribute.type == PROPERTY_TYPES.LIST) {
+        this.mapKey = attribute.schema.property.type.split('.').pop();
+        this.mapKeyError = null;
+        this.type = attribute.schema.property.type;
+      } else { //map
+        if (key) {
+          this.mapKey = key;
+          this.mapKeyError = null;
+        } else {
+          this.mapKey = '';
+          this.mapKeyError = 'Key cannot be empty.';
+        }
+        this.type = attribute.type;
+      }
+      this.valueObj = (this.type == PROPERTY_TYPES.JSON && typeof value == 'object') ? JSON.stringify(value) : value;
+      this.schema = new SchemaAttributeGroupModel(new SchemaAttribute(attribute.schema.property));
+      this.updateValueObjOrig();
+    } else { //creating a standard derived prop
+      super(attribute);
+      this.parentName = parentName ? parentName : null;
+      this.attributesName = (parentName) ? parentName + '#' + attribute.name : attribute.name;
+      this.canBeDeclared = true; //defaults to true
+    }
+    this.valueObjIsValid = true;
+    this.derivedDataType = this.getDerivedAttributeType();
+  }
+
+  public getActualMapKey() {
+    return (this.mapKeyError) ? this.name : this.mapKey;
+  }
+
+  public updateValueObj(valueObj: any, isValid: boolean) {
+    this.valueObj = AttributeFEModel.cleanValueObj(valueObj);
+    this.valueObjIsValid = isValid;
+    this.valueObjIsChanged = this.hasValueObjChanged();
+  }
+
+  public updateValueObjOrig() {
+    this.valueObjOrig = _.cloneDeep(this.valueObj);
+    this.valueObjIsChanged = false;
+  }
+
+  public hasValueObjChanged() {
+    return !_.isEqual(this.valueObj, this.valueObjOrig);
+  }
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/output-be-model.ts b/catalog-ui/src/app/models/attributes-outputs/output-be-model.ts
new file mode 100644 (file)
index 0000000..c5f0bfa
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {AttributeBEModel} from "./attribute-be-model";
+
+export class OutputBEModel extends AttributeBEModel {
+
+  outputPath: string;
+  outputs: Array<OutputComponentInstanceModel>;
+  instanceUniqueId: string;
+  ownerId: string;
+  attributeId: string;
+  attributes: Array<OutputComponentInstanceModel>;
+
+  constructor(output?: OutputBEModel) {
+    super(output);
+    this.instanceUniqueId = output.instanceUniqueId;
+    this.attributeId = output.attributeId;
+    this.attributes = output.attributes;
+    this.outputs = output.outputs;
+    this.ownerId = output.ownerId;
+    this.outputPath = output.outputPath;
+  }
+
+}
+
+export interface OutputComponentInstanceModel extends OutputBEModel {
+  componentInstanceId: string;
+  componentInstanceName: string;
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/output-fe-model.ts b/catalog-ui/src/app/models/attributes-outputs/output-fe-model.ts
new file mode 100644 (file)
index 0000000..8806562
--- /dev/null
@@ -0,0 +1,76 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {PROPERTY_DATA} from "../../utils/constants";
+import {OutputBEModel} from "./output-be-model";
+import {AttributeFEModel} from "./attribute-fe-model";
+import {DerivedAttributeType} from "./attribute-be-model";
+
+export class OutputFEModel extends OutputBEModel {
+  isSimpleType: boolean;
+  relatedAttributeValue: any;
+  relatedAttributeName: string;
+  defaultValueObj: any;
+  defaultValueObjIsValid: boolean;
+  defaultValueObjOrig: any;
+  defaultValueObjIsChanged: boolean;
+  derivedDataType: DerivedAttributeType;
+
+  constructor(output?: OutputBEModel) {
+    super(output);
+    if (output) {
+      this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1;
+      let relatedAttribute = output.attributes && output.attributes[0] || output.outputs && output.outputs[0];
+      if (relatedAttribute) {
+        this.relatedAttributeValue = relatedAttribute.value;
+        this.relatedAttributeName = relatedAttribute.name;
+      }
+      this.derivedDataType = this.getDerivedAttributeType();
+      this.resetDefaultValueObjValidation();
+      this.updateDefaultValueObjOrig();
+    }
+  }
+
+  public updateDefaultValueObjOrig() {
+    this.defaultValueObjOrig = _.cloneDeep(this.defaultValueObj);
+    this.defaultValueObjIsChanged = false;
+  }
+
+  public getJSONDefaultValue(): string {
+    return AttributeFEModel.stringifyValueObj(this.defaultValueObj, this.schema.property.type, this.derivedDataType);
+  }
+
+  public getDefaultValueObj(): any {
+    return AttributeFEModel.parseValueObj(this.defaultValueObj, this.type, this.derivedDataType);
+  }
+
+  public resetDefaultValueObjValidation() {
+    this.defaultValueObjIsValid = true;
+  }
+
+  hasDefaultValueChanged(): boolean {
+    return !_.isEqual(this.defaultValueObj, this.defaultValueObjOrig);
+  }
+
+  hasChanged(): boolean {
+    return this.hasDefaultValueChanged() ;
+  }
+}
diff --git a/catalog-ui/src/app/models/attributes-outputs/simple-flat-attribute.ts b/catalog-ui/src/app/models/attributes-outputs/simple-flat-attribute.ts
new file mode 100644 (file)
index 0000000..bd8b0cd
--- /dev/null
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+export class SimpleFlatAttribute {
+    uniqueId: string;
+    path: string;
+    name: string;
+    parentName: string;
+    instanceName: string;
+
+    constructor(uniqueId?: string, path?: string, name?: string, parentName?: string, instanceName?: string) {
+        this.uniqueId = uniqueId;
+        this.path = path;
+        this.name = name;
+        this.parentName = parentName;
+        this.instanceName = instanceName;
+    }
+}
index a51358c..ae5eac0 100644 (file)
 
 'use strict';
 import * as _ from "lodash";
-import {SchemaAttributeGroupModel, SchemaAttribute} from "./schema-attribute";
-import {SchemaPropertyGroupModel, SchemaProperty} from "./aschema-property";
+import {SchemaAttribute, SchemaAttributeGroupModel} from "./schema-attribute";
+import {AttributeOutputDetail} from "app/models/attributes-outputs/attribute-output-detail";
+import {AttributeBEModel} from "app/models/attributes-outputs/attribute-be-model";
 
 export class AttributesGroup {
-    constructor(attributesObj?:AttributesGroup) {
-        _.forEach(attributesObj, (attributes:Array<AttributeModel>, instance) => {
-            this[instance] = [];
-            _.forEach(attributes, (attribute:AttributeModel):void => {
-                attribute.resourceInstanceUniqueId = instance;
-                attribute.readonly = true;
-                this[instance].push(new AttributeModel(attribute));
-            });
-        });
-    }
+  constructor(attributesObj?: AttributesGroup) {
+    _.forEach(attributesObj, (attributes: Array<AttributeModel>, instance) => {
+      this[instance] = [];
+      _.forEach(attributes, (attribute: AttributeModel): void => {
+        attribute.resourceInstanceUniqueId = instance;
+        attribute.readonly = true;
+        this[instance].push(new AttributeModel(attribute));
+      });
+    });
+  }
 }
 
 export interface IAttributeModel {
 
-    //server data
-    uniqueId:string;
-    name:string;
-    _default:string;
-    description:string;
-    type:string;
-    schema:SchemaAttributeGroupModel;
-    status:string;
-    value:string;
-    parentUniqueId:string;
-    //custom data
-    resourceInstanceUniqueId:string;
-    readonly:boolean;
-    valueUniqueUid:string;
+  //server data
+  uniqueId: string;
+  name: string;
+  _default: string;
+  description: string;
+  type: string;
+  schema: SchemaAttributeGroupModel;
+  status: string;
+  value: string;
+  parentUniqueId: string;
+  //custom data
+  resourceInstanceUniqueId: string;
+  readonly: boolean;
+  valueUniqueUid: string;
 }
 
-export class AttributeModel implements IAttributeModel {
-
-    //server data
-    uniqueId:string;
-    name:string;
-    _default:string;
-    description:string;
-    type:string;
-    schema:SchemaAttributeGroupModel;
-    status:string;
-    value:string;
-    parentUniqueId:string;
-    //custom data
-    resourceInstanceUniqueId:string;
-    readonly:boolean;
-    valueUniqueUid:string;
-
-    constructor(attribute?:AttributeModel) {
-        if (attribute) {
-            this.uniqueId = attribute.uniqueId;
-            this.name = attribute.name;
-            this._default = attribute._default;
-            this.description = attribute.description;
-            this.type = attribute.type;
-            this.status = attribute.status;
-            this.schema = attribute.schema;
-            this.value = attribute.value;
-            this.parentUniqueId = attribute.parentUniqueId;
-            this.resourceInstanceUniqueId = attribute.resourceInstanceUniqueId;
-            this.readonly = attribute.readonly;
-            this.valueUniqueUid = attribute.valueUniqueUid;
-        } else {
-            this._default = '';
-        }
-
-        if (!this.schema || !this.schema.property) {
-            this.schema = new SchemaPropertyGroupModel(new SchemaProperty());
-        } else {
-            //forcing creating new object, so editing different one than the object in the table
-            this.schema = new SchemaAttributeGroupModel(new SchemaAttribute(this.schema.property));
-        }
-
-        this.convertValueToView();
+export class AttributeModel extends AttributeBEModel implements IAttributeModel {
+
+  //server data
+  uniqueId: string;
+  name: string;
+  _default: string;
+  description: string;
+  type: string;
+  schema: SchemaAttributeGroupModel;
+  status: string;
+  value: string;
+  parentUniqueId: string;
+  //custom data
+  resourceInstanceUniqueId: string;
+  readonly: boolean;
+  valueUniqueUid: string;
+
+  getOutputValues: AttributeOutputDetail[];
+  subAttributeOutputPath: string;
+  outputPath: string;
+
+  constructor(attribute?: AttributeModel) {
+    super(attribute);
+    if (attribute) {
+      this.uniqueId = attribute.uniqueId;
+      this.name = attribute.name;
+      this._default = attribute._default;
+      this.description = attribute.description;
+      this.type = attribute.type;
+      this.status = attribute.status;
+      this.schema = attribute.schema;
+      this.value = attribute.value;
+      this.parentUniqueId = attribute.parentUniqueId;
+      this.resourceInstanceUniqueId = attribute.resourceInstanceUniqueId;
+      this.readonly = attribute.readonly;
+      this.valueUniqueUid = attribute.valueUniqueUid;
+
+      this.getOutputValues = attribute.getOutputValues;
+      this.subAttributeOutputPath = attribute.subAttributeOutputPath;
+      this.outputPath = attribute.outputPath;
+    } else {
+      this._default = '';
+    }
+
+    if (!this.schema || !this.schema.property) {
+      this.schema = new SchemaAttributeGroupModel(new SchemaAttribute());
+    } else {
+      //forcing creating new object, so editing different one than the object in the table
+      this.schema = new SchemaAttributeGroupModel(new SchemaAttribute(this.schema.property));
+    }
+
+    this.convertValueToView();
+  }
+
+  public convertToServerObject(): string {
+    if (this._default && this.type === 'map') {
+      this._default = '{' + this._default + '}';
+    }
+    if (this._default && this.type === 'list') {
+      this._default = '[' + this._default + ']';
+    }
+    this._default = this._default != "" && this._default != "[]" && this._default != "{}" ? this._default : null;
+
+    return JSON.stringify(this);
+  }
+
+
+  public convertValueToView() {
+    //unwrapping value {} or [] if type is complex
+    if (this._default && (this.type === 'map' || this.type === 'list') &&
+        ['[', '{'].indexOf(this._default.charAt(0)) > -1 &&
+        [']', '}'].indexOf(this._default.slice(-1)) > -1) {
+      this._default = this._default.slice(1, -1);
     }
 
-    public convertToServerObject():string {
-        if (this._default && this.type === 'map') {
-            this._default = '{' + this._default + '}';
-        }
-        if (this._default && this.type === 'list') {
-            this._default = '[' + this._default + ']';
-        }
-        this._default = this._default != "" && this._default != "[]" && this._default != "{}" ? this._default : null;
-
-        return JSON.stringify(this);
-    };
-
-
-    public convertValueToView() {
-        //unwrapping value {} or [] if type is complex
-        if (this._default && (this.type === 'map' || this.type === 'list') &&
-            ['[', '{'].indexOf(this._default.charAt(0)) > -1 &&
-            [']', '}'].indexOf(this._default.slice(-1)) > -1) {
-            this._default = this._default.slice(1, -1);
-        }
-
-        //also for value - for the modal in canvas
-        if (this.value && (this.type === 'map' || this.type === 'list') &&
-            ['[', '{'].indexOf(this.value.charAt(0)) > -1 &&
-            [']', '}'].indexOf(this.value.slice(-1)) > -1) {
-            this.value = this.value.slice(1, -1);
-        }
+    //also for value - for the modal in canvas
+    if (this.value && (this.type === 'map' || this.type === 'list') &&
+        ['[', '{'].indexOf(this.value.charAt(0)) > -1 &&
+        [']', '}'].indexOf(this.value.slice(-1)) > -1) {
+      this.value = this.value.slice(1, -1);
     }
+  }
 
-    public toJSON = ():any => {
-        if (!this.resourceInstanceUniqueId) {
-            this.value = undefined;
-        }
-        this.readonly = undefined;
-        this.resourceInstanceUniqueId = undefined;
-        return this;
-    };
+  public toJSON = (): any => {
+    if (!this.resourceInstanceUniqueId) {
+      this.value = undefined;
+    }
+    this.readonly = undefined;
+    this.resourceInstanceUniqueId = undefined;
+    return this;
+  };
 }
index c278ad3..7717f8a 100644 (file)
@@ -22,7 +22,7 @@
  * Created by rcohen on 9/25/2016.
  */
 'use strict';
-import {SchemaPropertyGroupModel} from "./aschema-property";
+import {SchemaPropertyGroupModel} from "./schema-property";
 import {PropertyModel} from "./properties";
 
 export class DataTypePropertyModel extends PropertyModel{
index dc36d78..d72211c 100644 (file)
@@ -23,6 +23,7 @@
  */
 'use strict';
 import {PropertyBEModel} from "./properties-inputs/property-be-model";
+import {AttributeBEModel} from "./attributes-outputs/attribute-be-model";
 
 export class DataTypeModel {
 
@@ -34,6 +35,7 @@ export class DataTypeModel {
     creationTime:string;
     modificationTime:string;
     properties: Array<PropertyBEModel>;
+    attributes: Array<AttributeBEModel>;
 
     constructor(dataType:DataTypeModel) {
         if (dataType) {
@@ -43,6 +45,7 @@ export class DataTypeModel {
             this.creationTime = dataType.creationTime;
             this.modificationTime = dataType.modificationTime;
             this.properties = dataType.properties;
+            this.attributes = dataType.attributes;
         }
     }
 
index 33803b1..7524933 100644 (file)
@@ -22,7 +22,7 @@
  * Created by obarda on 1/22/2017.
  */
 'use strict';
-import {SchemaPropertyGroupModel} from "./aschema-property";
+import {SchemaPropertyGroupModel} from "./schema-property";
 
 export interface InputPropertyBase {
 
index 562db98..e267d5f 100644 (file)
@@ -24,7 +24,7 @@
 'use strict';
 import {PropertyModel} from "./properties";
 import {InputPropertyBase} from "./input-property-base";
-import {SchemaPropertyGroupModel} from "./aschema-property";
+import {SchemaPropertyGroupModel} from "./schema-property";
 
 export class InputsGroup {
     constructor(inputsObj?: InputsGroup) {
index 82f15a8..ac05f19 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 import * as _ from "lodash";
-import { SchemaPropertyGroupModel, SchemaProperty } from '../aschema-property';
+import { SchemaPropertyGroupModel, SchemaProperty } from '../schema-property';
 import { DerivedPropertyType, PropertyBEModel, PropertyFEModel } from '../../models';
 import { PROPERTY_TYPES } from 'app/utils';
 import { UUID } from "angular2-uuid";
index 909f712..347b8a1 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 import * as _ from "lodash";
-import { SchemaPropertyGroupModel, SchemaProperty } from "../aschema-property";
+import { SchemaPropertyGroupModel, SchemaProperty } from "../schema-property";
 import {PropertyFEModel} from "../../models";
 import {PROPERTY_DATA} from "../../utils/constants";
 import {InputBEModel} from "./input-be-model";
index b997ea4..aea5707 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils/constants';
-import { SchemaProperty, SchemaPropertyGroupModel } from '../aschema-property';
+import { SchemaProperty, SchemaPropertyGroupModel } from '../schema-property';
 import { ToscaPresentationData } from '../tosca-presentation';
 import { PropertyInputDetail } from './property-input-detail';
 import { Metadata } from '../metadata';
index 06c735d..3b8c2d1 100644 (file)
@@ -21,7 +21,7 @@
  */
 
 import * as _ from "lodash";
-import {SchemaPropertyGroupModel, SchemaProperty} from '../aschema-property';
+import {SchemaPropertyGroupModel, SchemaProperty} from '../schema-property';
 import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils';
 import { FilterPropertiesAssignmentData, PropertyBEModel, DerivedPropertyType, DerivedFEPropertyMap, DerivedFEProperty } from 'app/models';
 
index b87edff..e292a6c 100644 (file)
@@ -20,7 +20,7 @@
 
 'use strict';
 import * as _ from "lodash";
-import {SchemaPropertyGroupModel, SchemaProperty} from "./aschema-property";
+import {SchemaPropertyGroupModel, SchemaProperty} from "./schema-property";
 import {InputPropertyBase} from "./input-property-base";
 import {PropertyBEModel} from "./properties-inputs/property-be-model";
 
index 35211d4..e222007 100644 (file)
 
 'use strict';
 
-import { SchemaProperty } from './aschema-property';
+import {SchemaProperty} from './schema-property';
 
 export class SchemaAttributeGroupModel {
-    property: SchemaAttribute;
+  property: SchemaAttribute;
 
-    constructor(schemaAttribute?: SchemaAttribute) {
-        this.property = schemaAttribute;
-    }
+  constructor(schemaAttribute?: SchemaAttribute) {
+    this.property = schemaAttribute;
+  }
 }
 
 export class SchemaAttribute extends SchemaProperty {
index 56efcdc..270a764 100644 (file)
@@ -50,8 +50,8 @@ import {ValidationOnLoadDirective} from "../directives/utils/validation-on-load/
 import {InfoTooltipDirective} from "../directives/info-tooltip/info-tooltip";
 import {SdcTabsDirective} from "../directives/sdc-tabs/sdc-tabs-directive";
 import {
-    SdcSingleTabDirective,
-    InnerSdcSingleTabDirective
+  InnerSdcSingleTabDirective,
+  SdcSingleTabDirective
 } from "../directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive";
 import {ExpandCollapseListHeaderDirective} from "../directives/utils/expand-collapse-list-header/expand-collapse-list-header";
 import {JsonExportExcelDirective} from "../directives/export-json-to-excel/export-json-to-excel";
@@ -66,9 +66,39 @@ import {LinksFactory} from "../models/graph/graph-links/links-factory";
 import {CapabilitiesListDirective} from "../directives/capabilities-and-requirements/capability/capabilities-list-directive";
 import {RequirementsListDirective} from "../directives/capabilities-and-requirements/requirement/requirements-list-directive";
 import {PreventDoubleClickDirective} from "../directives/prevent-double-click/prevent-double-click";
+// *** NG2 Components (downgraded) *** //
+import {downgradeComponent} from "@angular/upgrade/static";
+import {MenuListNg2Component} from "../ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component";
+import {TopNavComponent} from "../ng2/components/layout/top-nav/top-nav.component";
+import {ZoneContainerComponent} from "../ng2/pages/composition/graph/canvas-zone/zone-container.component";
+import {ZoneInstanceComponent} from "../ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component";
+import {CompositionPanelComponent} from 'app/ng2/pages/composition/panel/composition-panel.component';
+import {PropertiesAssignmentComponent} from "../ng2/pages/properties-assignment/properties-assignment.page.component";
+import {SearchWithAutoCompleteComponent} from "../ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component";
+import {PalettePopupPanelComponent} from "../ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component";
+import {ServicePathSelectorComponent} from '../ng2/pages/composition/graph/service-path-selector/service-path-selector.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";
+import {TileComponent} from "../ng2/components/ui/tile/tile.component";
+import {CompositionPageComponent} from "../ng2/pages/composition/composition-page.component";
+import {CatalogComponent} from "../ng2/pages/catalog/catalog.component";
+import {HomeComponent} from "../ng2/pages/home/home.component";
+import {PluginContextViewPageComponent} from "../ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component";
+import {PluginTabViewPageComponent} from "../ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component";
+import {CompositionGraphComponent} from "../ng2/pages/composition/graph/composition-graph.component";
+import {DeploymentPageComponent} from "../ng2/pages/workspace/deployment/deployment-page.component";
+import {ActivityLogComponent} from "../ng2/pages/workspace/activity-log/activity-log.component";
+import {ToscaArtifactPageComponent} from "../ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component";
+import {InformationArtifactPageComponent} from "../ng2/pages/workspace/information-artifact/information-artifact-page.component";
+import {AttributesComponent} from "../view-models/workspace/tabs/attributes/attributes.component";
+import {DeploymentArtifactsPageComponent} from "../ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component";
+import {ReqAndCapabilitiesComponent} from "../ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component";
+import {DistributionComponent} from '../ng2/pages/workspace/disribution/distribution.component';
+import {AttributesOutputsComponent} from "../ng2/pages/attributes-outputs/attributes-outputs.page.component";
 
-let moduleName:string = 'Sdc.Directives';
-let directiveModule:ng.IModule = angular.module(moduleName, []);
+let moduleName: string = 'Sdc.Directives';
+let directiveModule: ng.IModule = angular.module(moduleName, []);
 
 directiveModule.directive('clickedOutside', ClickedOutsideDirective.factory);
 directiveModule.directive('loader', LoaderDirective.factory);
@@ -135,178 +165,152 @@ directiveModule.directive('capabilitiesList', CapabilitiesListDirective.factory)
 directiveModule.directive('requirementsList', RequirementsListDirective.factory);
 
 
-// *** NG2 Components (downgraded) *** //
-import {downgradeComponent} from "@angular/upgrade/static";
-import {MenuListNg2Component} from "../ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component";
-import {TopNavComponent} from "../ng2/components/layout/top-nav/top-nav.component";
-import {ZoneContainerComponent} from "../ng2/pages/composition/graph/canvas-zone/zone-container.component";
-import {ZoneInstanceComponent} from "../ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component";
-import {CompositionPanelComponent} from 'app/ng2/pages/composition/panel/composition-panel.component';
-import {PropertiesAssignmentComponent} from "../ng2/pages/properties-assignment/properties-assignment.page.component";
-import {SearchWithAutoCompleteComponent} from "../ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component";
-import {PalettePopupPanelComponent} from "../ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component";
-import {ServicePathSelectorComponent} from '../ng2/pages/composition/graph/service-path-selector/service-path-selector.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";
-import {TileComponent} from "../ng2/components/ui/tile/tile.component";
-import {CompositionPageComponent} from "../ng2/pages/composition/composition-page.component";
-import {CatalogComponent} from "../ng2/pages/catalog/catalog.component";
-import {HomeComponent} from "../ng2/pages/home/home.component";
-import {PluginContextViewPageComponent} from "../ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component";
-import {PluginTabViewPageComponent} from "../ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component";
-import {CompositionGraphComponent} from "../ng2/pages/composition/graph/composition-graph.component";
-import {DeploymentPageComponent} from "../ng2/pages/workspace/deployment/deployment-page.component";
-import {ActivityLogComponent} from "../ng2/pages/workspace/activity-log/activity-log.component";
-import {ToscaArtifactPageComponent} from "../ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component";
-import {InformationArtifactPageComponent} from "../ng2/pages/workspace/information-artifact/information-artifact-page.component";
-import {AttributesComponent} from "../ng2/pages/workspace/attributes/attributes.component";
-import {DeploymentArtifactsPageComponent} from "../ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component";
-import { ReqAndCapabilitiesComponent } from "../ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component";
-import { DistributionComponent } from '../ng2/pages/workspace/disribution/distribution.component';
-
 directiveModule.directive('menuListNg2', downgradeComponent({
-    component: MenuListNg2Component,
-    inputs: ['props']
+  component: MenuListNg2Component,
+  inputs: ['props']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('topNav', downgradeComponent({
-    component: TopNavComponent,
-    inputs: ['version', 'menuModel', 'topLvlSelectedIndex', 'hideSearch', 'searchTerm', 'notificationIconCallback', 'unsavedChanges', 'unsavedChangesCallback'],
-    outputs: ['searchTermChange']
+  component: TopNavComponent,
+  inputs: ['version', 'menuModel', 'topLvlSelectedIndex', 'hideSearch', 'searchTerm', 'notificationIconCallback', 'unsavedChanges', 'unsavedChangesCallback'],
+  outputs: ['searchTermChange']
 }) as ng.IDirectiveFactory);
 
 directiveModule.directive('ng2ZoneContainer', downgradeComponent({
-    component: ZoneContainerComponent,
-    inputs: ['title', 'count', 'type', 'visible', 'minimized'],
-    outputs: ['minimize', 'backgroundClick']
+  component: ZoneContainerComponent,
+  inputs: ['title', 'count', 'type', 'visible', 'minimized'],
+  outputs: ['minimize', 'backgroundClick']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2ZoneInstance', downgradeComponent({
-    component: ZoneInstanceComponent,
-    inputs: ['zoneInstance', 'isActive', 'activeInstanceMode', 'defaultIconText', 'isViewOnly', 'hidden', 'forceSave'],
-    outputs: ['modeChange', 'tagHandleClick', 'assignmentSaveStart', 'assignmentSaveComplete']
+  component: ZoneInstanceComponent,
+  inputs: ['zoneInstance', 'isActive', 'activeInstanceMode', 'defaultIconText', 'isViewOnly', 'hidden', 'forceSave'],
+  outputs: ['modeChange', 'tagHandleClick', 'assignmentSaveStart', 'assignmentSaveComplete']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2CompositionPanel', downgradeComponent({
-    component: CompositionPanelComponent,
-    inputs: ['isViewOnly', 'isLoading', 'isCertified', 'selectedZoneInstanceId', 'selectedZoneInstanceType', 'selectedZoneInstanceName', 'topologyTemplate'],
+  component: CompositionPanelComponent,
+  inputs: ['isViewOnly', 'isLoading', 'isCertified', 'selectedZoneInstanceId', 'selectedZoneInstanceType', 'selectedZoneInstanceName', 'topologyTemplate'],
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('propertiesAssignment', downgradeComponent({
-    component: PropertiesAssignmentComponent
+  component: PropertiesAssignmentComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('compositionPage', downgradeComponent({
-    component: CompositionPageComponent
+  component: CompositionPageComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('activityLog', downgradeComponent({
-    component: ActivityLogComponent
+  component: ActivityLogComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('distribution', downgradeComponent({
-    component: DistributionComponent
+  component: DistributionComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('attributes', downgradeComponent({
-    component: AttributesComponent
+  component: AttributesComponent
+}) as angular.IDirectiveFactory);
+
+directiveModule.directive('attributesOutputs', downgradeComponent({
+  component: AttributesOutputsComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('reqAndCapabilities', downgradeComponent({
-    component: ReqAndCapabilitiesComponent
+  component: ReqAndCapabilitiesComponent
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2SearchWithAutocomplete', downgradeComponent({
-    component: SearchWithAutoCompleteComponent,
-    inputs: ['searchPlaceholder', 'searchBarClass', 'autoCompleteValues'],
-    outputs: ['searchChanged', 'searchButtonClicked']
+  component: SearchWithAutoCompleteComponent,
+  inputs: ['searchPlaceholder', 'searchBarClass', 'autoCompleteValues'],
+  outputs: ['searchChanged', 'searchButtonClicked']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2PalettePopupPanel', downgradeComponent({
-    component: PalettePopupPanelComponent,
-    inputs: [],
-    outputs: []
+  component: PalettePopupPanelComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2ServicePathSelector', downgradeComponent({
-    component: ServicePathSelectorComponent,
-    inputs: ['drawPath', 'deletePaths', 'service', 'selectedPathId'],
-    outputs: []
+  component: ServicePathSelectorComponent,
+  inputs: ['drawPath', 'deletePaths', 'service', 'selectedPathId'],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('interfaceOperation', downgradeComponent({
-    component: InterfaceOperationComponent,
-    inputs: ['component', 'readonly'],
-    outputs: []
+  component: InterfaceOperationComponent,
+  inputs: ['component', 'readonly'],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2MultilineEllipsis', downgradeComponent({
-    component: MultilineEllipsisComponent,
-    inputs: ['lines', 'lineHeight', 'className'],
-    outputs: ['hasEllipsisChanged']
+  component: MultilineEllipsisComponent,
+  inputs: ['lines', 'lineHeight', 'className'],
+  outputs: ['hasEllipsisChanged']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('ng2UiTile', downgradeComponent({
-    component: TileComponent,
-    inputs: ['component'],
-    outputs: ['onTileClick']
+  component: TileComponent,
+  inputs: ['component'],
+  outputs: ['onTileClick']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('pluginFrame', downgradeComponent({
-    component: PluginFrameComponent,
-    inputs: ['plugin', 'queryParams'],
-    outputs: ['onLoadingDone']
+  component: PluginFrameComponent,
+  inputs: ['plugin', 'queryParams'],
+  outputs: ['onLoadingDone']
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('catalogPage', downgradeComponent({
-    component: CatalogComponent,
-    inputs: [],
-    outputs: []
+  component: CatalogComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('homePage', downgradeComponent({
-    component: HomeComponent,
-    inputs: [],
-    outputs: []
+  component: HomeComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('pluginContextView', downgradeComponent({
-    component: PluginContextViewPageComponent,
-    inputs: [],
-    outputs: []
+  component: PluginContextViewPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('pluginTabView', downgradeComponent({
-    component: PluginTabViewPageComponent,
-    inputs: [],
-    outputs: []
+  component: PluginTabViewPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('compositionGraph', downgradeComponent({
-    component: CompositionGraphComponent,
-    inputs: ['topologyTemplate', 'isViewOnly'],
-    outputs: []
+  component: CompositionGraphComponent,
+  inputs: ['topologyTemplate', 'isViewOnly'],
+  outputs: []
 }) as angular.IDirectiveFactory);
 directiveModule.directive('toscaArtifactPage', downgradeComponent({
-    component: ToscaArtifactPageComponent,
-    inputs: [],
-    outputs: []
+  component: ToscaArtifactPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('deploymentPage', downgradeComponent({
-    component: DeploymentPageComponent,
-    inputs: [],
-    outputs: []
+  component: DeploymentPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 
 directiveModule.directive('informationArtifactPage', downgradeComponent({
-    component: InformationArtifactPageComponent,
-    inputs: [],
-    outputs: []
+  component: InformationArtifactPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
 directiveModule.directive('deploymentArtifactPage', downgradeComponent({
-    component: DeploymentArtifactsPageComponent,
-    inputs: [],
-    outputs: []
+  component: DeploymentArtifactsPageComponent,
+  inputs: [],
+  outputs: []
 }) as angular.IDirectiveFactory);
\ No newline at end of file
index ac8a9b6..e8dde94 100644 (file)
  * ============LICENSE_END=========================================================
  */
 
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { BrowserModule } from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {BrowserModule} from '@angular/platform-browser';
 import { NgModule, APP_INITIALIZER } from '@angular/core';
-import { FormsModule } from '@angular/forms';
+import {FormsModule} from '@angular/forms';
 import { forwardRef } from '@angular/core';
-import { AppComponent } from './app.component';
-import { UpgradeAdapter } from '@angular/upgrade';
-import { UpgradeModule } from '@angular/upgrade/static';
+import {AppComponent} from './app.component';
+import {UpgradeAdapter} from '@angular/upgrade';
+import {UpgradeModule} from '@angular/upgrade/static';
 import { SdcUiComponentsModule, SdcUiComponents } from 'onap-ui-angular';
-import { PropertiesAssignmentModule } from './pages/properties-assignment/properties-assignment.module';
+import {PropertiesAssignmentModule} from './pages/properties-assignment/properties-assignment.module';
 import {
     DataTypesServiceProvider, CookieServiceProvider, StateServiceFactory,
     StateParamsServiceFactory, ScopeServiceFactory,
     NotificationServiceProvider, ComponentFactoryProvider
 } from './utils/ng1-upgraded-provider';
-import { ConfigService } from './services/config.service';
-import { AuthenticationService } from './services/authentication.service';
-import { Cookie2Service } from './services/cookie.service';
-import { ComponentServiceNg2 } from './services/component-services/component.service';
-import { ComponentServiceFactoryNg2 } from './services/component-services/component.service.factory';
-import { ServiceServiceNg2 } from './services/component-services/service.service';
-import { ComponentInstanceServiceNg2 } from './services/component-instance-services/component-instance.service';
-import { ModalService } from './services/modal.service';
-import { UiElementsModule } from './components/ui/ui-elements.module';
-import { ConnectionWizardModule } from './pages/composition/graph/connection-wizard/connection-wizard.module';
-import { InterfaceOperationModule } from './pages/interface-operation/interface-operation.module';
-import { OperationCreatorModule } from './pages/interface-operation/operation-creator/operation-creator.module';
-import { LayoutModule } from './components/layout/layout.module';
-import { UserService } from './services/user.service';
-import { DynamicComponentService } from './services/dynamic-component.service';
-import { SdcConfig } from './config/sdc-config.config';
-import { SdcMenu } from './config/sdc-menu.config';
-import { TranslateModule } from './shared/translator/translate.module';
-import { TranslationServiceConfig } from './config/translation.service.config';
-import { MultilineEllipsisModule } from './shared/multiline-ellipsis/multiline-ellipsis.module';
-import { ServicePathCreatorModule } from './pages/composition/graph/service-path-creator/service-path-creator.module';
-import { ServicePathsListModule } from './pages/composition/graph/service-paths-list/service-paths-list.module';
+import {ConfigService} from './services/config.service';
+import {AuthenticationService} from './services/authentication.service';
+import {Cookie2Service} from './services/cookie.service';
+import {ComponentServiceNg2} from './services/component-services/component.service';
+import {ComponentServiceFactoryNg2} from './services/component-services/component.service.factory';
+import {ServiceServiceNg2} from './services/component-services/service.service';
+import {ComponentInstanceServiceNg2} from './services/component-instance-services/component-instance.service';
+import {ModalService} from './services/modal.service';
+import {UiElementsModule} from './components/ui/ui-elements.module';
+import {ConnectionWizardModule} from './pages/composition/graph/connection-wizard/connection-wizard.module';
+import {InterfaceOperationModule} from './pages/interface-operation/interface-operation.module';
+import {OperationCreatorModule} from './pages/interface-operation/operation-creator/operation-creator.module';
+import {LayoutModule} from './components/layout/layout.module';
+import {UserService} from './services/user.service';
+import {DynamicComponentService} from './services/dynamic-component.service';
+import {SdcConfig} from './config/sdc-config.config';
+import {SdcMenu} from './config/sdc-menu.config';
+import {TranslateModule} from './shared/translator/translate.module';
+import {TranslationServiceConfig} from './config/translation.service.config';
+import {MultilineEllipsisModule} from './shared/multiline-ellipsis/multiline-ellipsis.module';
+import {ServicePathCreatorModule} from './pages/composition/graph/service-path-creator/service-path-creator.module';
+import {ServicePathsListModule} from './pages/composition/graph/service-paths-list/service-paths-list.module';
 import { ServicePathSelectorModule } from 'app/ng2/pages/composition/graph/service-path-selector/service-path-selector.module';
 import { CompositionPanelModule } from 'app/ng2/pages/composition/panel/composition-panel.module';
-import { CatalogModule } from './pages/catalog/catalog.module';
-import { HomeModule } from './pages/home/home.module';
-import { WindowRef } from './services/window.service';
-import { CatalogService } from './services/catalog.service';
+import {CatalogModule} from './pages/catalog/catalog.module';
+import {HomeModule} from './pages/home/home.module';
+import {WindowRef} from './services/window.service';
+import {CatalogService} from './services/catalog.service';
 import { ModalsHandlerProvider } from './utils/ng1-upgraded-provider';
-import { PluginFrameModule } from './components/ui/plugin/plugin-frame.module';
-import { PluginsService } from './services/plugins.service';
-import { EventBusService } from './services/event-bus.service';
-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 {PluginFrameModule} from './components/ui/plugin/plugin-frame.module';
+import {PluginsService} from './services/plugins.service';
+import {EventBusService} from './services/event-bus.service';
+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 {WorkspaceModule} from './pages/workspace/workspace.module';
-import { ModalsModule } from './components/modals/modals.module';
+import {ModalsModule} from './components/modals/modals.module';
 import { SharingService, CacheService, HomeService } from 'app/services-ng2';
-import { ArtifactConfigService } from "./services/artifact-config.service";
-import { IUserProperties } from 'app/models';
-import { PluginsModule } from './pages/plugins/plugins-module';
+import {ArtifactConfigService} from "./services/artifact-config.service";
+import {IUserProperties} from 'app/models';
+import {PluginsModule} from './pages/plugins/plugins-module';
 import {WorkspaceNg1BridgeService} from './pages/workspace/workspace-ng1-bridge-service';
 import {NgxsModule} from '@ngxs/store';
 import {NgxsLoggerPluginModule} from '@ngxs/logger-plugin';
 import {NgxsReduxDevtoolsPluginModule} from '@ngxs/devtools-plugin';
 import {EventListenerService} from '../services/event-listener-service';
-import { HttpClientModule } from '@angular/common/http';
-import { httpInterceptorProviders } from './http-interceptor';
-import { HttpHelperService } from './services/http-hepler.service';
-import { ModulesService } from "./services/modules.service";
-import { TranslateService } from 'app/ng2/shared/translator/translate.service';
-import { FileUtilsService } from './services/file-utils.service';
-import { ImportVSPService } from './components/modals/onboarding-modal/import-vsp.service';
-import { OnboardingService } from './services/onboarding.service';
-import { ServiceConsumptionCreatorModule } from './pages/service-consumption-editor/service-consumption-editor.module';
-import { ServiceDependenciesModule } from './components/logic/service-dependencies/service-dependencies.module';
-import { ServiceDependenciesEditorModule } from './pages/service-dependencies-editor/service-dependencies-editor.module';
-import { PropertyCreatorModule } from './pages/properties-assignment/property-creator/property-creator.module';
-import { DeclareListModule } from './pages/properties-assignment/declare-list/declare-list.module';
-import { WorkflowServiceNg2 } from './services/workflow.service';
-import { ToscaTypesServiceNg2 } from "./services/tosca-types.service";
+import {HttpClientModule} from '@angular/common/http';
+import {httpInterceptorProviders} from './http-interceptor';
+import {HttpHelperService} from './services/http-hepler.service';
+import {ModulesService} from "./services/modules.service";
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+import {FileUtilsService} from './services/file-utils.service';
+import {ImportVSPService} from './components/modals/onboarding-modal/import-vsp.service';
+import {OnboardingService} from './services/onboarding.service';
+import {ServiceConsumptionCreatorModule} from './pages/service-consumption-editor/service-consumption-editor.module';
+import {ServiceDependenciesModule} from './components/logic/service-dependencies/service-dependencies.module';
+import {ServiceDependenciesEditorModule} from './pages/service-dependencies-editor/service-dependencies-editor.module';
+import {PropertyCreatorModule} from './pages/properties-assignment/property-creator/property-creator.module';
+import {DeclareListModule} from './pages/properties-assignment/declare-list/declare-list.module';
+import {WorkflowServiceNg2} from './services/workflow.service';
+import {ToscaTypesServiceNg2} from "./services/tosca-types.service";
 import {CapabilitiesFilterPropertiesEditorComponentModule} from "./pages/composition/capabilities-filter-properties-editor/capabilities-filter-properties-editor.module";
 import {InterfaceOperationHandlerModule} from "./pages/composition/interface-operatons/operation-creator/interface-operation-handler.module";
+import {AttributesOutputsModule} from "./pages/attributes-outputs/attributes-outputs.module";
 
 
 declare const __ENV__: string;
@@ -105,128 +106,128 @@ export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
 
 export function configServiceFactory(config: ConfigService, authService: AuthenticationService, eventListener: EventListenerService) {
 
-    return () => {
-        return authService.authenticate().toPromise()
-            .then((userInfo: IUserProperties) => {
-                authService.setLoggedinUser(userInfo);
-                return Promise.all([
-                    config.loadSdcSetupData(),
-                    config.loadValidationConfiguration(),
-                    config.loadPluginsConfiguration(),
-                ])
-            }).then(() => {
-                eventListener.notifyObservers('ON_FINISH_LOADING');
-            })
-            .catch(() => {
-                console.log('AUTH FAILED! from app module');
-            });
-    };
+  return () => {
+    return authService.authenticate().toPromise()
+    .then((userInfo: IUserProperties) => {
+      authService.setLoggedinUser(userInfo);
+      return Promise.all([
+        config.loadSdcSetupData(),
+        config.loadValidationConfiguration(),
+        config.loadPluginsConfiguration(),
+      ])
+    }).then(() => {
+      eventListener.notifyObservers('ON_FINISH_LOADING');
+    })
+    .catch(() => {
+      console.log('AUTH FAILED! from app module');
+    });
+  };
 }
 
 @NgModule({
-    declarations: [
-        AppComponent
-    ],
-    imports: [
-        BrowserAnimationsModule,
-        BrowserModule,
-        UpgradeModule,
-        FormsModule,
-        HttpClientModule,
-        LayoutModule,
-        TranslateModule,
-        MultilineEllipsisModule,
-        UiElementsModule,
-        CompositionPanelModule,
-        SdcUiComponentsModule,
-        AutomatedUpgradeModule,
+  declarations: [
+    AppComponent
+  ],
+  imports: [
+    BrowserAnimationsModule,
+    BrowserModule,
+    UpgradeModule,
+    FormsModule,
+    HttpClientModule,
+    LayoutModule,
+    TranslateModule,
+    MultilineEllipsisModule,
+    UiElementsModule,
+    CompositionPanelModule,
+    SdcUiComponentsModule,
+    AutomatedUpgradeModule,
 
-        // We need to import them here since we use them in angular1
-        ConnectionWizardModule,
-        PropertiesAssignmentModule,
-        PropertyCreatorModule,
-        DeclareListModule,
-        PluginFrameModule,
-        PluginsModule,
-        InterfaceOperationModule,
-        OperationCreatorModule,
+    // We need to import them here since we use them in angular1
+    ConnectionWizardModule,
+    PropertiesAssignmentModule,
+    AttributesOutputsModule,
+    PropertyCreatorModule,
+    DeclareListModule,
+    PluginFrameModule,
+    PluginsModule,
+    InterfaceOperationModule,
+    OperationCreatorModule,
         InterfaceOperationHandlerModule,
-        ServicePathCreatorModule,
-        ServicePathsListModule,
-        ServicePathSelectorModule,
-        ServiceConsumptionCreatorModule,
-        ServiceDependenciesModule,
-        ServiceDependenciesEditorModule,
-        CapabilitiesFilterPropertiesEditorComponentModule,
-        WorkspaceModule,
-        ModalsModule,
-        CatalogModule,
-        HomeModule,
-        NgxsModule.forRoot([]),
-        NgxsLoggerPluginModule.forRoot({ logger: console, collapsed: false }),
-        NgxsReduxDevtoolsPluginModule.forRoot({
-            disabled: __ENV__ === 'prod'
-        })
-    ],
-    exports: [],
-    entryComponents: [
-    ],
-    providers: [
-        WindowRef,
-        httpInterceptorProviders,
-        DataTypesServiceProvider,
-        SharingService,
-        CacheService,
-        HomeService,
-        ArtifactConfigService,
-        ComponentFactoryProvider,
-        CookieServiceProvider,
-        StateServiceFactory,
-        StateParamsServiceFactory,
-        ScopeServiceFactory,
-        NotificationServiceProvider,
-        ModalsHandlerProvider,
-        UserService,
-        Cookie2Service,
-        ConfigService,
-        ComponentServiceNg2,
-        ComponentServiceFactoryNg2,
-        ModalService,
-        ImportVSPService,
-        OnboardingService,
-        ServiceServiceNg2,
-        AutomatedUpgradeService,
-        WorkflowServiceNg2,
-        ToscaTypesServiceNg2,
-        WorkspaceNg1BridgeService,
-        HttpHelperService,
-        AuthenticationService,
-        PoliciesService,
-        GroupsService,
-        ModulesService,
-        DynamicComponentService,
-        SdcConfig,
-        SdcMenu,
-        ComponentInstanceServiceNg2,
-        EventListenerService,
-        TranslationServiceConfig,
-        TranslateService,
-        PluginsService,
-        CatalogService,
-        EventBusService,
-        FileUtilsService,
-        {
-            provide: APP_INITIALIZER,
-            useFactory: configServiceFactory,
-            deps: [ConfigService, AuthenticationService, EventListenerService],
-            multi: true
-        },
-    ],
-    bootstrap: [AppComponent]
+    ServicePathCreatorModule,
+    ServicePathsListModule,
+    ServicePathSelectorModule,
+    ServiceConsumptionCreatorModule,
+    ServiceDependenciesModule,
+    ServiceDependenciesEditorModule,
+    CapabilitiesFilterPropertiesEditorComponentModule,
+    WorkspaceModule,
+    ModalsModule,
+    CatalogModule,
+    HomeModule,
+    NgxsModule.forRoot([]),
+    NgxsLoggerPluginModule.forRoot({logger: console, collapsed: false}),
+    NgxsReduxDevtoolsPluginModule.forRoot({
+      disabled: __ENV__ === 'prod'
+    })
+  ],
+  exports: [],
+  entryComponents: [],
+  providers: [
+    WindowRef,
+    httpInterceptorProviders,
+    DataTypesServiceProvider,
+    SharingService,
+    CacheService,
+    HomeService,
+    ArtifactConfigService,
+    ComponentFactoryProvider,
+    CookieServiceProvider,
+    StateServiceFactory,
+    StateParamsServiceFactory,
+    ScopeServiceFactory,
+    NotificationServiceProvider,
+    ModalsHandlerProvider,
+    UserService,
+    Cookie2Service,
+    ConfigService,
+    ComponentServiceNg2,
+    ComponentServiceFactoryNg2,
+    ModalService,
+    ImportVSPService,
+    OnboardingService,
+    ServiceServiceNg2,
+    AutomatedUpgradeService,
+    WorkflowServiceNg2,
+    ToscaTypesServiceNg2,
+    WorkspaceNg1BridgeService,
+    HttpHelperService,
+    AuthenticationService,
+    PoliciesService,
+    GroupsService,
+    ModulesService,
+    DynamicComponentService,
+    SdcConfig,
+    SdcMenu,
+    ComponentInstanceServiceNg2,
+    EventListenerService,
+    TranslationServiceConfig,
+    TranslateService,
+    PluginsService,
+    CatalogService,
+    EventBusService,
+    FileUtilsService,
+    {
+      provide: APP_INITIALIZER,
+      useFactory: configServiceFactory,
+      deps: [ConfigService, AuthenticationService, EventListenerService],
+      multi: true
+    },
+  ],
+  bootstrap: [AppComponent]
 })
 
 export class AppModule {
-    constructor(public upgrade: UpgradeModule) {
+  constructor(public upgrade: UpgradeModule) {
 
-    }
+  }
 }
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/attribute-table.module.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/attribute-table.module.ts
new file mode 100644 (file)
index 0000000..5f4b157
--- /dev/null
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {NgModule} from "@angular/core";
+import {AttributesTableComponent} from "./attributes-table.component";
+import {DynamicAttributeComponent} from "./dynamic-attribute/dynamic-attribute.component";
+import {FormsModule} from "@angular/forms";
+import {UiElementsModule} from "../../ui/ui-elements.module";
+import {CommonModule} from "@angular/common";
+import {FilterChildAttributesPipe} from "./pipes/filterChildAttributes.pipe";
+import {GlobalPipesModule} from "../../../pipes/global-pipes.module";
+import {MultilineEllipsisModule} from "../../../shared/multiline-ellipsis/multiline-ellipsis.module";
+import {AttributesService} from "../../../services/attributes.service";
+
+@NgModule({
+  imports: [
+    FormsModule,
+    CommonModule,
+    GlobalPipesModule,
+    UiElementsModule,
+    MultilineEllipsisModule
+  ],
+  declarations: [
+    FilterChildAttributesPipe,
+    DynamicAttributeComponent,
+    AttributesTableComponent
+  ],
+  exports: [AttributesTableComponent, DynamicAttributeComponent],
+  providers: [FilterChildAttributesPipe, AttributesService]
+})
+export class AttributeTableModule {
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.html b/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.html
new file mode 100644 (file)
index 0000000..1115620
--- /dev/null
@@ -0,0 +1,97 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ -->
+
+<div class="attributes-table">
+    <loader [display]="isLoading" [size]="'large'" [relative]="true" [loaderDelay]="500"></loader>
+    <div class="table-header">
+        <div class="table-cell col1" (click)="sort('name')">Attribute Name
+            <span *ngIf="sortBy === 'name'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}">
+            </span>
+        </div>
+        <div class="table-cell col2" (click)="sort('type')" *ngIf="!hideAttributeType">Type
+            <span *ngIf="sortBy === 'type'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}">
+            </span>
+        </div>
+        <div class="table-cell col3" (click)="sort('schema.property.type')" *ngIf="!hideAttributeType">EntrySchema
+            <span *ngIf="sortBy === 'schema.property.type'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}">
+            </span>
+        </div>
+        <div class="table-cell valueCol">Value</div>
+    </div>
+    <div class="table-body" [ngClass]="{'view-mode': readonly}">
+        <div class="no-data" *ngIf="!feAttributesMap || !(feAttributesMap | keys).length">No data to display</div>
+
+        <ng-container *ngFor="let instanceId of feAttributesMap | keys; trackBy:vspId">
+            <!-- Icon & Instance Name -->
+            <div class="table-rows-header white-sub-header" *ngIf="feInstanceNamesMap">
+                <span [ngClass]="['prop-instance-icon', feInstanceNamesMap[instanceId].iconClass, 'small']"></span>
+                {{feInstanceNamesMap[instanceId].name}}
+                <div class="sprite-new archive-label" *ngIf="feInstanceNamesMap[instanceId].originArchived == true"></div>
+            </div>
+
+            <div class="table-row" *ngFor="let property of feAttributesMap[instanceId] | searchFilter:'name':searchTerm | propertiesOrderBy:{path: path, direction: direction}; trackBy:property?.name "
+                 (click)="onClickAttributeRow(property, instanceId, $event)" [ngClass]="{'selected': selectedAttributeId && selectedAttributeId === property.name, 'readonly': property.isDisabled || property.isDeclared}">
+
+                <div class="table-cell col1" [ngClass]="{'filtered':property.name === attributeNameSearchText}" [class.round-checkbox]="property.isDeclared">
+                <!-- Attribute Name -->
+                    <div class="attribute-name">
+                        <checkbox *ngIf="hasDeclareOption" [(checked)]="property.isSelected" [disabled]="property.isDisabled || property.isDeclared || readonly"
+                                  (checkedChange)="attributeChecked(property)" [attr.data-tests-id]="property.name"></checkbox>
+                        <div class="inner-cell-div-multiline" tooltip="{{property.name}}">
+                            <multiline-ellipsis className="table-cell-multiline-ellipsis" [lines]="2">{{property.name}}</multiline-ellipsis>
+                        </div>
+                    </div>
+                    <span *ngIf="property.description" class="property-description-icon sprite-new show-desc" tooltip="{{property.description}}"
+                        tooltipDelay="0"></span>
+                </div>
+                <!-- Attribute Type -->
+                <div class="table-cell col2" *ngIf="!hideAttributeType">
+                    <div class="inner-cell-div" tooltip="{{property.type | contentAfterLastDot}}">
+                        <span>{{property.type | contentAfterLastDot}}</span>
+                    </div>
+                </div>
+                <!-- Attribute ES (Entry Schema) -->
+                <div class="table-cell col3" *ngIf="!hideAttributeType">
+                    <div *ngIf="property.schema && property.schema.property && property.schema.property.type" class="inner-cell-div" tooltip="{{property.schema.property.type | contentAfterLastDot}}">
+                        <span>{{property.schema.property.type | contentAfterLastDot}}</span>
+                    </div>
+                </div>
+                <!-- Attribute Value -->
+                <div class="table-cell valueCol">
+                    <dynamic-property
+                        [selectedAttributeId]="selectedAttributeId"
+                        [hasDeclareOption]="hasDeclareOption"
+                        [canBeDeclared]="hasDeclareOption && true"
+                        [attribute]="property"
+                        [expandedChildId]="property.expandedChildPropertyId"
+                        [attributeNameSearchText]="attributeNameSearchText"
+                        [readonly]="readonly"
+                        (attributeChanged)="onAttributeChanged(property)"
+                        (expandChild)="property.updateExpandedChildPropertyId($event)"
+                        (clickOnAttributeRow)="onClickAttributeInnerRow($event, instanceId)"
+                        (checkAttribute)="attributeChecked(property, $event)"
+                        >
+                    </dynamic-property>
+                </div>
+            </div>
+        </ng-container>
+
+    </div>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.less b/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.less
new file mode 100644 (file)
index 0000000..26ae0d4
--- /dev/null
@@ -0,0 +1,264 @@
+@import './../../../../../assets/styles/mixins.less';
+@import '../../../../../assets/styles/sprite';
+@smaller-screen: ~"only screen and (max-width: 1580px)";
+
+:host /deep/ input { width:100%;}
+
+.attributes-table {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    height: 100%;
+    text-align: left;
+
+
+    .inner-cell-div {
+        text-overflow: ellipsis;
+        overflow: hidden;
+        height: 20px;
+    }
+
+    .inner-cell-div-multiline {
+        max-width: 100%;
+    }
+
+    .table-header {
+        display: flex;
+        flex-direction:row;
+        flex: 0 0 auto;
+        font-weight:bold;
+        border-top: #d2d2d2 solid 1px;
+        background-color: #f2f2f2;
+
+        .table-cell {
+            color:#191919;
+            font-size:13px;
+            .table-header-sort-arrow {
+                display: inline-block;
+                background-color: transparent;
+                border: none;
+                color: #AAA;
+                margin: 8px 0 0 5px;
+                &.up {
+                    border-left: 5px solid transparent;
+                    border-right: 5px solid transparent;
+                    border-bottom: 5px solid;
+                    height:5px;
+                }
+                &.down {
+                    border-left: 5px solid transparent;
+                    border-right: 5px solid transparent;
+                    border-top: 5px solid;
+                }
+            }
+        }
+    }
+
+    .table-rows-header {
+        border: #d2d2d2 solid 1px;
+        border-top:none;
+        display: flex;
+        align-items: center;
+        .archive-label{
+            margin-left: 10px;
+        }
+    }
+
+    .table-body {
+        display:flex;
+        flex-direction: column;
+        overflow-y:auto;
+        flex: 1;
+        background-color: @main_color_p;
+
+        .no-data {
+            border: #d2d2d2 solid 1px;
+            border-top:none;
+            text-align: center;
+            height: 100%;
+            padding: 20px;
+        }
+        /deep/.selected{
+            background-color: #e6f6fb;
+            color:  #009fdb;
+        }
+        &.view-mode{
+            /deep/ .dynamic-property-row:not(.selected){
+                background-color:#f8f8f8;
+            }
+        }
+        .table-row {
+            display: flex;
+            flex-direction:row;
+            flex: 0 0 auto;
+            &.readonly{
+                background-color: #f8f8f8;
+                cursor: auto;
+            }
+
+            &:hover:not(.selected){
+                background-color:#f8f8f8; cursor:pointer;
+                /deep/ .dynamic-property-row:not(.selected){
+                    background-color:#f8f8f8; cursor:pointer;
+                }
+            }
+
+            .selected-row {
+                background-color:#e6f6fb;
+            }
+
+            .table-cell.valueCol {
+                padding:0px;
+
+            }
+        }
+    }
+    .table-cell {
+        font-size:13px;
+        flex:1;
+        border: #d2d2d2 solid 1px;
+        border-right:none;
+        border-top:none;
+        padding:10px;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow:hidden;
+        display: flex;
+        min-height:40px;
+
+        &:last-child {
+            border-right:#d2d2d2 solid 1px;
+        }
+
+        // Column: Property Name
+        &.col1 {
+            flex: 1 0 190px;
+            max-width:300px;
+            display: flex;
+            @media @smaller-screen { flex: 0 0 25%;}
+
+            .attribute-name {
+                flex: 1;
+                display: flex;
+                overflow: hidden;
+                //max-width: 90%; fix bug 327139
+            }
+
+            .property-description-icon {
+                float: right;
+                margin-top: 4px;
+                margin-left: 15px;
+                flex: 0 0 auto;
+            }
+        }
+
+        // Column: Type
+        &.col2 {
+            flex: 0 0 150px;
+            max-width:150px;
+            @media @smaller-screen { flex: 0 0 20%;}
+        }
+
+        // Column: ES
+        &.col3 {
+            flex:0 0 120px;
+            max-width:120px;
+            @media @smaller-screen { flex: 0 0 15%;}
+        }
+
+        // Column: Value
+        &.valueCol {
+            flex: 2 0 250px;
+            display: flex;
+            @media @smaller-screen { flex: 1 0 40%;}
+        }
+
+
+        /deep/ .checkbox-container {
+            margin-right: 10px;
+        }
+
+        /deep/ &.round-checkbox {
+            .checkbox-container input[type=checkbox].checkbox-hidden {
+                &:checked ~ .checkbox-icon::before {
+                    .sprite-new;
+                    .round-checked-icon;
+                }
+                &[disabled] ~ .checkbox-icon::before {
+                    .sprite-new;
+                    .round-checked-icon.disabled;
+                    background-color:inherit;
+                    border:none;
+                    //animation: addDisabledCheck 4s linear;
+                }
+            }
+        }
+    }
+
+    .delete-button-container {
+        max-height: 24px;
+        cursor: pointer;
+    }
+
+    .filtered {
+        /deep/ .checkbox-label-content{
+            background-color: yellow;
+        }
+    }
+
+    dynamic-property {
+        width:100%;
+        &:last-child /deep/ .dynamic-property-row {
+            border-bottom:none;
+        }
+    }
+
+    .table-row {
+        /deep/ .table-cell-multiline-ellipsis .multiline-ellipsis-dots {
+            background: linear-gradient(to right, transparent 0%, #ffffff 80%);
+            padding-left: 1em;
+        }
+
+        &.selected /deep/ .table-cell-multiline-ellipsis .multiline-ellipsis-dots {
+            background: linear-gradient(to right, transparent 0%, #e6f6fb 80%);
+            padding-left: 1em;
+        }
+
+        &.readonly /deep/ .table-cell-multiline-ellipsis .multiline-ellipsis-dots {
+            background: linear-gradient(to right, transparent 0%, #f8f8f8 80%);
+            padding-left: 1em;
+        }
+
+        &:hover:not(.selected) /deep/ .table-cell-multiline-ellipsis .multiline-ellipsis-dots {
+            background: linear-gradient(to right, transparent 0%, #f8f8f8 80%);
+            padding-left: 1em;
+        }
+    }
+
+    .prop-instance-icon {
+        vertical-align: middle;
+        margin-right: 7px;
+        &.defaulticon.small {
+            background-color: @main_color_q;
+            border-radius:14px;
+        }
+        // square icons
+        &.icon-group {
+            .square-icon();
+            background-color: @main_color_a;
+
+            &::before {
+                content: "G";
+            }
+        }
+        &.icon-policy {
+            .square-icon();
+            background-color: @main_color_r;
+
+            &::before {
+                content: "P";
+            }
+        }
+
+    }
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.spec.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.spec.ts
new file mode 100644 (file)
index 0000000..e916d78
--- /dev/null
@@ -0,0 +1,190 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {NO_ERRORS_SCHEMA, SimpleChange} from '@angular/core';
+import {ComponentFixture} from '@angular/core/testing';
+import {ConfigureFn, configureTests} from '../../../../../jest/test-config.helper';
+import {ContentAfterLastDotPipe} from '../../../pipes/contentAfterLastDot.pipe';
+import {KeysPipe} from '../../../pipes/keys.pipe';
+import {SearchFilterPipe} from '../../../pipes/searchFilter.pipe';
+import {ModalService} from '../../../services/modal.service';
+import {AttributeRowSelectedEvent, AttributesTableComponent} from './attributes-table.component';
+import {AttributesService} from "../../../services/attributes.service";
+import {AttributeFEModel} from "../../../../models/attributes-outputs/attribute-fe-model";
+import {AttributeBEModel} from "app/models/attributes-outputs/attribute-be-model";
+import {DerivedFEAttribute} from "../../../../models/attributes-outputs/derived-fe-attribute";
+import {PropertiesOrderByPipe} from "app/ng2/pipes/properties-order-by.pipe";
+
+describe('attributes-table component', () => {
+
+  let fixture: ComponentFixture<AttributesTableComponent>;
+  let attributesServiceMock: Partial<AttributesService>;
+  let modalServiceMock: Partial<ModalService>;
+
+  beforeEach(
+      () => {
+        attributesServiceMock = {
+          undoDisableRelatedAttributes: jest.fn(),
+          disableRelatedAttributes: jest.fn()
+        };
+        modalServiceMock = {};
+
+        const configure: ConfigureFn = (testBed) => {
+          testBed.configureTestingModule({
+            declarations: [
+              AttributesTableComponent,
+              KeysPipe,
+              PropertiesOrderByPipe,
+              SearchFilterPipe,
+              ContentAfterLastDotPipe
+            ],
+            imports: [],
+            schemas: [NO_ERRORS_SCHEMA],
+            providers: [
+              {provide: AttributesService, useValue: attributesServiceMock},
+              {provide: ModalService, useValue: modalServiceMock}
+            ],
+          });
+        };
+
+        configureTests(configure).then((testBed) => {
+          fixture = testBed.createComponent(AttributesTableComponent);
+        });
+      }
+  );
+
+  it('When Properties assignment page is loaded, it is sorted by attribute name (acsending)', () => {
+    const fePropertiesMapValues = new SimpleChange('previousValue', 'currentValue', true);
+    const changes = {
+      fePropertiesMap: fePropertiesMapValues
+    };
+
+    // init values before ngOnChanges was called
+    fixture.componentInstance.sortBy = 'existingValue';
+
+    fixture.componentInstance.ngOnChanges(changes);
+
+    expect(fixture.componentInstance.reverse).toEqual(true);
+    expect(fixture.componentInstance.direction).toEqual(fixture.componentInstance.ascUpperLettersFirst);
+    expect(fixture.componentInstance.sortBy).toEqual('name');
+    expect(fixture.componentInstance.path.length).toEqual(1);
+    expect(fixture.componentInstance.path[0]).toEqual('name');
+  });
+
+  it('When ngOnChanges is called without fePropertiesMap,' +
+      ' sortBy will remain as it was', () => {
+    const fePropertiesMapValues = new SimpleChange('previousValue', 'currentValue', true);
+    const changes = {
+      dummyKey: fePropertiesMapValues
+    };
+
+    // init values before ngOnChanges was called
+    fixture.componentInstance.sortBy = 'existingValue';
+    fixture.componentInstance.sort = jest.fn();
+
+    fixture.componentInstance.ngOnChanges(changes);
+
+    expect(fixture.componentInstance.sortBy).toEqual('existingValue');
+  });
+
+  it('When sort is called init this.direction to 1', () => {
+    // init values
+    fixture.componentInstance.reverse = false;
+    fixture.componentInstance.direction = 0;
+    fixture.componentInstance.sortBy = 'initialize.Value';
+    fixture.componentInstance.path = [];
+
+    // call sore function
+    fixture.componentInstance.sort('initialize.Value');
+
+    // expect that
+    expect(fixture.componentInstance.reverse).toBe(true);
+    expect(fixture.componentInstance.direction).toBe(fixture.componentInstance.ascUpperLettersFirst);
+    expect(fixture.componentInstance.sortBy).toBe('initialize.Value');
+    expect(fixture.componentInstance.path.length).toBe(2);
+    expect(fixture.componentInstance.path[0]).toBe('initialize');
+    expect(fixture.componentInstance.path[1]).toBe('Value');
+  });
+
+  it('When sort is called init this.direction to -1', () => {
+    // init values
+    fixture.componentInstance.reverse = true;
+    fixture.componentInstance.direction = 0;
+    fixture.componentInstance.sortBy = 'initialize.Value';
+    fixture.componentInstance.path = [];
+
+    // call sore function
+    fixture.componentInstance.sort('initialize.Value');
+
+    // expect that
+    expect(fixture.componentInstance.reverse).toBe(false);
+    expect(fixture.componentInstance.direction).toBe(fixture.componentInstance.descLowerLettersFirst);
+  });
+
+  it('When onPropertyChanged is called, event is emitted', () => {
+    spyOn(fixture.componentInstance.emitter, 'emit');
+    fixture.componentInstance.onAttributeChanged('testProperty');
+    expect(fixture.componentInstance.emitter.emit).toHaveBeenCalledWith('testProperty');
+  });
+
+  it('When onClickPropertyRow is called, selectedPropertyId is updated and event is emitted.', () => {
+    const attributeFEModel = new AttributeFEModel(new AttributeBEModel());
+    attributeFEModel.name = 'attributeName';
+    const attributeRowSelectedEvent: AttributeRowSelectedEvent = new AttributeRowSelectedEvent(attributeFEModel, 'instanceName');
+
+    spyOn(fixture.componentInstance.selectAttributeRow, 'emit');
+    fixture.componentInstance.onClickAttributeRow(attributeFEModel, 'instanceName');
+
+    expect(fixture.componentInstance.selectedAttributeId).toBe('attributeName');
+    expect(fixture.componentInstance.selectAttributeRow.emit).toHaveBeenCalledWith(attributeRowSelectedEvent);
+  });
+
+  it('When onClickPropertyInnerRow is called, event is emitted.', () => {
+    const derivedFEProperty = new DerivedFEAttribute(new AttributeBEModel());
+    const attributeRowSelectedEvent: AttributeRowSelectedEvent = new AttributeRowSelectedEvent(derivedFEProperty, 'instanceName');
+    spyOn(fixture.componentInstance.selectAttributeRow, 'emit');
+    fixture.componentInstance.onClickAttributeInnerRow(derivedFEProperty, 'instanceName');
+
+    expect(fixture.componentInstance.selectAttributeRow.emit).toHaveBeenCalledWith(attributeRowSelectedEvent);
+  });
+
+  it('When attributeChecked is called, attributesService.undoDisableRelatedProperties is called and event is emitted.', () => {
+
+    const attributeFEModel = new AttributeFEModel(new AttributeBEModel());
+    attributeFEModel.isSelected = false;
+
+    spyOn(fixture.componentInstance.updateCheckedAttributeCount, 'emit');
+    fixture.componentInstance.attributeChecked(attributeFEModel);
+    expect(attributesServiceMock.undoDisableRelatedAttributes).toHaveBeenCalledWith(attributeFEModel, undefined);
+    expect(fixture.componentInstance.updateCheckedAttributeCount.emit).toHaveBeenCalledWith(false);
+  });
+
+  it('When attributeChecked is called, attributesService.disableRelatedProperties is called and event is emitted.', () => {
+
+    const attributeFEModel = new AttributeFEModel(new AttributeBEModel());
+    attributeFEModel.isSelected = true;
+
+    spyOn(fixture.componentInstance.updateCheckedAttributeCount, 'emit');
+    fixture.componentInstance.attributeChecked(attributeFEModel);
+    expect(attributesServiceMock.disableRelatedAttributes).toHaveBeenCalledWith(attributeFEModel, undefined);
+    expect(fixture.componentInstance.updateCheckedAttributeCount.emit).toHaveBeenCalledWith(true);
+  });
+
+});
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/attributes-table.component.ts
new file mode 100644 (file)
index 0000000..000e2cc
--- /dev/null
@@ -0,0 +1,116 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core';
+import {InstanceFeDetails} from '../../../../models/instance-fe-details';
+import {InstanceFeAttributesMap} from "../../../../models/attributes-outputs/attribute-fe-map";
+import {AttributeFEModel} from "../../../../models/attributes-outputs/attribute-fe-model";
+import {AttributesService} from "../../../services/attributes.service";
+import {DerivedFEAttribute} from "../../../../models/attributes-outputs/derived-fe-attribute";
+
+@Component({
+  selector: 'attributes-table',
+  templateUrl: './attributes-table.component.html',
+  styleUrls: ['./attributes-table.component.less']
+})
+export class AttributesTableComponent implements OnChanges {
+
+  @Input() feAttributesMap: InstanceFeAttributesMap;
+  @Input() feInstanceNamesMap: Map<string, InstanceFeDetails>;
+  @Input() selectedAttributeId: string;
+  @Input() attributeNameSearchText: string;
+  @Input() searchTerm: string;
+  @Input() readonly: boolean;
+  @Input() isLoading: boolean;
+  @Input() hasDeclareOption: boolean;
+  @Input() hideAttributeType: boolean;
+  @Input() showDelete: boolean;
+
+  @Output('attributeChanged') emitter: EventEmitter<AttributeFEModel> = new EventEmitter<AttributeFEModel>();
+  @Output() selectAttributeRow: EventEmitter<AttributeRowSelectedEvent> = new EventEmitter<AttributeRowSelectedEvent>();
+  @Output() updateCheckedAttributeCount: EventEmitter<boolean> = new EventEmitter<boolean>(); // only for hasDeclareOption
+  @Output() updateCheckedChildAttributeCount: EventEmitter<boolean> = new EventEmitter<boolean>();//only for hasDeclareListOption
+  @Output() deleteAttribute: EventEmitter<AttributeFEModel> = new EventEmitter<AttributeFEModel>();
+
+  sortBy: string;
+  reverse: boolean;
+  direction: number;
+  path: string[];
+
+  readonly ascUpperLettersFirst = 1;
+  readonly descLowerLettersFirst = -1;
+
+  constructor(private attributesService: AttributesService) {
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.fePropertiesMap) {
+      this.sortBy = '';
+      this.sort('name');
+    }
+  }
+
+  sort(sortBy) {
+    this.reverse = (this.sortBy === sortBy) ? !this.reverse : true;
+    this.direction = this.reverse ? this.ascUpperLettersFirst : this.descLowerLettersFirst;
+    this.sortBy = sortBy;
+    this.path = sortBy.split('.');
+  }
+
+  onAttributeChanged = (attribute) => {
+    this.emitter.emit(attribute);
+  }
+
+  // Click on main row (row of AttributeFEModel)
+  onClickAttributeRow = (attribute: AttributeFEModel, instanceName: string) => {
+    this.selectedAttributeId = attribute.name;
+    const attributeRowSelectedEvent: AttributeRowSelectedEvent = new AttributeRowSelectedEvent(attribute, instanceName);
+    this.selectAttributeRow.emit(attributeRowSelectedEvent);
+  }
+
+  // Click on inner row (row of DerivedFEAttribute)
+  onClickAttributeInnerRow = (attribute: DerivedFEAttribute, instanceName: string) => {
+    const attributeRowSelectedEvent: AttributeRowSelectedEvent = new AttributeRowSelectedEvent(attribute, instanceName);
+    this.selectAttributeRow.emit(attributeRowSelectedEvent);
+  }
+
+  attributeChecked = (attrib: AttributeFEModel, childAttribName?: string) => {
+    const isChecked: boolean = (!childAttribName) ? attrib.isSelected : attrib.flattenedChildren.find((attrib) => attrib.attributesName == childAttribName).isSelected;
+
+    if (isChecked) {
+      this.attributesService.disableRelatedAttributes(attrib, childAttribName);
+    } else {
+      this.attributesService.undoDisableRelatedAttributes(attrib, childAttribName);
+    }
+    this.updateCheckedAttributeCount.emit(isChecked);
+
+  }
+
+}
+
+export class AttributeRowSelectedEvent {
+  attributeModel: AttributeFEModel | DerivedFEAttribute;
+  instanceName: string;
+
+  constructor(attributeModel: AttributeFEModel | DerivedFEAttribute, instanceName: string) {
+    this.attributeModel = attributeModel;
+    this.instanceName = instanceName;
+  }
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html
new file mode 100644 (file)
index 0000000..7f271af
--- /dev/null
@@ -0,0 +1,102 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ -->
+
+<div *ngIf="!attribute.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn]
+     [ngClass]="{'selected': selectedAttributeId && selectedAttributeId === attribute.propertiesName, 'readonly':  attribute.isDisabled ||attribute.isDeclared}"
+     [class.with-top-border]="attribute.isChildOfListOrMap"
+     (click)="onClickPropertyRow(attribute, $event)">
+    <!-- LEFT CELL -->
+    <ng-container *ngIf="!isAttributeFEModel">
+        <div class="table-cell" *ngIf="canBeDeclared" [ngClass]="{'filtered':attribute.name === attributeNameSearchText}" [class.round-checkbox]="attribute.isDeclared"> <!-- simple children of complex type [@checkEffect]="property.isDeclared"-->
+            <checkbox *ngIf="hasDeclareOption" [(checked)]="attribute.isSelected" [disabled]="attribute.isDisabled ||attribute.isDeclared || readonly" (checkedChange)="checkAttribute.emit(attribute.propertiesName)" ></checkbox>
+            <div class="inner-cell-div" tooltip="{{attribute.name}}"><span>{{attribute.name}}</span></div>
+        </div>
+        <div class="table-cell" *ngIf="!canBeDeclared && !attribute.isChildOfListOrMap">
+            <div class="inner-cell-div" tooltip="{{attribute.name}}"><span>{{attribute.name}}</span></div>
+        </div> <!-- simple children of complex type within map or list -->
+        <div class="table-cell map-entry" *ngIf="attribute.isChildOfListOrMap && attribType == derivedAttributeType.MAP"><!-- map left cell -->
+            <dynamic-element #mapKeyInput
+                class="value-input"
+                pattern="validationUtils.getValidationPattern(string)"
+                [value]="attribute.mapKey"
+                type="string"
+                [name]="attribute.name"
+                (elementChanged)="mapKeyChanged.emit($event.value)"
+                [readonly]="readonly"
+                [testId]="'prop-key-' + attributeTestsId"
+            ></dynamic-element>
+        </div>
+    </ng-container>
+    <!-- RIGHT CELL OR FULL WIDTH CELL-->
+    <ng-container *ngIf="attribType == derivedAttributeType.SIMPLE || attribute.isDeclared || (attribute.isChildOfListOrMap && attribType == derivedAttributeType.MAP && attribute.schema.property.isSimpleType)">
+        <div class="table-cell">
+            <dynamic-element class="value-input"
+                pattern="validationUtils.getValidationPattern(property.type)"
+                [value]="attribute.isDeclared ? attribute.value : attribute.valueObj"
+                [type]="attribute.isDeclared ? 'string' : attribute.type"
+                [name]="attribute.name"
+                [path]="attribute.propertiesName"
+                (elementChanged)="onElementChanged($event)"
+                [readonly]="readonly || attribute.isDeclared || attribute.isDisabled"
+                [testId]="'prop-' + attributeTestsId"
+                [declared] = "attribute.isDeclared"
+                [constraints] = "constraints"
+            ></dynamic-element>
+        </div>
+    </ng-container>
+    <ng-container *ngIf="!isAttributeFEModel && attribType != derivedAttributeType.SIMPLE && !attribute.isDeclared"> <!-- right cell for complex elements, or list complex -->
+        <div class="table-cell" *ngIf="attribType == derivedAttributeType.COMPLEX">{{attribute.type | contentAfterLastDot }}</div>
+        <div class="table-cell" *ngIf="attribType == derivedAttributeType.MAP && !attribute.schema.property.isSimpleType">{{attribute.schema.property.type | contentAfterLastDot }}</div>
+    </ng-container>
+    <ng-container *ngIf="isAttributeFEModel && (attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && !attribute.isDeclared"><!-- empty, full-width table cell - for PropertyFEModel of type list or map -->
+        <div class="table-cell empty"></div>
+    </ng-container>
+    <!-- ICONS: add, delete, and expand -->
+    <ng-container *ngIf="!attribute.isDeclared">
+            <a *ngIf="(attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && !attribute.isChildOfListOrMap" class="property-icon add-item" (click)="createNewChildProperty();" [ngClass]="{'disabled':readonly || preventInsertItem(attribute)}" [attr.data-tests-id]="'add-to-list-' + attributeTestsId">Add value to list</a>
+            <span *ngIf="attribute.isChildOfListOrMap" (click)="deleteItem.emit(attribute);" class="property-icon sprite-new delete-item-icon" [ngClass]="{'disabled':readonly}" [attr.data-tests-id]="'delete-from-list-' + attributeTestsId"></span>
+            <span *ngIf="!isAttributeFEModel && (attribType == derivedAttributeType.COMPLEX || ((attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && hasChildren))" (click)="expandChildById(attribPath)" class="property-icon sprite-new round-expand-icon" [class.open]="expandedChildId.indexOf(attribPath) == 0" [attr.data-tests-id]="'expand-' + attributeTestsId" ></span>
+    </ng-container>
+
+</div>
+<!-- FLAT CHILDREN -->
+<div class="flat-children-container" *ngIf="isAttributeFEModel && !attribute.isDeclared">
+    <ng-container *ngFor="let prop of attribute.flattenedChildren | filterChildAttributes: expandedChildId; trackBy:prop?.propertiesName">
+        <dynamic-property
+            [selectedAttributeId]="selectedAttributeId"
+            [hasDeclareOption]="hasDeclareOption"
+            [canBeDeclared]="hasDeclareOption && prop.canBeDeclared"
+            [attribute]="prop"
+            [rootAttribute]="rootAttribute || attribute"
+            [expandedChildId]="expandedChildId"
+            [attributeNameSearchText]="attributeNameSearchText"
+            [readonly]="readonly"
+            [hasChildren]="getHasChildren(prop)"
+            (propertyChanged)="childValueChanged(prop)"
+            (mapKeyChanged)="updateChildKeyInParent(prop, $event)"
+            (expandChild)="expandChildById($event)"
+            (deleteItem)="deleteListOrMapItem($event)"
+            (clickOnAttributeRow)="onClickPropertyRow($event)"
+            (checkAttribute)="checkedChange($event)"
+            (addChildAttribsToParent)="addChildProps($event, prop.propertiesName)"
+            >
+        </dynamic-property>
+    </ng-container>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less
new file mode 100644 (file)
index 0000000..fd572b0
--- /dev/null
@@ -0,0 +1,82 @@
+@import '../../../../../../assets/styles/variables.less';
+.flat-children-container {
+    .dynamic-property-row {
+        /*create nested left border classes for up to 10 levels of nesting*/
+        .nested-border-loop(@i) when (@i > 0) {
+            @size: (@i - 1) *2;
+            &.nested-level-@{i} .table-cell:first-child {
+                border-left: ~"solid @{size}px #009fdb";
+            }
+            .nested-border-loop(@i - 1)
+        }
+        .nested-border-loop(10);
+    }
+    dynamic-property {
+        &:first-child .dynamic-property-row.with-top-border {
+            border-top:solid 1px #d2d2d2;
+        }
+        &:not(:last-child) .dynamic-property-row {
+            border-bottom:solid 1px #d2d2d2;
+        }
+    }
+}
+.dynamic-property-row {
+    display:flex;
+    flex-direction:row;
+    align-items: stretch;
+
+    &.readonly{
+        background-color: @tlv_color_t;
+        cursor: auto;
+    }
+    //for the case that the parent is disabled but the child is enabled
+    &:not(.readonly){
+        background-color: @main_color_p;
+    }
+
+    .table-cell {
+        flex: 1;
+        padding:9px;
+        justify-content: center;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+
+        &:first-child {
+            flex: 0 0 50%;
+            border-right:#d2d2d2 solid 1px;
+            &:only-of-type {
+                flex: 1 1 100%;
+                border-right:none;
+            }
+        }
+        &.empty {
+            height:40px;
+        }
+    }
+    .property-icon {
+        flex: 0 0 auto;
+        margin-right:10px;
+        align-self:center;
+        cursor:pointer;
+    }
+}
+
+.filtered {
+    /deep/ .checkbox-label-content{
+        background-color: yellow;
+    }
+}
+.inner-cell-div{
+    max-width: 100%;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    display: inline;
+    padding-left: 8px;
+}
+.error {
+    border: solid 1px @func_color_q;
+    color: @func_color_q;
+    outline: none;
+    box-sizing: border-box;
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts
new file mode 100644 (file)
index 0000000..39faac9
--- /dev/null
@@ -0,0 +1,232 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {Component, EventEmitter, Input, Output, ViewChild} from "@angular/core";
+import {PROPERTY_TYPES} from 'app/utils';
+import {DataTypeService} from "../../../../services/data-type.service";
+import {animate, style, transition, trigger} from '@angular/animations';
+import {IUiElementChangeEvent} from "../../../ui/form-components/ui-element-base.component";
+import {DynamicElementComponent} from "../../../ui/dynamic-element/dynamic-element.component";
+import {DerivedAttributeType} from "../../../../../models/attributes-outputs/attribute-be-model";
+import {AttributeFEModel} from "app/models/attributes-outputs/attribute-fe-model";
+import {DerivedFEAttribute} from "../../../../../models/attributes-outputs/derived-fe-attribute";
+import {AttributesUtils} from "../../../../pages/attributes-outputs/services/attributes.utils";
+
+@Component({
+  selector: 'dynamic-property',
+  templateUrl: './dynamic-attribute.component.html',
+  styleUrls: ['./dynamic-attribute.component.less'],
+  animations: [trigger('fadeIn', [transition(':enter', [style({opacity: '0'}), animate('.7s ease-out', style({opacity: '1'}))])])]
+})
+export class DynamicAttributeComponent {
+
+  derivedAttributeType = DerivedAttributeType;
+  attribType: DerivedAttributeType;
+  attribPath: string;
+  isAttributeFEModel: boolean;
+  nestedLevel: number;
+  attributeTestsId: string;
+  constraints: string[];
+
+  @Input() canBeDeclared: boolean;
+  @Input() attribute: AttributeFEModel | DerivedFEAttribute;
+  @Input() expandedChildId: string;
+  @Input() selectedAttributeId: string;
+  @Input() attributeNameSearchText: string;
+  @Input() readonly: boolean;
+  @Input() hasChildren: boolean;
+  @Input() hasDeclareOption: boolean;
+  @Input() rootAttribute: AttributeFEModel;
+
+  @Output('attributeChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
+  @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
+  @Output() checkAttribute: EventEmitter<string> = new EventEmitter<string>();
+  @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
+  @Output() clickOnAttributeRow: EventEmitter<AttributeFEModel | DerivedFEAttribute> = new EventEmitter<AttributeFEModel | DerivedFEAttribute>();
+  @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
+  @Output() addChildAttribsToParent: EventEmitter<Array<DerivedFEAttribute>> = new EventEmitter<Array<DerivedFEAttribute>>();
+
+  @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
+
+  constructor(private attributesUtils: AttributesUtils, private dataTypeService: DataTypeService) {
+  }
+
+  ngOnInit() {
+    this.isAttributeFEModel = this.attribute instanceof AttributeFEModel;
+    this.attribType = this.attribute.derivedDataType;
+    this.attribPath = (this.attribute instanceof AttributeFEModel) ? this.attribute.name : this.attribute.attributesName;
+    this.nestedLevel = (this.attribute.attributesName.match(/#/g) || []).length;
+    this.rootAttribute = (this.rootAttribute) ? this.rootAttribute : <AttributeFEModel>this.attribute;
+    this.attributeTestsId = this.getAttributeTestsId();
+
+    this.initConstraintsValues();
+
+  }
+
+  initConstraintsValues() {
+    let primitiveProperties = ['string', 'integer', 'float', 'boolean'];
+
+    if (this.attribute.constraints) {
+      this.constraints = this.attribute.constraints[0].validValues
+    }
+
+    //Complex Type
+    else if (primitiveProperties.indexOf(this.rootAttribute.type) == -1 && primitiveProperties.indexOf(this.attribute.type) >= 0) {
+      this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootAttribute.type, this.attribute.name);
+    } else {
+      this.constraints = null;
+    }
+
+  }
+
+  onClickPropertyRow = (property, event) => {
+    // Because DynamicAttributeComponent is recursive second time the event is fire event.stopPropagation = undefined
+    event && event.stopPropagation && event.stopPropagation();
+    this.clickOnAttributeRow.emit(property);
+  }
+
+  expandChildById = (id: string) => {
+    this.expandedChildId = id;
+    this.expandChild.emit(id);
+  }
+
+  checkedChange = (propName: string) => {
+    this.checkAttribute.emit(propName);
+  }
+
+  getHasChildren = (property: DerivedFEAttribute): boolean => {// enter to this function only from base property (AttributeFEModel) and check for child property if it has children
+    return _.filter((<AttributeFEModel>this.attribute).flattenedChildren, (prop: DerivedFEAttribute) => {
+      return _.startsWith(prop.attributesName + '#', property.attributesName);
+    }).length > 1;
+  }
+
+  getAttributeTestsId = () => {
+    return [this.rootAttribute.name].concat(this.rootAttribute.getParentNamesArray(this.attribute.attributesName, [], true)).join('.');
+  };
+
+  onElementChanged = (event: IUiElementChangeEvent) => {
+    this.attribute.updateValueObj(event.value, event.isValid);
+    this.emitter.emit();
+  };
+
+  createNewChildProperty = (): void => {
+
+    let newProps: Array<DerivedFEAttribute> = this.attributesUtils.createListOrMapChildren(this.attribute, "", null);
+    this.attributesUtils.assignFlattenedChildrenValues(this.attribute.valueObj, [newProps[0]], this.attribute.attributesName);
+    if (this.attribute instanceof AttributeFEModel) {
+      this.addChildProps(newProps, this.attribute.name);
+    } else {
+      this.addChildAttribsToParent.emit(newProps);
+    }
+  }
+
+  addChildProps = (newProps: Array<DerivedFEAttribute>, childPropName: string) => {
+
+    if (this.attribute instanceof AttributeFEModel) {
+      let insertIndex: number = this.attribute.getIndexOfChild(childPropName) + this.attribute.getCountOfChildren(childPropName); //insert after parent prop and existing children
+      this.attribute.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
+      this.expandChildById(newProps[0].attributesName);
+
+      this.updateMapKeyValueOnMainParent(newProps);
+      this.emitter.emit();
+    }
+  }
+
+  updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEAttribute>) {
+    if (this.attribute instanceof AttributeFEModel) {
+      const attributeFEModel: AttributeFEModel = <AttributeFEModel>this.attribute;
+      //Update only if all this attributeFEModel parents has key name
+      if (attributeFEModel.getParentNamesArray(childrenProps[0].attributesName, []).indexOf('') === -1) {
+        angular.forEach(childrenProps, (prop: DerivedFEAttribute): void => { //Update parent AttributeFEModel with value for each child, including nested props
+          attributeFEModel.childPropUpdated(prop);
+          if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
+            attributeFEModel.childPropMapKeyUpdated(prop, prop.mapKey, true);
+          }
+        }, this);
+        //grab the cumulative value for the new item from parent AttributeFEModel and assign that value to DerivedFEProp[0] (which is the list or map parent with UUID of the set we just added)
+        let parentNames = (<AttributeFEModel>attributeFEModel).getParentNamesArray(childrenProps[0].attributesName, []);
+        childrenProps[0].valueObj = _.get(attributeFEModel.valueObj, parentNames.join('.'), null);
+      }
+    }
+  }
+
+  childValueChanged = (property: DerivedFEAttribute) => { //value of child property changed
+
+    if (this.attribute instanceof AttributeFEModel) { // will always be the case
+      if (this.attribute.getParentNamesArray(property.attributesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
+        this.attribute.childPropUpdated(property);
+        this.emitter.emit();
+      }
+    }
+  }
+
+  deleteListOrMapItem = (item: DerivedFEAttribute) => {
+    if (this.attribute instanceof AttributeFEModel) {
+      this.removeValueFromParent(item);
+      this.attribute.flattenedChildren.splice(this.attribute.getIndexOfChild(item.attributesName), this.attribute.getCountOfChildren(item.attributesName));
+      this.expandChildById(item.attributesName);
+    }
+  }
+
+  removeValueFromParent = (item: DerivedFEAttribute) => {
+    if (this.attribute instanceof AttributeFEModel) {
+      let itemParent = (item.parentName == this.attribute.name)
+          ? this.attribute : this.attribute.flattenedChildren.find(prop => prop.attributesName == item.parentName);
+      if (!itemParent) {
+        return;
+      }
+
+      if (item.derivedDataType == DerivedAttributeType.MAP) {
+        const oldKey = item.getActualMapKey();
+        delete itemParent.valueObj[oldKey];
+        if (itemParent instanceof AttributeFEModel) {
+          delete itemParent.valueObjValidation[oldKey];
+          itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
+        }
+        this.attribute.childPropMapKeyUpdated(item, null);  // remove map key
+      } else {
+        const itemIndex: number = this.attribute.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.attributesName).indexOf(item.attributesName);
+        itemParent.valueObj.splice(itemIndex, 1);
+        if (itemParent instanceof AttributeFEModel) {
+          itemParent.valueObjValidation.splice(itemIndex, 1);
+          itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
+        }
+      }
+      if (itemParent instanceof AttributeFEModel) { //direct child
+        this.emitter.emit();
+      } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
+        this.childValueChanged(itemParent);
+      }
+    }
+  }
+
+  updateChildKeyInParent(childProp: DerivedFEAttribute, newMapKey: string) {
+    if (this.attribute instanceof AttributeFEModel) {
+      this.attribute.childPropMapKeyUpdated(childProp, newMapKey);
+      this.emitter.emit();
+    }
+  }
+
+  preventInsertItem = (property: DerivedFEAttribute): boolean => {
+    return property.type == PROPERTY_TYPES.MAP && Object.keys(property.valueObj).indexOf('') > -1;
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/pipes/filterChildAttributes.pipe.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/pipes/filterChildAttributes.pipe.ts
new file mode 100644 (file)
index 0000000..b2098d8
--- /dev/null
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Pipe, PipeTransform} from '@angular/core';
+import {DerivedFEAttribute} from "../../../../../models/attributes-outputs/derived-fe-attribute";
+
+@Pipe({
+  name: 'filterChildAttributes',
+})
+export class FilterChildAttributesPipe implements PipeTransform {
+  public transform(childAttributes: Array<DerivedFEAttribute>, parentId: string) {
+    if (!parentId || !childAttributes) return childAttributes;
+
+    let validParents: Array<string> = [parentId];
+    while (parentId.lastIndexOf('#') > 0) {
+      parentId = parentId.substring(0, parentId.lastIndexOf('#'));
+      validParents.push(parentId);
+    }
+    return childAttributes.filter(derivedAttrib => validParents.indexOf(derivedAttrib.parentName) > -1);
+  }
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.module.ts b/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.module.ts
new file mode 100644 (file)
index 0000000..fc6c73f
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {NgModule} from '@angular/core'
+import {BrowserModule} from '@angular/platform-browser'
+import {HierarchyNavigationComponent} from "./hierarchy-navigation.component";
+
+@NgModule({
+  imports: [BrowserModule],
+  declarations: [HierarchyNavigationComponent],
+  bootstrap: [],
+  exports: [HierarchyNavigationComponent]
+})
+export class HierarchyNavigationModule {
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html
new file mode 100644 (file)
index 0000000..fbae0e4
--- /dev/null
@@ -0,0 +1,97 @@
+<!--
+============LICENSE_START=======================================================
+*  Copyright (C) 2021 Nordix Foundation
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+-->
+
+<div class="output-attributes-table">
+  <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+  <div class="table-header">
+    <div class="table-cell col-output-attribute-name" (click)="sort('name')">Output Name
+      <span *ngIf="sortBy === 'name'" class="table-header-sort-arrow"
+            [ngClass]="{'down': reverse, 'up':!reverse}"></span>
+    </div>
+    <div class="table-cell col-output-attribute-instance" (click)="sort('instanceUniqueId')">From
+      Instance
+      <span *ngIf="sortBy === 'instanceUniqueId'" class="table-header-sort-arrow"
+            [ngClass]="{'down': reverse, 'up':!reverse}"></span>
+    </div>
+    <div class="table-cell col-output-attribute-type" (click)="sort('type')">Type
+      <span *ngIf="sortBy === 'type'" class="table-header-sort-arrow"
+            [ngClass]="{'down': reverse, 'up':!reverse}">
+            </span>
+    </div>
+    <div class="table-cell col-output-attribute-required" (click)="sort('required')"
+         *ngIf="componentType == 'SERVICE'">
+      <span tooltip="Required in Runtime" tooltipDelay="400">Req. in RT</span>
+    </div>
+    <div class="table-cell col-output-attribute-value">Value</div>
+  </div>
+  <div class="table-body">
+    <div class="no-data" *ngIf="!outputs || !outputs.length">No data to display</div>
+    <div>
+      <div class="table-row" *ngFor="let output of outputs">
+        <!-- attribute Name -->
+        <div class="table-cell col-output-attribute-name">
+          <div class="output-inner-cell-div">
+            <span class="attribute-name" tooltip="{{output.name}}">{{output.name}}</span>
+          </div>
+          <span *ngIf="output.description"
+                class="attribute-description-icon sprite-new show-desc"
+                tooltip="{{output.description}}" tooltipDelay="0"></span>
+        </div>
+        <!-- From Instance -->
+        <div class="table-cell col-output-attribute-instance">
+          <div class="output-inner-cell-div"
+               tooltip="{{instanceNamesMap[output.instanceUniqueId]?.name}}">
+            <span>{{instanceNamesMap[output.instanceUniqueId]?.name}}</span>
+          </div>
+        </div>
+        <!-- Type -->
+        <div class="table-cell col-output-attribute-type">
+          <div class="output-inner-cell-div" tooltip="{{output.type | contentAfterLastDot}}">
+            <span>{{output.type | contentAfterLastDot}}</span>
+          </div>
+        </div>
+        <!-- Required in runtime -->
+        <div class="table-cell col-output-attribute-required" *ngIf="componentType == 'SERVICE'">
+          <sdc-checkbox [(checked)]="output.required"
+                        (checkedChange)="onRequiredChanged(output, $event)"
+                        [disabled]="readonly"></sdc-checkbox>
+        </div>
+        <!-- Value -->
+        <div class="table-cell col-output-attribute-value output-value-col"
+             [class.inner-table-container]="!output.isSimpleType">
+          <dynamic-element class="value-output"
+                           *ngIf="checkInstanceFeAttributesMapIsFilled() && output.isSimpleType"
+                           pattern="null"
+                           [value]="output.value"
+                           [type]="'string'"
+                           [name]="output.name"
+                           (elementChanged)="onOutputChanged(output, $event)"
+                           [readonly]="true"
+                           [testId]="'output-' + output.name"
+                           [constraints]="getConstraints(output)">
+          </dynamic-element>
+          <div class="delete-button-container">
+            <span *ngIf="output.instanceUniqueId && !readonly" class="sprite-new delete-btn"
+                  (click)="openDeleteModal(output)"></span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less
new file mode 100644 (file)
index 0000000..56ed502
--- /dev/null
@@ -0,0 +1,189 @@
+@import './../../../../../assets/styles/variables.less';
+
+:host /deep/ output {
+  width: 100%;
+}
+
+.output-attributes-table {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  height: 100%;
+  text-align: left;
+
+  .output-inner-cell-div {
+    max-width: 100%;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    height: 20px;
+  }
+
+
+  .table-header {
+    font-weight: bold;
+    border-top: #d2d2d2 solid 1px;
+    background-color: #eaeaea;
+    color: #191919;
+
+    .table-cell {
+      font-size: 13px;
+
+      .table-header-sort-arrow {
+        display: inline-block;
+        background-color: transparent;
+        border: none;
+        color: #AAA;
+        margin: 8px 0 0 5px;
+
+        &.up {
+          border-left: 5px solid transparent;
+          border-right: 5px solid transparent;
+          border-bottom: 5px solid;
+        }
+
+        &.down {
+          border-left: 5px solid transparent;
+          border-right: 5px solid transparent;
+          border-top: 5px solid;
+        }
+      }
+    }
+
+    .output-value-col {
+      justify-content: flex-start;
+      padding: 10px;
+    }
+  }
+
+  .table-header, .table-row {
+    display: flex;
+    flex-direction: row;
+    flex: 0 0 auto;
+  }
+
+  .table-body {
+    display: flex;
+    flex-direction: column;
+    overflow-y: auto;
+    flex: 1;
+
+    .no-data {
+      border: #d2d2d2 solid 1px;
+      border-top: none;
+      text-align: center;
+      height: 100%;
+      padding: 20px;
+    }
+
+    /deep/ .selected {
+      background-color: #e6f6fb;
+      color: #009fdb;
+    }
+  }
+
+  .table-row {
+    &:hover {
+      background-color: #f8f8f8;
+      cursor: pointer;
+    }
+
+    &:last-child {
+      flex: 1 0 auto;
+    }
+
+    .selected-row {
+      background-color: #e6f6fb;
+    }
+  }
+
+  .table-cell {
+    font-size: 13px;
+    flex: 1;
+    border: #d2d2d2 solid 1px;
+    border-right: none;
+    border-top: none;
+    padding: 10px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+
+
+    &:last-child {
+      border-right: #d2d2d2 solid 1px;
+    }
+
+    &.col-output-attribute-name {
+      flex: 1 0 130px;
+      max-width: 250px;
+
+      justify-content: space-between;
+
+      .property-name {
+        flex: 1;
+      }
+
+      .property-description-icon {
+        float: right;
+        margin-top: 4px;
+        margin-left: 5px;
+        flex: 0 0 auto;
+      }
+    }
+
+    &.col-output-attribute-type {
+      flex: 0 0 140px;
+      max-width: 140px;
+    }
+
+    &.col-output-attribute-instance {
+      flex: 0 0 120px;
+      max-width: 120px;
+    }
+
+    &.col-output-attribute-required {
+      flex: 0 0 80px;
+      max-width: 80px;
+      text-align: center;
+    }
+
+    &.col-output-attribute-value {
+      .value-output {
+        flex: 1;
+        border: none;
+        background-color: inherit;
+
+        &:focus, &:active {
+          border: none;
+          outline: none;
+        }
+      }
+
+      .delete-btn {
+        flex: 0 0 auto;
+      }
+
+      .delete-button-container {
+        max-height: 24px;
+      }
+
+      &.inner-table-container {
+        padding: 0;
+
+        .delete-button-container {
+          padding: 0 8px 0 0;
+        }
+      }
+    }
+
+    &.output-value-col {
+      padding: 8px;
+    }
+
+  }
+
+  .filtered {
+    /deep/ .checkbox-label-content {
+      background-color: yellow;
+    }
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts
new file mode 100644 (file)
index 0000000..a7caeaa
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2021 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
+import {InstanceFeDetails} from "../../../../models/instance-fe-details";
+import {ModalService} from "../../../services/modal.service";
+import {DataTypeService} from "../../../services/data-type.service";
+import {TranslateService} from "../../../shared/translator/translate.service";
+import {InstanceFeAttributesMap} from "app/models/attributes-outputs/attribute-fe-map";
+import {Select} from "@ngxs/store";
+import {WorkspaceState} from "../../../store/states/workspace.state";
+import {OutputFEModel} from "app/models/attributes-outputs/output-fe-model";
+import {InputFEModel} from "../../../../models/properties-inputs/input-fe-model";
+
+@Component({
+  selector: 'outputs-table',
+  templateUrl: './outputs-table.component.html',
+  styleUrls: ['./outputs-table.component.less']
+})
+export class OutputsTableComponent implements OnInit {
+  @Select(WorkspaceState.isViewOnly)
+  isViewOnly$: boolean;
+
+  @ViewChild('componentOutputsTable')
+  private table: any;
+
+  @Input() outputs: Array<OutputFEModel>;
+  @Input() instanceNamesMap: Map<string, InstanceFeDetails>;
+  @Input() readonly: boolean;
+  @Input() isLoading: boolean;
+  @Input() componentType: string;
+  @Output() outputChanged: EventEmitter<any> = new EventEmitter<any>();
+  @Output() deleteOutput: EventEmitter<any> = new EventEmitter<any>();
+  @Input() feAttributesMap: InstanceFeAttributesMap;
+
+  deleteMsgTitle: string;
+  deleteMsgBodyTxt: string;
+  modalDeleteBtn: string;
+  modalCancelBtn: string;
+  sortBy: string;
+  reverse: boolean;
+  selectedOutputToDelete: OutputFEModel;
+
+  constructor(private modalService: ModalService,
+              private dataTypeService: DataTypeService,
+              private translateService: TranslateService) {
+  }
+
+  ngOnInit() {
+    this.translateService.languageChangedObservable.subscribe((lang) => {
+      this.deleteMsgTitle = this.translateService.translate('DELETE_OUTPUT_TITLE');
+      this.modalDeleteBtn = this.translateService.translate('MODAL_DELETE');
+      this.modalCancelBtn = this.translateService.translate('MODAL_CANCEL');
+
+    });
+  }
+
+  sort = (sortBy) => {
+    this.reverse = (this.sortBy === sortBy) ? !this.reverse : true;
+    let reverse = this.reverse ? 1 : -1;
+    this.sortBy = sortBy;
+    let instanceNameMapTemp = this.instanceNamesMap;
+    let itemIdx1Val = "";
+    let itemIdx2Val = "";
+    this.outputs.sort(function (itemIdx1, itemIdx2) {
+      if (sortBy == 'instanceUniqueId') {
+        itemIdx1Val = (itemIdx1[sortBy] && instanceNameMapTemp[itemIdx1[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx1[sortBy]].name : "";
+        itemIdx2Val = (itemIdx2[sortBy] && instanceNameMapTemp[itemIdx2[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx2[sortBy]].name : "";
+      } else {
+        itemIdx1Val = itemIdx1[sortBy];
+        itemIdx2Val = itemIdx2[sortBy];
+      }
+      if (itemIdx1Val < itemIdx2Val) {
+        return -1 * reverse;
+      } else if (itemIdx1Val > itemIdx2Val) {
+        return 1 * reverse;
+      } else {
+        return 0;
+      }
+    });
+  };
+
+  onOutputChanged = (output, event) => {
+    output.updateDefaultValueObj(event.value, event.isValid);
+    this.outputChanged.emit(output);
+  };
+
+  onRequiredChanged = (output: OutputFEModel, event) => {
+    this.outputChanged.emit(output);
+  }
+
+  onDeleteOutput = () => {
+    this.deleteOutput.emit(this.selectedOutputToDelete);
+    this.modalService.closeCurrentModal();
+  };
+
+  openDeleteModal = (output: OutputFEModel) => {
+    this.selectedOutputToDelete = output;
+    this.modalService.createActionModal("Delete Output", "Are you sure you want to delete this output?", "Delete", this.onDeleteOutput, "Close").instance.open();
+  }
+
+  getConstraints(output: OutputFEModel): string[] {
+    if (output.outputPath) {
+      const pathValuesName = output.outputPath.split('#');
+      const rootPropertyName = pathValuesName[0];
+      const propertyName = pathValuesName[1];
+      let filteredRootPropertyType = _.values(this.feAttributesMap)[0].filter(property =>
+          property.name == rootPropertyName);
+      if (filteredRootPropertyType.length > 0) {
+        let rootPropertyType = filteredRootPropertyType[0].type;
+        return this.dataTypeService.getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName);
+      } else {
+        return null;
+      }
+
+    } else {
+      return null;
+    }
+  }
+
+  checkInstanceFeAttributesMapIsFilled() {
+    return _.keys(this.feAttributesMap).length > 0
+  }
+
+}
index e499b37..b79bec0 100644 (file)
@@ -81,7 +81,6 @@ export class PropertiesTableComponent implements OnChanges {
 
     // Click on main row (row of propertyFEModel)
     onClickPropertyRow = (property: PropertyFEModel, instanceName: string, event?) => {
-        // event && event.stopPropagation();
         this.selectedPropertyId = property.name;
         const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName);
         this.selectPropertyRow.emit(propertyRowSelectedEvent);
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.html b/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.html
new file mode 100644 (file)
index 0000000..9687d1e
--- /dev/null
@@ -0,0 +1,66 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+-->
+
+<div class="attribute-creator">
+  <loader [display]="isLoading" [size]="'large'" [relative]="true" [loaderDelay]="500"></loader>
+  <form class="w-sdc-form">
+
+    <div class="side-by-side">
+      <div class="i-sdc-form-item">
+        <label class="i-sdc-form-label required">Name</label>
+        <input class="i-sdc-form-input"
+               type="text"
+               name="propertyName"
+               data-tests-id="property-name"
+               [(ngModel)]="attributeModel.name"
+               [ngModelOptions]="{ debounce: 200 }"
+               autofocus/>
+      </div>
+      <!-- Type -->
+      <div class="i-sdc-form-item">
+        <label class="i-sdc-form-label required">Type</label>
+        <ui-element-dropdown [testId]="'property-type'"
+                             class="cell link-selector"
+                             [values]="typesAttributes"
+                             [(value)]="attributeModel.type"></ui-element-dropdown>
+      </div>
+      <div class="i-sdc-form-item attributeSchemaType" *ngIf="showSchema()">
+        <label class="i-sdc-form-label required">Schema Type</label>
+        <ui-element-dropdown [testId]="'property-type'"
+                             class="cell link-selector"
+                             [values]="typesSchemaAttributes"
+                             [(value)]="attributeModel.schema.property.type"></ui-element-dropdown>
+      </div>
+    </div>
+
+    <!-- Description -->
+    <div class="i-sdc-form-item">
+      <label class="i-sdc-form-label">Description</label>
+      <textarea class="i-sdc-form-textarea"
+                [pattern]="validation.commentValidationPattern"
+                name="propertyDescription"
+                [(ngModel)]="attributeModel.description"
+                [ngModelOptions]="{ debounce: 200 }"
+                data-tests-id="property-description"
+      ></textarea>
+    </div>
+
+  </form>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.less b/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.less
new file mode 100644 (file)
index 0000000..8b5152b
--- /dev/null
@@ -0,0 +1,36 @@
+@import '../../../../../assets/styles/variables.less';
+
+.attribute-creator {
+  font-family: @font-opensans-regular;
+  user-select: none;
+  padding-top: 12px;
+  padding-bottom: 20px;
+
+  .i-sdc-form-label {
+    font-size: 12px;
+  }
+
+  .w-sdc-form .i-sdc-form-item {
+    margin-bottom: 15px;
+  }
+
+  .side-by-side {
+    display: flex;
+
+    .i-sdc-form-item {
+      flex-basis: 100%;
+
+      &:first-child {
+        flex-basis: 50%;
+        margin-right: 10px;
+      }
+
+    }
+
+    .attributeSchemaType {
+      margin-left: 10px;
+    }
+  }
+
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component.ts
new file mode 100644 (file)
index 0000000..5fc3d5b
--- /dev/null
@@ -0,0 +1,98 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Component} from '@angular/core';
+import {DataTypesMap} from 'app/models';
+import {DropdownValue} from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component';
+import {DataTypeService} from 'app/ng2/services/data-type.service';
+import {PROPERTY_DATA} from 'app/utils';
+import * as _ from 'lodash';
+import {PROPERTY_TYPES} from '../../../../utils';
+import {AttributeBEModel} from "../../../../models/attributes-outputs/attribute-be-model";
+import {Validation} from "../../../../view-models/workspace/tabs/general/general-view-model";
+
+@Component({
+  selector: 'attribute-creator',
+  templateUrl: './attribute-creator.component.html',
+  styleUrls: ['./attribute-creator.component.less'],
+})
+
+export class AttributeCreatorComponent {
+
+  validation:Validation;
+  typesAttributes: DropdownValue[];
+  typesSchemaAttributes: DropdownValue[];
+  attributeModel: AttributeBEModel;
+  dataTypes: DataTypesMap;
+  isLoading: boolean;
+
+  constructor(protected dataTypeService: DataTypeService) {
+  }
+
+  ngOnInit() {
+    this.attributeModel = new AttributeBEModel();
+    this.attributeModel.type = '';
+    this.attributeModel.schema.property.type = '';
+    const types: string[] = PROPERTY_DATA.TYPES; // All types - simple type + map + list
+    this.dataTypes = this.dataTypeService.getAllDataTypes(); // Get all data types in service
+    const nonPrimitiveTypes: string[] = _.filter(Object.keys(this.dataTypes), (type: string) => {
+      return types.indexOf(type) === -1;
+    });
+
+    this.typesAttributes = _.map(PROPERTY_DATA.TYPES,
+        (type: string) => new DropdownValue(type, type)
+    );
+    const typesSimpleProperties = _.map(PROPERTY_DATA.SIMPLE_TYPES,
+        (type: string) => new DropdownValue(type, type)
+    );
+    const nonPrimitiveTypesValues = _.map(nonPrimitiveTypes,
+        (type: string) => new DropdownValue(type,
+            type.replace('org.openecomp.datatypes.heat.', ''))
+    )
+    .sort((a, b) => a.label.localeCompare(b.label));
+    this.typesAttributes = _.concat(this.typesAttributes, nonPrimitiveTypesValues);
+    this.typesSchemaAttributes = _.concat(typesSimpleProperties, nonPrimitiveTypesValues);
+    this.typesAttributes.unshift(new DropdownValue('', 'Select Type...'));
+    this.typesSchemaAttributes.unshift(new DropdownValue('', 'Select Schema Type...'));
+
+  }
+
+  checkFormValidForSubmit() {
+    const showSchema: boolean = this.showSchema();
+    const isSchemaValid: boolean = (!(showSchema && !this.attributeModel.schema.property.type));
+    if (!showSchema) {
+      this.attributeModel.schema.property.type = '';
+    }
+    return this.attributeModel.name && this.attributeModel.type && isSchemaValid;
+  }
+
+  showSchema(): boolean {
+    return [PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP].indexOf(this.attributeModel.type) > -1;
+  }
+
+  onSchemaTypeChange(): void {
+    if (this.attributeModel.type === PROPERTY_TYPES.MAP) {
+      this.attributeModel.value = JSON.stringify({'': null});
+    } else if (this.attributeModel.type === PROPERTY_TYPES.LIST) {
+      this.attributeModel.value = JSON.stringify([]);
+    }
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.module.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.module.ts
new file mode 100644 (file)
index 0000000..5779731
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+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 '../../../shared/translator/translate.module';
+import {AttributeCreatorComponent} from './attribute-creator.component';
+
+@NgModule({
+  declarations: [
+    AttributeCreatorComponent,
+  ],
+  imports: [
+    CommonModule,
+    FormsModule,
+    FormElementsModule,
+    UiElementsModule,
+    TranslateModule
+  ],
+  exports: [],
+  entryComponents: [
+    AttributeCreatorComponent
+  ],
+  providers: []
+})
+
+export class AttributeCreatorModule {
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.module.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.module.ts
new file mode 100644 (file)
index 0000000..5543832
--- /dev/null
@@ -0,0 +1,65 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {SdcUiComponentsModule} from 'onap-ui-angular';
+import {GlobalPipesModule} from "../../pipes/global-pipes.module";
+import {NgxDatatableModule} from "@swimlane/ngx-datatable";
+import {TranslateModule} from "../../shared/translator/translate.module";
+import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
+import {AttributesOutputsComponent} from "./attributes-outputs.page.component";
+import {TabModule} from "../../components/ui/tabs/tabs.module";
+import {UiElementsModule} from "../../components/ui/ui-elements.module"
+import {HierarchyNavigationModule} from "../../components/logic/hierarchy-navigtion/hierarchy-navigation.module";
+import {AttributesService} from "../../services/attributes.service";
+import {HierarchyNavService} from "./services/hierarchy-nav.service";
+import {AttributesUtils} from "./services/attributes.utils";
+import {OutputsUtils} from "./services/outputs.utils";
+import { OutputsTableComponent } from "app/ng2/components/logic/outputs-table/outputs-table.component";
+import {AttributeTableModule} from "../../components/logic/attributes-table/attribute-table.module";
+
+@NgModule({
+  declarations: [
+    AttributesOutputsComponent,
+    OutputsTableComponent
+  ],
+  imports: [
+    CommonModule,
+    SdcUiComponentsModule,
+    GlobalPipesModule,
+    NgxDatatableModule,
+    TabModule,
+    HierarchyNavigationModule,
+    UiElementsModule,
+    TranslateModule,
+    AttributeTableModule
+  ],
+  exports: [
+    AttributesOutputsComponent
+  ],
+  entryComponents: [
+    AttributesOutputsComponent
+  ],
+  providers: [TopologyTemplateService, AttributesService, HierarchyNavService, AttributesUtils, OutputsUtils]
+})
+
+export class AttributesOutputsModule {
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.html b/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.html
new file mode 100644 (file)
index 0000000..778d545
--- /dev/null
@@ -0,0 +1,103 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+-->
+
+<div class="attributes-outputs-page">
+
+  <div class="main-content">
+    <div class="left-column">
+      <div class="main-tabs-section">
+        <tabs #attributeOutputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)"
+              [hideIndicationOnTabChange]="true">
+          <tab tabTitle="Attributes">
+            <attributes-table class="attributes-table"
+                              [feAttributesMap]="instanceFeAttributesMap"
+                              [feInstanceNamesMap]="componentInstanceNamesMap"
+                              [selectedAttributeId]="selectedFlatAttribute.path"
+                              [readonly]="isReadonly || resourceIsReadonly"
+                              [isLoading]="loadingAttributes || savingChangedData"
+                              [hasDeclareOption]="true"
+                              (attributeChanged)="dataChanged($event)"
+                              (selectAttributeRow)="selectAttributeRow($event)"
+                              (updateCheckedAttributeCount)="updateCheckedAttributeCount($event)">
+            </attributes-table>
+          </tab>
+
+          <tab tabTitle="Outputs">
+            <outputs-table class="attributes-table"
+                           [feAttributesMap]="instanceFeAttributesMap"
+                           [readonly]="isReadonly"
+                           [outputs]="outputs | searchFilter:'name':searchQuery"
+                           [instanceNamesMap]="componentInstanceNamesMap"
+                           [isLoading]="loadingOutputs"
+                           [componentType]="component.componentType"
+                           (deleteOutput)="deleteOutput($event)"
+                           (outputChanged)="dataChanged($event)">
+            </outputs-table>
+          </tab>
+        </tabs>
+        <div class="header">
+          <button class="tlv-btn blue declare-button" [disabled]="!checkedAttributesCount || isReadonly || hasChangedData" (click)="declareAttributes()" data-tests-id="declare-button declare-output">Declare Output</button>
+        </div>
+      </div>
+    </div>
+    <div class="right-column">
+      <tabs #hierarchyNavTabs tabStyle="simple-tabs" class="gray-border">
+        <tab tabTitle="Composition">
+          <div class="hierarchy-nav-container">
+            <loader [display]="loadingInstances" [size]="'medium'" [relative]="true"
+                    [loaderDelay]="500"></loader>
+            <div class="hierarchy-header white-sub-header">
+              <span tooltip="{{component.name}}">{{component.name}}</span>
+            </div>
+            <div
+                *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isOutputsTabSelected">
+              No data to display
+            </div>
+            <hierarchy-navigation class="hierarchy-nav"
+                                  (updateSelected)="onInstanceSelectedUpdate($event)"
+                                  [displayData]="isOutputsTabSelected ? []: instancesNavigationData"
+                                  [selectedItem]="selectedInstanceData?.uniqueId"
+                                  [displayOptions]="hierarchyInstancesDisplayOptions"></hierarchy-navigation>
+          </div>
+        </tab>
+        <tab tabTitle="Attribute Structure">
+          <div class="hierarchy-nav-container">
+            <div class="hierarchy-header white-sub-header"
+                 [class.selected]="selectedFlatAttribute.path == attributeStructureHeader">
+              <span
+                  tooltip="{{isAttributesTabSelected ? attributeStructureHeader : ''}}">{{isAttributesTabSelected
+                  ? (attributeStructureHeader || "No Attribute Selected")
+                  : "No Attribute Selected"}}</span>
+            </div>
+            <div
+                *ngIf="!attributesNavigationData || attributesNavigationData.length === 0 || isOutputsTabSelected ">
+              No data to display
+            </div>
+            <hierarchy-navigation class="hierarchy-nav"
+                                  (updateSelected)="onAttributeSelectedUpdate($event)"
+                                  [displayData]="isOutputsTabSelected ? [] : attributesNavigationData"
+                                  [selectedItem]="selectedFlatAttribute.path"
+                                  [displayOptions]="hierarchyAttributesDisplayOptions"></hierarchy-navigation>
+          </div>
+        </tab>
+      </tabs>
+    </div>
+  </div>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.less b/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.less
new file mode 100644 (file)
index 0000000..386d243
--- /dev/null
@@ -0,0 +1,234 @@
+@import '../../../../assets/styles/variables';
+@import '../../../../assets/styles/mixins';
+
+@ng2-shadow-gray: #f8f8f8;
+@ng2-light-gray: #eaeaea;
+@ng2-medium-gray: #d2d2d2;
+@ng2-med-dark-gray: #999999;
+@ng2-dark-gray: #5a5a5a;
+@ng2-shadow-blue: #e6f6fb;
+@ng2-bold-blue: #009fdb;
+@ng2-success-green: #4ca90c;
+@ng2-title-font-size: 16px;
+@ng2-text-font-size: 14px;
+
+:host {
+  display: block;
+  height: 100%;
+}
+
+/deep/ tabs {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.attributes-outputs-page {
+  height: 100%;
+  font-family: @font-opensans-regular;
+
+  .main-content {
+    display: flex;
+    flex-direction: row;
+    height: 100%;
+  }
+
+  .left-column {
+    flex: 1 0 500px;
+    position: relative;
+    min-width: 930px;
+
+    /deep/ .tabs {
+      width: 33%;
+      text-align: center;
+    }
+
+    /deep/ .tab {
+      padding: 12px;
+      flex: 1;
+      font-family: @font-opensans-regular;
+
+      &.active {
+        color: #009fdb;
+        border-color: #d2d2d2;
+        border-top: solid 4px #009fdb;
+        background-color: white;
+        padding-top: 9px;
+        font-family: @font-opensans-medium;
+      }
+
+      .tab-indication {
+        background-color: #4ca90c;
+        border: solid 2px #fff;
+        border-radius: 50%;
+        font-size: 12px;
+      }
+    }
+
+    .header {
+      position: absolute;
+      top: 0;
+      right: 0;
+      display: flex;
+    }
+
+    .search-filter-container {
+      display: flex;
+      flex-direction: row;
+
+      .search-box {
+        border: 1px solid @ng2-medium-gray;
+        border-radius: 3px;
+        height: 32px;
+        margin: 0;
+        padding: 2px 20px 4px 10px;
+        outline: none;
+        font-style: italic;
+        color: @ng2-med-dark-gray;
+        width: auto;
+
+        &::-moz-placeholder {
+          color: @ng2-med-dark-gray;
+        }
+
+        &::-webkit-input-placeholder {
+          color: @ng2-med-dark-gray;
+        }
+      }
+
+      .search-icon {
+        background-position: -48px -3137px;
+        width: 14px;
+        height: 14px;
+        position: absolute;
+        left: 170px;
+        top: 8px;
+      }
+
+      &.without-filter {
+        margin-right: 10px;
+
+        .search-icon {
+          right: 4px;
+        }
+      }
+
+    }
+
+    .clear-filter {
+      cursor: pointer;
+      color: #009fdb;
+      font-family: @font-opensans-medium-italic;
+      text-decoration: underline;
+      padding-right: 10px;
+      font-size: 12px;
+    }
+
+    .declare-button {
+      &:not(:last-of-type) {
+        margin-right: 10px;
+      }
+    }
+
+    .main-tabs-section {
+      position: relative;
+
+      .main-tabs-section-buttons {
+        position: absolute;
+        top: 45px;
+        right: 0;
+        padding: 4px;
+      }
+    }
+  }
+
+  .right-column {
+    display: flex;
+    flex: 0 0 350px;
+    flex-direction: column;
+    margin: 0px 0 0 1em;
+    overflow-x: auto;
+
+    .add-btn {
+
+      align-self: flex-end;
+      margin-top: 10px;
+      margin-bottom: 19px;
+    }
+
+    /deep/ .tabs {
+      border-bottom: solid 1px #d0d0d0;
+    }
+
+    /deep/ .tab {
+      flex: none;
+      padding: 8px 20px 0;
+      font-size: 14px;
+      line-height: 30px;
+      font-family: @font-opensans-regular;
+
+      &.active {
+        font-family: @font-opensans-medium;
+      }
+    }
+  }
+
+  .hierarchy-tabs {
+    flex: 0 0 40px;
+  }
+
+  .gray-border {
+    border: 1px solid #ddd;
+  }
+
+  /deep/ .white-sub-header {
+    background-color: #fffefe;
+    box-shadow: 0px 1px 0.99px 0.01px rgba(34, 31, 31, 0.15);
+    border-bottom: #d2d2d2 solid 1px;
+    font-size: 14px;
+    text-align: left;
+    flex: 0 0 auto;
+    padding: 10px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+    .a_13_r;
+    text-transform: uppercase;
+
+    &.hierarchy-header {
+      padding-left: 20px;
+
+      &.selected {
+        background-color: #e6f6fb;
+      }
+    }
+
+  }
+
+  .hierarchy-nav-container {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+
+  .hierarchy-header {
+
+    span {
+      text-overflow: ellipsis;
+      overflow: hidden;
+      white-space: nowrap;
+      max-width: 290px;
+    }
+  }
+
+  .hierarchy-nav {
+    flex: 1;
+    overflow: auto;
+    display: grid;
+    margin-top: 1em;
+    margin-left: 1em;
+    font-size: 12px;
+    padding-top: 1em;
+  }
+}
+
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/attributes-outputs.page.component.ts
new file mode 100644 (file)
index 0000000..d7db8f3
--- /dev/null
@@ -0,0 +1,719 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Component, Inject, ViewChild} from '@angular/core';
+import {
+  ButtonModel,
+  Component as ComponentData,
+  ComponentInstance,
+  ModalModel,
+  ToscaPresentationData
+} from 'app/models';
+import {SdcUiCommon, SdcUiServices} from 'onap-ui-angular';
+import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
+import {Tab, Tabs} from "../../components/ui/tabs/tabs.component";
+import * as _ from 'lodash';
+import {OutputFEModel} from "../../../models/attributes-outputs/output-fe-model";
+import {OutputBEModel} from "../../../models/attributes-outputs/output-be-model";
+import {EVENTS, ResourceType, WorkspaceMode} from "../../../utils/constants";
+import {ComponentModeService} from "../../services/component-services/component-mode.service";
+import {EventListenerService} from "app/services";
+import {HierarchyNavService} from "./services/hierarchy-nav.service";
+import {ComponentServiceNg2} from "../../services/component-services/component.service";
+import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service";
+import {KeysPipe} from "../../pipes/keys.pipe";
+import {
+  InstanceAttributesAPIMap,
+  InstanceBeAttributesMap,
+  InstanceFeAttributesMap
+} from "app/models/attributes-outputs/attribute-fe-map";
+import {ModalService} from "../../services/modal.service";
+import {InstanceFeDetails} from "../../../models/instance-fe-details";
+import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
+import {UnsavedChangesComponent} from "../../components/ui/forms/unsaved-changes/unsaved-changes.component";
+import {SimpleFlatAttribute} from "app/models/attributes-outputs/simple-flat-attribute";
+import {AttributeFEModel} from "../../../models/attributes-outputs/attribute-fe-model";
+import {AttributesUtils} from "./services/attributes.utils";
+import {OutputsUtils} from "app/ng2/pages/attributes-outputs/services/outputs.utils";
+import {AttributesService} from "app/ng2/services/attributes.service";
+import {DerivedFEAttribute} from "../../../models/attributes-outputs/derived-fe-attribute";
+import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
+import {AttributeCreatorComponent} from "app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component";
+import {AttributeRowSelectedEvent} from "app/ng2/components/logic/attributes-table/attributes-table.component";
+
+const SERVICE_SELF_TITLE = "SELF";
+
+@Component({
+  selector: 'attributes-outputs',
+  templateUrl: './attributes-outputs.page.component.html',
+  styleUrls: ['./attributes-outputs.page.component.less', '../../../../assets/styles/table-style.less']
+})
+export class AttributesOutputsComponent {
+  title = "Attributes & Outputs";
+
+  @ViewChild('componentAttributesTable')
+  private table: any;
+
+  component: ComponentData;
+  componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
+
+  attributesNavigationData = [];
+  instancesNavigationData = [];
+
+  instanceFeAttributesMap: InstanceFeAttributesMap;
+  outputs: Array<OutputFEModel> = [];
+  instances: Array<ComponentInstance> = [];
+  searchQuery: string;
+  attributeStructureHeader: string;
+
+  selectedFlatAttribute: SimpleFlatAttribute = new SimpleFlatAttribute();
+  selectedInstanceData: ComponentInstance = null;
+  checkedAttributesCount: number = 0;
+
+  hierarchyAttributesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
+  hierarchyInstancesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
+  searchAttributeName: string;
+  currentMainTab: Tab;
+  isOutputsTabSelected: boolean;
+  isAttributesTabSelected: boolean;
+  isReadonly: boolean;
+  resourceIsReadonly: boolean;
+  loadingInstances: boolean = false;
+  loadingOutputs: boolean = false;
+  loadingAttributes: boolean = false;
+  changedData: Array<AttributeFEModel | OutputFEModel>;
+  hasChangedData: boolean;
+  isValidChangedData: boolean;
+  savingChangedData: boolean;
+  stateChangeStartUnregister: Function;
+  serviceBeAttributesMap: InstanceBeAttributesMap;
+
+  @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
+  @ViewChild('attributeOutputTabs') attributeOutputTabs: Tabs;
+
+
+  constructor(private attributesService: AttributesService,
+              private hierarchyNavService: HierarchyNavService,
+              private attributesUtils: AttributesUtils,
+              private outputsUtils: OutputsUtils,
+              private componentServiceNg2: ComponentServiceNg2,
+              private componentInstanceServiceNg2: ComponentInstanceServiceNg2,
+              @Inject("$stateParams") _stateParams,
+              @Inject("$scope") private $scope: ng.IScope,
+              @Inject("$state") private $state: ng.ui.IStateService,
+              @Inject("Notification") private Notification: any,
+              private componentModeService: ComponentModeService,
+              private EventListenerService: EventListenerService,
+              private ModalServiceSdcUI: SdcUiServices.ModalService,
+              private ModalService: ModalService,
+              private keysPipe: KeysPipe,
+              private topologyTemplateService: TopologyTemplateService) {
+
+    this.instanceFeAttributesMap = new InstanceFeAttributesMap();
+    /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time
+    than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
+    this.component = _stateParams.component;
+    this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.onCheckout);
+    this.updateViewMode();
+
+    this.changedData = [];
+    this.updateHasChangedData();
+    this.isValidChangedData = true;
+  }
+
+  ngOnInit() {
+    this.loadingOutputs = true;
+    this.loadingInstances = true;
+    this.loadingAttributes = true;
+    this.topologyTemplateService
+    .getComponentOutputsWithAttributes(this.component.componentType, this.component.uniqueId)
+    .subscribe(response => {
+      if (response.outputs) {
+        response.outputs.forEach(output => {
+          const newOutput: OutputFEModel = new OutputFEModel(output);
+          this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
+          this.outputs.push(newOutput);
+        });
+      }
+    }, error => {
+      this.Notification.error({
+        message: 'Failed to Initialize:' + error,
+        title: 'Failure'
+      });
+    }, () => {
+      this.loadingOutputs = false;
+    });
+    this.componentServiceNg2
+    .getComponentResourceAttributesData(this.component)
+    .subscribe(response => {
+      this.instances = [];
+      this.instances.push(...response.componentInstances);
+
+      // add the service self instance to the top of the list.
+      const serviceInstance = new ComponentInstance();
+      serviceInstance.name = SERVICE_SELF_TITLE;
+      serviceInstance.uniqueId = this.component.uniqueId;
+      this.instances.unshift(serviceInstance);
+      if (this.instances) {
+        this.instances.forEach(instance => {
+          this.instancesNavigationData.push(instance);
+          this.componentInstanceNamesMap[instance.uniqueId] = <InstanceFeDetails>{
+            name: instance.name,
+            iconClass: instance.iconClass,
+            originArchived: instance.originArchived
+          };
+        });
+      }
+      this.selectFirstInstanceByDefault();
+    }, (error) => {
+      this.Notification.error({
+        message: 'Failed to Initialize:' + error,
+        title: 'Failure'
+      });
+    }, () => {
+      this.loadingInstances = false;
+      this.loadingAttributes = false;
+    });
+
+    this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
+      // stop if has changed attributes
+      if (this.hasChangedData) {
+        event.preventDefault();
+        this.showUnsavedChangesAlert().then(() => {
+          this.$state.go(toState, toParams);
+        }, () => {
+        });
+      }
+    });
+  }
+
+  ngOnDestroy() {
+    this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
+    this.stateChangeStartUnregister();
+  }
+
+  selectFirstInstanceByDefault = () => {
+    if (this.instancesNavigationData.length > 0) {
+      this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
+    }
+  };
+
+  updateViewMode = () => {
+    this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
+  }
+
+  onCheckout = (component: ComponentData) => {
+    this.component = component;
+    this.updateViewMode();
+  }
+
+  isSelf = (): boolean => {
+    return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
+  }
+
+  getServiceAttributes() {
+    this.loadingAttributes = true;
+    this.topologyTemplateService
+    .getServiceAttributes(this.component.uniqueId)
+    .subscribe((response) => {
+      this.serviceBeAttributesMap = new InstanceBeAttributesMap();
+      this.serviceBeAttributesMap[this.component.uniqueId] = response;
+      this.processInstanceAttributesResponse(this.serviceBeAttributesMap, false);
+    }, (error) => {
+      this.Notification.error({
+        message: 'Failed to get Service Attribute:' + error,
+        title: 'Failure'
+      });
+    }, () => {
+      this.loadingAttributes = false;
+    });
+  }
+
+  onInstanceSelectedUpdate = (instance: ComponentInstance) => {
+    // stop if has changed attributes
+    if (this.hasChangedData) {
+      this.showUnsavedChangesAlert().then(() => {
+        this.changeSelectedInstance(instance)
+      });
+      return;
+    }
+    this.changeSelectedInstance(instance);
+  };
+
+  changeSelectedInstance = (instance: ComponentInstance) => {
+    this.selectedInstanceData = instance;
+    this.loadingAttributes = true;
+    if (instance instanceof ComponentInstance) {
+      let instanceBeAttributesMap: InstanceBeAttributesMap = new InstanceBeAttributesMap();
+      if (this.isOutput(instance.originType)) {
+        this.componentInstanceServiceNg2
+        .getComponentInstanceOutputs(this.component, instance)
+        .subscribe(response => {
+          instanceBeAttributesMap[instance.uniqueId] = response;
+          this.processInstanceAttributesResponse(instanceBeAttributesMap, true);
+        }, error => {
+          this.Notification.error({
+            message: 'Failed to change Selected Instance:' + error,
+            title: 'Failure'
+          });
+        }, () => {
+          this.loadingAttributes = false;
+        });
+      } else if (this.isSelf()) {
+        this.getServiceAttributes();
+      } else {
+        this.componentInstanceServiceNg2
+        .getComponentInstanceAttributes(this.component, instance.uniqueId)
+        .subscribe(response => {
+          instanceBeAttributesMap[instance.uniqueId] = response;
+          this.processInstanceAttributesResponse(instanceBeAttributesMap, false);
+        }, error => {
+          this.Notification.error({
+            message: 'Failed to change Selected Instance:' + error,
+            title: 'Failure'
+          });
+        }, () => {
+          this.loadingAttributes = false;
+        });
+      }
+
+      this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
+    } else {
+      this.loadingAttributes = false;
+    }
+
+    //clear selected attribute from the navigation
+    this.selectedFlatAttribute = new SimpleFlatAttribute();
+    this.attributesNavigationData = [];
+  };
+
+  /**
+   * Entry point handling response from server
+   */
+  processInstanceAttributesResponse = (instanceBeAttributesMap: InstanceBeAttributesMap, originTypeIsVF: boolean) => {
+    this.instanceFeAttributesMap = this.attributesUtils.convertAttributesMapToFEAndCreateChildren(instanceBeAttributesMap, originTypeIsVF, this.outputs); //create flattened children, disable declared attribs, and init values
+    this.checkedAttributesCount = 0;
+  };
+
+
+  /*** VALUE CHANGE EVENTS ***/
+  dataChanged = (item: AttributeFEModel | OutputFEModel) => {
+    let itemHasChanged;
+    if (this.isAttributesTabSelected && item instanceof AttributeFEModel) {
+      itemHasChanged = item.hasValueObjChanged();
+    } else if (this.isOutputsTabSelected && item instanceof OutputFEModel) {
+      itemHasChanged = item.hasChanged();
+    }
+
+    const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
+    if (itemHasChanged) {
+      if (dataChangedIdx === -1) {
+        this.changedData.push(item);
+      }
+    } else {
+      if (dataChangedIdx !== -1) {
+        this.changedData.splice(dataChangedIdx, 1);
+      }
+    }
+
+    if (this.isAttributesTabSelected) {
+      this.isValidChangedData = this.changedData.every((changedItem) => (<AttributeFEModel>changedItem).valueObjIsValid);
+    } else if (this.isOutputsTabSelected) {
+      this.isValidChangedData = this.changedData.every((changedItem) => (<OutputFEModel>changedItem).defaultValueObjIsValid);
+    }
+    this.updateHasChangedData();
+  };
+
+
+  /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
+
+  /**
+   * Handle select node in navigation area, and select the row in table
+   */
+  onAttributeSelectedUpdate = ($event) => {
+    this.selectedFlatAttribute = $event;
+    let parentAttribute: AttributeFEModel = this.attributesService.getParentAttributeFEModelFromPath(this.instanceFeAttributesMap[this.selectedFlatAttribute.instanceName], this.selectedFlatAttribute.path);
+    parentAttribute.expandedChildAttributeId = this.selectedFlatAttribute.path;
+  };
+
+  /**
+   * When user select row in table, this will prepare the hierarchy object for the tree.
+   */
+  selectAttributeRow = (attributeRowSelectedEvent: AttributeRowSelectedEvent) => {
+    let attribute = attributeRowSelectedEvent.attributeModel;
+    let instanceName = attributeRowSelectedEvent.instanceName;
+    this.attributeStructureHeader = null;
+
+    // Build hierarchy tree for the navigation and update attributesNavigationData with it.
+    if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
+      let simpleFlatAttributes: Array<SimpleFlatAttribute>;
+      if (attribute instanceof AttributeFEModel) {
+        simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(attribute, instanceName);
+      } else if (attribute instanceof DerivedFEAttribute) {
+        // Need to find parent AttributeFEModel
+        let parentAttributeFEModel: AttributeFEModel = _.find(this.instanceFeAttributesMap[instanceName], (tmpFeAttribute): boolean => {
+          return attribute.attributesName.indexOf(tmpFeAttribute.name) === 0;
+        });
+        simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(parentAttributeFEModel, instanceName);
+      }
+      this.attributesNavigationData = simpleFlatAttributes;
+    }
+
+    // Update the header in the navigation tree with attribute name.
+    this.attributeStructureHeader = (attribute.attributesName.split('#'))[0];
+
+    // Set selected attribute in table
+    this.selectedFlatAttribute = this.hierarchyNavService.createSimpleFlatAttribute(attribute, instanceName);
+    this.hierarchyNavTabs.triggerTabChange('Attribute Structure');
+  };
+
+  tabChanged = (event) => {
+    // stop if has changed attributes
+    if (this.hasChangedData) {
+      this.attributeOutputTabs.triggerTabChange(this.currentMainTab.title);
+      this.showUnsavedChangesAlert().then(() => {
+        this.attributeOutputTabs.selectTab(this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title));
+      }, () => {
+      });
+      return;
+    }
+
+    this.currentMainTab = this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title);
+    this.isAttributesTabSelected = this.currentMainTab.title === "Attributes";
+    this.isOutputsTabSelected = this.currentMainTab.title === "Outputs";
+    this.attributeStructureHeader = null;
+    this.searchQuery = '';
+  };
+
+
+  /*** DECLARE ATTRIBUTES/OUTPUTS ***/
+  declareAttributes = (): void => {
+    let selectedComponentInstancesAttributes: InstanceBeAttributesMap = new InstanceBeAttributesMap();
+    let selectedComponentInstancesOutputs: InstanceBeAttributesMap = new InstanceBeAttributesMap();
+    let instancesIds = this.keysPipe.transform(this.instanceFeAttributesMap, []);
+
+    angular.forEach(instancesIds, (instanceId: string): void => {
+      let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
+      if (selectedInstanceData instanceof ComponentInstance) {
+        if (!this.isOutput(selectedInstanceData.originType)) {
+          // convert Attribute FE model -> Attribute BE model, extract only checked
+          selectedComponentInstancesAttributes[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
+        } else {
+          selectedComponentInstancesOutputs[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
+        }
+      }
+    });
+
+    let outputsToCreate: InstanceAttributesAPIMap = new InstanceAttributesAPIMap(selectedComponentInstancesOutputs, selectedComponentInstancesAttributes);
+    this.topologyTemplateService
+    .createOutput(this.component, outputsToCreate, this.isSelf())
+    .subscribe((response) => {
+      this.setOutputTabIndication(response.length);
+      this.checkedAttributesCount = 0;
+      response.forEach((output: OutputBEModel) => {
+        const newOutput: OutputFEModel = new OutputFEModel(output);
+        this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
+        this.outputs.push(newOutput);
+        this.updateAttributeValueAfterDeclare(newOutput);
+      });
+    });
+  };
+
+  saveChangedData = (): Promise<(AttributeBEModel | OutputBEModel)[]> => {
+    return new Promise((resolve, reject) => {
+      if (!this.isValidChangedData) {
+        reject('Changed data is invalid - cannot save!');
+        return;
+      }
+      if (!this.changedData.length) {
+        resolve([]);
+        return;
+      }
+
+      // make request and its handlers
+      let request;
+      let handleSuccess, handleError;
+      if (this.isAttributesTabSelected) {
+        this.changedData.map((changedAttrib) => {
+          changedAttrib = <AttributeFEModel>changedAttrib;
+          const attribBE = new AttributeBEModel(changedAttrib);
+          attribBE.toscaPresentation = new ToscaPresentationData();
+          attribBE.toscaPresentation.ownerId = changedAttrib.parentUniqueId;
+          attribBE.value = changedAttrib.getJSONValue();
+          attribBE.name = changedAttrib.origName || changedAttrib.name;
+          delete attribBE.origName;
+          return attribBE;
+        });
+      } else if (this.isOutputsTabSelected) {
+        const changedOutputs: OutputBEModel[] = this.changedData.map((changedOutput) => {
+          changedOutput = <OutputFEModel>changedOutput;
+          const outputBE = new OutputBEModel(changedOutput);
+          outputBE.defaultValue = changedOutput.getJSONDefaultValue();
+          return outputBE;
+        });
+        request = this.componentServiceNg2
+        .updateComponentOutputs(this.component, changedOutputs);
+        handleSuccess = (response) => {
+          // reset each changed attribute with new value and remove it from changed attributes list
+          response.forEach((resOutput) => {
+            const changedOutput = <OutputFEModel>this.changedData.shift();
+            this.outputsUtils.resetOutputDefaultValue(changedOutput, resOutput.defaultValue);
+            changedOutput.required = resOutput.required;
+          });
+        }
+        this.savingChangedData = true;
+        request.subscribe(
+            (response) => {
+              this.savingChangedData = false;
+              handleSuccess && handleSuccess(response);
+              this.updateHasChangedData();
+              resolve(response);
+            },
+            (error) => {
+              this.savingChangedData = false;
+              handleError && handleError(error);
+              this.updateHasChangedData();
+              reject(error);
+            }
+        );
+      }
+
+    });
+  };
+
+
+  reverseChangedData = (): void => {
+    // make reverse item handler
+    let handleReverseItem;
+    if (this.isAttributesTabSelected) {
+      handleReverseItem = (changedItem) => {
+        changedItem = <AttributeFEModel>changedItem;
+        this.attributesUtils.resetAttributeValue(changedItem, changedItem.value);
+        this.checkedAttributesCount = 0;
+      };
+    } else if (this.isOutputsTabSelected) {
+      handleReverseItem = (changedItem) => {
+        changedItem = <OutputFEModel>changedItem;
+        this.outputsUtils.resetOutputDefaultValue(changedItem, changedItem.defaultValue);
+        changedItem.required = changedItem.requiredOrig;
+      };
+    }
+
+    this.changedData.forEach(handleReverseItem);
+    this.changedData = [];
+    this.updateHasChangedData();
+  };
+
+  updateHasChangedData = (): boolean => {
+    const curHasChangedData: boolean = (this.changedData.length > 0);
+    if (curHasChangedData !== this.hasChangedData) {
+      this.hasChangedData = curHasChangedData;
+      if (this.hasChangedData) {
+        this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
+      } else {
+        this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
+      }
+    }
+    return this.hasChangedData;
+  };
+
+  doSaveChangedData = (onSuccessFunction?: Function, onError?: Function): void => {
+    this.saveChangedData().then(
+        () => {
+          this.Notification.success({
+            message: 'Successfully saved changes',
+            title: 'Saved'
+          });
+          if (onSuccessFunction) onSuccessFunction();
+          if (this.isAttributesTabSelected) {
+            this.checkedAttributesCount = 0;
+          }
+        },
+        () => {
+          this.Notification.error({
+            message: 'Failed to save changes!',
+            title: 'Failure'
+          });
+          if (onError) onError();
+        }
+    );
+  };
+
+  showUnsavedChangesAlert = (): Promise<any> => {
+    let modalTitle: string;
+    if (this.isAttributesTabSelected) {
+      modalTitle = `Unsaved attributes for ${this.selectedInstanceData.name}`;
+    } else if (this.isOutputsTabSelected) {
+      modalTitle = `Unsaved outputs for ${this.component.name}`;
+    }
+
+    return new Promise<any>((resolve, reject) => {
+      this.ModalServiceSdcUI.openCustomModal(
+          {
+            title: modalTitle,
+            size: 'sm',
+            type: SdcUiCommon.ModalType.custom,
+            testId: "navigate-modal",
+
+            buttons: [
+              {
+                id: 'cancelButton',
+                text: 'Cancel',
+                type: SdcUiCommon.ButtonType.secondary,
+                size: 'xsm',
+                closeModal: true,
+                callback: () => reject()
+              },
+              {
+                id: 'discardButton',
+                text: 'Discard',
+                type: SdcUiCommon.ButtonType.secondary,
+                size: 'xsm',
+                closeModal: true,
+                callback: () => {
+                  this.reverseChangedData();
+                  resolve()
+                }
+              },
+              {
+                id: 'saveButton',
+                text: 'Save',
+                type: SdcUiCommon.ButtonType.primary,
+                size: 'xsm',
+                closeModal: true,
+                disabled: !this.isValidChangedData,
+                callback: () => this.doSaveChangedData(resolve, reject)
+              }
+            ] as SdcUiCommon.IModalButtonComponent[]
+          } as SdcUiCommon.IModalConfig, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
+    });
+
+  }
+
+  updateAttributeValueAfterDeclare = (output: OutputFEModel) => {
+    const attributeList = this.instanceFeAttributesMap[output.instanceUniqueId];
+    if (attributeList) {
+      const instanceName = output.instanceUniqueId.slice(output.instanceUniqueId.lastIndexOf('.') + 1);
+      const attributeForUpdatingVal = attributeList.find((feAttribute: AttributeFEModel) => {
+        return feAttribute.name == output.relatedAttributeName &&
+            (feAttribute.name == output.relatedAttributeName || output.name === instanceName.concat('_').concat(feAttribute.name.replace(/[.]/g, '_')));
+      });
+      const outputPath = (output.outputPath && output.outputPath != attributeForUpdatingVal.name) ? output.outputPath : undefined;
+      attributeForUpdatingVal.setAsDeclared(outputPath); //set attribute as declared before assigning value
+      // this.attributesService.disableRelatedAttributes(attributeForUpdatingVal, outputPath);
+      this.attributesUtils.resetAttributeValue(attributeForUpdatingVal, output.relatedAttributeValue, outputPath);
+    }
+  }
+
+  //used for declare button, to keep count of newly checked attributes (and ignore declared attributes)
+  updateCheckedAttributeCount = (increment: boolean): void => {
+    this.checkedAttributesCount += (increment) ? 1 : -1;
+  };
+
+  setOutputTabIndication = (numOutputs: number): void => {
+    this.attributeOutputTabs.setTabIndication('Outputs', numOutputs);
+  };
+
+
+  resetUnsavedChangesForOutput = (output: OutputFEModel) => {
+    this.outputsUtils.resetOutputDefaultValue(output, output.defaultValue);
+    this.changedData = this.changedData.filter((changedItem) => changedItem.uniqueId !== output.uniqueId);
+    this.updateHasChangedData();
+  }
+
+  deleteOutput = (output: OutputFEModel) => {
+    //reset any unsaved changes to the output before deleting it
+    this.resetUnsavedChangesForOutput(output);
+
+    let outputToDelete = new OutputBEModel(output);
+
+    this.componentServiceNg2
+    .deleteOutput(this.component, outputToDelete)
+    .subscribe(response => {
+      this.outputs = this.outputs.filter(output => output.uniqueId !== response.uniqueId);
+
+      //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning attributes within the response, use commented code below instead!
+      this.changeSelectedInstance(this.selectedInstanceData);
+    }, error => {
+      this.Notification.error({
+        message: 'Failed to delete Output:' + error,
+        title: 'Failure'
+      });
+    });
+  };
+
+  deleteAttribute = (attribute: AttributeFEModel) => {
+    const attributeToDelete = new AttributeFEModel(attribute);
+    this.loadingAttributes = true;
+    const feMap = this.instanceFeAttributesMap;
+    this.topologyTemplateService
+    .deleteServiceAttribute(this.component.uniqueId, attributeToDelete)
+    .subscribe((response) => {
+      const attribs = feMap[this.component.uniqueId];
+      attribs.splice(attribs.findIndex(p => p.uniqueId === response), 1);
+    }, (error) => {
+      this.Notification.error({
+        message: 'Failed to delete Attribute:' + error,
+        title: 'Failure'
+      });
+    }, () => {
+      this.loadingAttributes = false;
+    });
+  }
+
+  addAttribute = () => {
+    let modalTitle = 'Add Attribute';
+    let modal = this.ModalService.createCustomModal(new ModalModel(
+        'sm',
+        modalTitle,
+        null,
+        [
+          new ButtonModel('Save', 'blue', () => {
+            modal.instance.dynamicContent.instance.isLoading = true;
+            const newAttribute: AttributeBEModel = modal.instance.dynamicContent.instance.attributeModel;
+            this.topologyTemplateService.createServiceAttribute(this.component.uniqueId, newAttribute)
+            .subscribe((response) => {
+              modal.instance.dynamicContent.instance.isLoading = false;
+              const newAttrib: AttributeFEModel = this.attributesUtils.convertAddAttributeBEToAttributeFE(response);
+              this.instanceFeAttributesMap[this.component.uniqueId].push(newAttrib);
+              modal.instance.close();
+            }, (error) => {
+              modal.instance.dynamicContent.instance.isLoading = false;
+              this.Notification.error({
+                message: 'Failed to add Attribute:' + error,
+                title: 'Failure'
+              });
+            });
+          }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
+          new ButtonModel('Cancel', 'outline grey', () => {
+            modal.instance.close();
+          }),
+        ],
+        null
+    ));
+    this.ModalService.addDynamicContentToModal(modal, AttributeCreatorComponent, {});
+    modal.instance.open();
+  }
+
+  private isOutput = (instanceType: string): boolean => {
+    return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/services/attributes.utils.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/services/attributes.utils.ts
new file mode 100644 (file)
index 0000000..51b1823
--- /dev/null
@@ -0,0 +1,198 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import { Injectable } from '@angular/core';
+import { DataTypeService } from "app/ng2/services/data-type.service";
+import { PROPERTY_TYPES } from "app/utils";
+import { AttributesService } from "app/ng2/services/attributes.service";
+import { InstanceBeAttributesMap, InstanceFeAttributesMap } from "app/models/attributes-outputs/attribute-fe-map";
+import {OutputFEModel} from "../../../../models/attributes-outputs/output-fe-model";
+import { AttributeBEModel, DerivedAttributeType } from "app/models/attributes-outputs/attribute-be-model";
+import { AttributeFEModel } from "app/models/attributes-outputs/attribute-fe-model";
+import { DerivedFEAttribute } from "app/models/attributes-outputs/derived-fe-attribute";
+import { DataTypeModel } from "app/models";
+
+@Injectable()
+export class AttributesUtils {
+
+    constructor(private dataTypeService:DataTypeService, private attributesService: AttributesService) {}
+
+    /**
+     * Entry point when getting attributes from server
+     * For each instance, loop through each property, and:
+     * 1. Create flattened children
+     * 2. Check against outputs to see if any props are declared and disable them
+     * 3. Initialize valueObj (which also creates any new list/map flattened children as needed)
+     * Returns InstanceFeAttributesMap
+     */
+    public convertAttributesMapToFEAndCreateChildren = (instanceAttributesMap:InstanceBeAttributesMap, isVF:boolean, outputs?:Array<OutputFEModel>): InstanceFeAttributesMap => {
+        let instanceFeAttributesMap:InstanceFeAttributesMap = new InstanceFeAttributesMap();
+        angular.forEach(instanceAttributesMap, (attributes:Array<AttributeBEModel>, instanceId:string) => {
+            let propertyFeArray: Array<AttributeFEModel> = [];
+            _.forEach(attributes, (property: AttributeBEModel) => {
+
+                if (this.dataTypeService.getDataTypeByTypeName(property.type)) { // if type not exist in data types remove property from list
+
+                    let newFEAttrib: AttributeFEModel = new AttributeFEModel(property); //Convert property to FE
+
+                    this.initValueObjectRef(newFEAttrib); //initialize valueObj AND creates flattened children
+                    propertyFeArray.push(newFEAttrib);
+                    newFEAttrib.updateExpandedChildAttributeId(newFEAttrib.name); //display only the first level of children
+
+                    //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children
+                    if (newFEAttrib.getOutputValues && newFEAttrib.getOutputValues.length) {
+                        newFEAttrib.getOutputValues.forEach(propOutputDetail => {
+                            let outputPath = propOutputDetail.outputPath;
+                            if (!outputPath) { //TODO: this is a workaround until Marina adds outputPath
+                                let output = outputs.find(output => output.uniqueId == propOutputDetail.outputId);
+                                if (!output) { console.log("CANNOT FIND INPUT FOR " + propOutputDetail.outputId); return; }
+                                else outputPath = output.outputPath;
+                            }
+                            if (outputPath == newFEAttrib.name) outputPath = undefined; // if not complex we need to remove the outputPath from FEModel so we not look for a child
+                            newFEAttrib.setAsDeclared(outputPath); //if a path is sent, its a child prop. this param is optional
+                            this.attributesService.disableRelatedAttributes(newFEAttrib, outputPath);
+                        });
+                    }
+                }
+            });
+            instanceFeAttributesMap[instanceId] = propertyFeArray;
+
+        });
+        return instanceFeAttributesMap;
+    }
+
+    public convertAddAttributeBEToAttributeFE = (property: AttributeBEModel): AttributeFEModel => {
+        const newFEProp: AttributeFEModel = new AttributeFEModel(property); //Convert property to FE
+        this.initValueObjectRef(newFEProp);
+        newFEProp.updateExpandedChildAttributeId(newFEProp.name); //display only the first level of children
+        return newFEProp;
+    }
+
+    public createListOrMapChildren = (property:AttributeFEModel | DerivedFEAttribute, key: string, valueObj: any): Array<DerivedFEAttribute> => {
+        let newProps: Array<DerivedFEAttribute> = [];
+        let parentProp = new DerivedFEAttribute(property, property.attributesName, true, key, valueObj);
+        newProps.push(parentProp);
+
+        if (!property.schema.property.isSimpleType) {
+            let additionalChildren:Array<DerivedFEAttribute> = this.createFlattenedChildren(property.schema.property.type, parentProp.attributesName);
+            this.assignFlattenedChildrenValues(parentProp.valueObj, additionalChildren, parentProp.attributesName);
+            additionalChildren.forEach(prop => prop.canBeDeclared = false);
+            newProps.push(...additionalChildren);
+        }
+        return newProps;
+    }
+
+    /**
+     * Creates derivedFEAttributes of a specified type and returns them.
+     */
+    private createFlattenedChildren = (type: string, parentName: string):Array<DerivedFEAttribute> => {
+        let tempProps: Array<DerivedFEAttribute> = [];
+        let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(type);
+        this.dataTypeService.getDerivedDataTypeAttributes(dataTypeObj, tempProps, parentName);
+        return _.sortBy(tempProps, ['propertiesName']);
+    }
+
+    /* Sets the valueObj of parent property and its children.
+    * Note: This logic is different than assignflattenedchildrenvalues - here we merge values, there we pick either the parents value, props value, or default value - without merging.
+    */
+    public initValueObjectRef = (attribute: AttributeFEModel): void => {
+        attribute.resetValueObjValidation();
+        if (attribute.isDeclared) { //if attribute is declared, it gets a simple output instead. List and map values and pseudo-children will be handled in attribute component
+            attribute.valueObj = attribute.value || attribute.defaultValue || null;  // use null for empty value object
+            if (attribute.valueObj && typeof attribute.valueObj == 'object') {
+                attribute.valueObj = JSON.stringify(attribute.valueObj);
+            }
+        } else {
+            attribute.valueObj = attribute.getValueObj();
+            if (attribute.derivedDataType == DerivedAttributeType.LIST || attribute.derivedDataType == DerivedAttributeType.MAP) {
+                attribute.flattenedChildren = [];
+                Object.keys(attribute.valueObj).forEach((key) => {
+                    attribute.flattenedChildren.push(...this.createListOrMapChildren(attribute, key, attribute.valueObj[key]))
+                });
+            } else if (attribute.derivedDataType === DerivedAttributeType.COMPLEX) {
+                attribute.flattenedChildren = this.createFlattenedChildren(attribute.type, attribute.name);
+                this.assignFlattenedChildrenValues(attribute.valueObj, attribute.flattenedChildren, attribute.name);
+                attribute.flattenedChildren.forEach((childProp) => {
+                    attribute.childPropUpdated(childProp);
+                });
+            }
+        }
+        attribute.updateValueObjOrig();
+    };
+
+    /*
+    * Loops through flattened attributes array and to assign values
+    * Then, convert any neccessary strings to objects, and vis-versa
+    * For list or map property, creates new children props if valueObj has values
+    */
+    public assignFlattenedChildrenValues = (parentValueJSON: any, derivedPropArray: Array<DerivedFEAttribute>, parentName: string) => {
+        if (!derivedPropArray || !parentName) return;
+        let propsToPushMap: Map<number, Array<DerivedFEAttribute>> = new Map<number, Array<DerivedFEAttribute>>();
+        derivedPropArray.forEach((prop, index) => {
+
+            let propNameInObj = prop.attributesName.substring(prop.attributesName.indexOf(parentName) + parentName.length + 1).split('#').join('.'); //extract everything after parent name
+            prop.valueObj = _.get(parentValueJSON, propNameInObj, prop.value || prop.defaultValue || null); //assign value -first value of parent if exists. If not, prop.value if not, prop.defaultvalue
+            prop.value = (prop.valueObj !== null && (typeof prop.valueObj) != 'string') ? JSON.stringify(prop.valueObj) : prop.valueObj;
+
+            if ((prop.isDeclared || prop.type == PROPERTY_TYPES.STRING || prop.type == PROPERTY_TYPES.JSON)) { //Stringify objects of items that are declared or from type string/json
+                prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == 'object') ? JSON.stringify(prop.valueObj) : prop.valueObj;
+            } else if(prop.type == PROPERTY_TYPES.INTEGER || prop.type == PROPERTY_TYPES.FLOAT || prop.type == PROPERTY_TYPES.BOOLEAN){ //parse ints and non-string simple types
+                prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == PROPERTY_TYPES.STRING) ? JSON.parse(prop.valueObj) : prop.valueObj;
+            } else { //parse strings that should be objects
+                if (prop.derivedDataType == DerivedAttributeType.COMPLEX) {
+                    prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
+                } else if (prop.derivedDataType == DerivedAttributeType.LIST) {
+                    prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '[]') : prop.valueObj;
+                } else if (prop.derivedDataType == DerivedAttributeType.MAP) {
+                    if (!prop.isChildOfListOrMap || !prop.schema.property.isSimpleType) {
+                        prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
+                    }
+                }
+                if ((prop.derivedDataType == DerivedAttributeType.LIST || prop.derivedDataType == DerivedAttributeType.MAP) && typeof prop.valueObj == 'object' && prop.valueObj !== null && Object.keys(prop.valueObj).length) {
+                    let newProps: Array<DerivedFEAttribute> = [];
+                    Object.keys(prop.valueObj).forEach((key) => {
+                        newProps.push(...this.createListOrMapChildren(prop, key, prop.valueObj[key]));//create new children, assign their values, and then add to array
+                    });
+                    propsToPushMap[index + 1] = newProps;
+                }
+            }
+
+            prop.valueObj = AttributeFEModel.cleanValueObj(prop.valueObj);
+        });
+
+        //add props after we're done looping (otherwise our loop gets messed up). Push in reverse order, so we dont mess up indexes.
+        Object.keys(propsToPushMap).reverse().forEach((indexToInsert) => {
+            derivedPropArray.splice(+indexToInsert, 0, ...propsToPushMap[indexToInsert]); //slacker parsing
+        });
+    }
+
+    public resetAttributeValue = (attribute: AttributeFEModel, newValue: string, nestedPath?: string): void => {
+        attribute.value = newValue;
+        if (nestedPath) {
+            let newAttrib = attribute.flattenedChildren.find(attrib => attrib.attributesName == nestedPath);
+            newAttrib && this.assignFlattenedChildrenValues(JSON.parse(newValue), [newAttrib], attribute.name);
+            attribute.updateValueObjOrig();
+        } else {
+            this.initValueObjectRef(attribute);
+        }
+    }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/services/hierarchy-nav.service.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/services/hierarchy-nav.service.ts
new file mode 100644 (file)
index 0000000..cef2eb2
--- /dev/null
@@ -0,0 +1,84 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Injectable} from '@angular/core';
+import {AttributeFEModel} from "../../../../models/attributes-outputs/attribute-fe-model";
+import {SimpleFlatAttribute} from "app/models/attributes-outputs/simple-flat-attribute";
+import {DerivedFEAttribute} from "../../../../models/attributes-outputs/derived-fe-attribute";
+
+@Injectable()
+export class HierarchyNavService {
+  /**
+   * Build hierarchy structure for the tree when user selects on table row.
+   * First create Array<SimpleFlatAttribute> and insert also the parent (AttributeFEModel) to this array.
+   * The Array is flat and contains SimpleFlatAttribute that has parentName and uniqueId.
+   * Now we build hierarchy from this Array (that includes children) and return it for the tree
+   *
+   * @argument attribute: AttributeFEModel - attribute contains flattenedChildren array of DerivedFEAttribute
+   * @returns  Array<SimpleFlatAttribute> - containing children Array<SimpleFlatAttribute>, augmantin children to SimpleFlatAttribute.
+   */
+  public getSimpleAttributesTree(attribute: AttributeFEModel, instanceName: string): Array<SimpleFlatAttribute> {
+    // Build Array of SimpleFlatAttribute before unflatten function
+    let flatAttributes: Array<SimpleFlatAttribute> = [];
+    flatAttributes.push(this.createSimpleFlatAttribute(attribute, instanceName)); // Push the root attribute
+    attribute.flattenedChildren.forEach((child: DerivedFEAttribute): void => {
+      if (child.isChildOfListOrMap && child.schema.property.isSimpleType) return; //do not display non-complex children of list or map
+      flatAttributes.push(this.createSimpleFlatAttribute(child, instanceName));
+    });
+
+    let tree = this.unflatten(flatAttributes, '', []);
+    return tree[0].childrens; // Return the childrens without the root.
+  }
+
+  public createSimpleFlatAttribute = (attribute: AttributeFEModel | DerivedFEAttribute, instanceName: string): SimpleFlatAttribute => {
+    if (attribute instanceof AttributeFEModel) {
+      return new SimpleFlatAttribute(attribute.uniqueId, attribute.name, attribute.name, '', instanceName);
+    } else {
+      let attribName: string = (attribute.isChildOfListOrMap) ? attribute.mapKey : attribute.name;
+      return new SimpleFlatAttribute(attribute.uniqueId, attribute.attributesName, attribName, attribute.parentName, instanceName);
+    }
+
+  }
+
+  /**
+   * Unflatten Array<SimpleFlatAttribute> and build hierarchy.
+   * The result will be Array<SimpleFlatAttribute> that augmantin with children for each SimpleFlatAttribute.
+   */
+  private unflatten(array: Array<SimpleFlatAttribute>, parent: any, tree?: any): any {
+    tree = typeof tree !== 'undefined' ? tree : [];
+    parent = typeof parent !== 'undefined' && parent !== '' ? parent : {path: ''};
+
+    var children = array.filter((child: SimpleFlatAttribute): boolean => {
+      return child.parentName == parent.path;
+    });
+
+    if (children && children.length) {
+      if (parent.path == '') {
+        tree = children;
+      } else {
+        parent['children'] = children;
+      }
+      children.forEach((child): void => {
+        this.unflatten(array, child);
+      });
+    }
+    return tree;
+  }
+}
diff --git a/catalog-ui/src/app/ng2/pages/attributes-outputs/services/outputs.utils.ts b/catalog-ui/src/app/ng2/pages/attributes-outputs/services/outputs.utils.ts
new file mode 100644 (file)
index 0000000..c2df55e
--- /dev/null
@@ -0,0 +1,40 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import { Injectable } from '@angular/core';
+import {OutputFEModel} from "../../../../models/attributes-outputs/output-fe-model";
+
+@Injectable()
+export class OutputsUtils {
+
+    constructor() {}
+
+    public initDefaultValueObject = (output: OutputFEModel): void => {
+        output.resetDefaultValueObjValidation();
+        output.defaultValueObj = output.getDefaultValueObj();
+        output.updateDefaultValueObjOrig();
+    };
+
+    public resetOutputDefaultValue = (output: OutputFEModel, newDefaultValue: string): void => {
+        output.defaultValue = newDefaultValue;
+        this.initDefaultValueObject(output);
+    }
+
+}
index 3def63e..0d676ed 100644 (file)
@@ -17,8 +17,7 @@
  * limitations under the License.
  * ============LICENSE_END=========================================================
  */
-import { NgModule } from "@angular/core";
-import {HierarchyNavigationComponent} from "../../components/logic/hierarchy-navigtion/hierarchy-navigation.component";
+import {NgModule} from "@angular/core";
 import {FormsModule} from "@angular/forms";
 import {PropertyTableModule} from "../../components/logic/properties-table/property-table.module";
 import {UiElementsModule} from "../../components/ui/ui-elements.module";
@@ -36,29 +35,30 @@ import {InputsUtils} from "./services/inputs.utils";
 import {ComponentModeService} from "../../services/component-services/component-mode.service";
 import {SdcUiComponentsModule} from "onap-ui-angular";
 import {ModalFormsModule} from "app/ng2/components/ui/forms/modal-forms.module";
+import {HierarchyNavigationModule} from "../../components/logic/hierarchy-navigtion/hierarchy-navigation.module";
 
 @NgModule({
-    declarations: [
-        PropertiesAssignmentComponent,
-        InputsTableComponent,
-        HierarchyNavigationComponent,
-        FilterPropertiesAssignmentComponent
-    ],
-    imports: [
-        BrowserModule,
-        FormsModule,
-        GlobalPipesModule,
-        PropertyTableModule,
-        PoliciesTableModule,
-        UiElementsModule,
-        SdcUiComponentsModule,
-        ModalFormsModule],
+  declarations: [
+    PropertiesAssignmentComponent,
+    InputsTableComponent,
+    FilterPropertiesAssignmentComponent
+  ],
+  imports: [
+    BrowserModule,
+    FormsModule,
+    GlobalPipesModule,
+    PropertyTableModule,
+    PoliciesTableModule,
+    HierarchyNavigationModule,
+    UiElementsModule,
+    SdcUiComponentsModule,
+    ModalFormsModule],
 
-    entryComponents: [PropertiesAssignmentComponent],
-    exports: [
-        PropertiesAssignmentComponent
-    ],
-    providers: [PropertiesService, HierarchyNavService, PropertiesUtils, InputsUtils, DataTypeService, ComponentModeService]
+  entryComponents: [PropertiesAssignmentComponent],
+  exports: [
+    PropertiesAssignmentComponent
+  ],
+  providers: [PropertiesService, HierarchyNavService, PropertiesUtils, InputsUtils, DataTypeService, ComponentModeService]
 })
 export class PropertiesAssignmentModule {
 
index 5053d52..8167caa 100644 (file)
@@ -6,6 +6,7 @@ import { DataTypeService } from 'app/ng2/services/data-type.service';
 import { PROPERTY_DATA } from 'app/utils';
 import * as _ from 'lodash';
 import { PROPERTY_TYPES } from '../../../../utils';
+import {Validation} from "../../../../view-models/workspace/tabs/general/general-view-model";
 
 @Component({
     selector: 'property-creator',
@@ -15,12 +16,10 @@ import { PROPERTY_TYPES } from '../../../../utils';
 
 export class PropertyCreatorComponent {
 
+    validation:Validation;
     typesProperties: DropdownValue[];
     typesSchemaProperties: DropdownValue[];
     propertyModel: PropertyBEModel;
-    // propertyNameValidationPattern:RegExp = /^[a-zA-Z0-9_:-]{1,50}$/;
-    // commentValidationPattern:RegExp = /^[\u0000-\u00BF]*$/;
-    // types:Array<string>;
     dataTypes: DataTypesMap;
     isLoading: boolean;
 
index b0a7651..426ed40 100644 (file)
@@ -11,7 +11,7 @@ import { AttributeOptions } from './attributes-options';
 @Component({
     selector: 'attribute-modal',
     templateUrl: './attribute-modal.component.html',
-    styleUrls: ['./attributes.component.less']
+    styleUrls: ['../../../../view-models/workspace/tabs/attributes/attributes.component.less']
 })
 export class AttributeModalComponent implements OnInit {
 
diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts
deleted file mode 100644 (file)
index f676e2b..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture } from '@angular/core/testing';
-import { NgxDatatableModule } from '@swimlane/ngx-datatable';
-import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular';
-import 'rxjs/add/observable/of';
-import { Observable } from 'rxjs/Rx';
-import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper';
-import { ComponentMetadata } from '../../../../models/component-metadata';
-import { ModalsHandler } from '../../../../utils';
-import { TopologyTemplateService } from '../../../services/component-services/topology-template.service';
-import { TranslateService } from '../../../shared/translator/translate.service';
-import { WorkspaceService } from '../workspace.service';
-import { AttributesComponent } from './attributes.component';
-
-describe('attributes component', () => {
-
-    let fixture: ComponentFixture<AttributesComponent>;
-
-    // Mocks
-    let workspaceServiceMock: Partial<WorkspaceService>;
-    let topologyTemplateServiceMock: Partial<TopologyTemplateService>;
-    let loaderServiceMock: Partial<SdcUiServices.LoaderService>;
-    let componentMetadataMock: ComponentMetadata;
-    let modalServiceMock: Partial<SdcUiServices.ModalService>;
-
-    const mockAttributesList = [
-        { uniqueId: '1', name: 'attr1', description: 'description1', type: 'string', hidden: false, defaultValue: 'val1', schema: null },
-        { uniqueId : '2', name : 'attr2', description: 'description2', type : 'int', hidden : false, defaultValue : 1, schema : null},
-        { uniqueId : '3', name : 'attr3', description: 'description3', type : 'double', hidden : false, defaultValue : 1.0, schema : null},
-        { uniqueId : '4', name : 'attr4', description: 'description4', type : 'boolean', hidden : false, defaultValue : true, schema : null},
-    ];
-
-    const newAttribute = {
-        uniqueId : '5', name : 'attr5', description: 'description5', type : 'string', hidden : false, defaultValue : 'val5', schema : null
-    };
-    const updatedAttribute = {
-        uniqueId : '2', name : 'attr2', description: 'description_new', type : 'string', hidden : false, defaultValue : 'new_val2', schema : null
-    };
-    const errorAttribute = {
-        uniqueId : '99', name : 'attr99', description: 'description_error', type : 'string', hidden : false, defaultValue : 'error', schema : null
-    };
-
-    beforeEach(
-        async(() => {
-
-            componentMetadataMock = new ComponentMetadata();
-            componentMetadataMock.uniqueId = 'fake';
-            componentMetadataMock.componentType = 'VL';
-
-            topologyTemplateServiceMock = {
-                getComponentAttributes: jest.fn().mockResolvedValue({ attributes : mockAttributesList }),
-                addAttributeAsync: jest.fn().mockImplementation(
-                    (compType, cUid, attr) => {
-                        if (attr === errorAttribute) {
-                            return Observable.throwError('add_error').toPromise();
-                        } else {
-                            return Observable.of(newAttribute).toPromise();
-                        }
-                    }
-                ),
-                updateAttributeAsync: jest.fn().mockImplementation(
-                    (compType, cUid, attr) => {
-                        if (attr === errorAttribute) {
-                            return Observable.throwError('update_error').toPromise();
-                        } else {
-                            return Observable.of(updatedAttribute).toPromise();
-                        }
-                    }
-                ),
-                deleteAttributeAsync: jest.fn().mockImplementation((cid, ctype, attr) => Observable.of(attr))
-            };
-
-            workspaceServiceMock = {
-                metadata: componentMetadataMock
-            };
-
-            const customModalInstance = { innerModalContent: { instance: { onValidationChange: { subscribe: jest.fn()}}}};
-
-            modalServiceMock = {
-                openInfoModal: jest.fn(),
-                openCustomModal: jest.fn().mockImplementation(() => customModalInstance)
-            };
-
-            loaderServiceMock = {
-                activate: jest.fn(),
-                deactivate: jest.fn()
-            };
-
-            const configure: ConfigureFn = (testBed) => {
-                testBed.configureTestingModule({
-                    declarations: [AttributesComponent],
-                    imports: [NgxDatatableModule],
-                    schemas: [NO_ERRORS_SCHEMA],
-                    providers: [
-                        {provide: WorkspaceService, useValue: workspaceServiceMock},
-                        {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock},
-                        {provide: ModalsHandler, useValue: {}},
-                        {provide: TranslateService, useValue: { translate: jest.fn() }},
-                        {provide: SdcUiServices.ModalService, useValue: modalServiceMock },
-                        {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock }
-                    ],
-                });
-            };
-
-            configureTests(configure).then((testBed) => {
-                fixture = testBed.createComponent(AttributesComponent);
-            });
-        })
-    );
-
-    it('should see exactly 1 attributes on init', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        expect(fixture.componentInstance.getAttributes().length).toEqual(4);
-    });
-
-    it('should see exactly 5 attributes when adding', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        expect(fixture.componentInstance.getAttributes().length).toEqual(4);
-
-        await fixture.componentInstance.addOrUpdateAttribute(newAttribute, false);
-        expect(fixture.componentInstance.getAttributes().length).toEqual(5);
-    });
-
-    it('should see exactly 3 attributes when deleting', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        expect(fixture.componentInstance.getAttributes().length).toEqual(4);
-        const attrToDelete = mockAttributesList[0];
-        expect(fixture.componentInstance.getAttributes().filter((attr) => attr.uniqueId === attrToDelete.uniqueId).length).toEqual(1);
-        await fixture.componentInstance.deleteAttribute(attrToDelete);
-        expect(fixture.componentInstance.getAttributes().length).toEqual(3);
-        expect(fixture.componentInstance.getAttributes().filter((attr) => attr.uniqueId === attrToDelete.uniqueId).length).toEqual(0);
-    });
-
-    it('should see updated attribute', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-
-        await fixture.componentInstance.addOrUpdateAttribute(updatedAttribute, true);
-        expect(fixture.componentInstance.getAttributes().length).toEqual(4);
-        const attribute = fixture.componentInstance.getAttributes().filter( (attr) => {
-            return attr.uniqueId === updatedAttribute.uniqueId;
-        })[0];
-        expect(attribute.description).toEqual( 'description_new');
-    });
-
-    it('Add fails, make sure loader is deactivated and attribute is not added', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        const numAttributes = fixture.componentInstance.getAttributes().length;
-        await fixture.componentInstance.addOrUpdateAttribute(errorAttribute, false); // Add
-        expect(loaderServiceMock.deactivate).toHaveBeenCalled();
-        expect(fixture.componentInstance.getAttributes().length).toEqual(numAttributes);
-    });
-
-    it('Update fails, make sure loader is deactivated', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        const numAttributes = fixture.componentInstance.getAttributes().length;
-        await fixture.componentInstance.addOrUpdateAttribute(errorAttribute, true); // Add
-        expect(loaderServiceMock.deactivate).toHaveBeenCalled();
-        expect(fixture.componentInstance.getAttributes().length).toEqual(numAttributes);
-    });
-
-    it('on delete modal shell be opened', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        const event = { stopPropagation: jest.fn() };
-        fixture.componentInstance.onDeleteAttribute(event, fixture.componentInstance.getAttributes()[0]);
-        expect(event.stopPropagation).toHaveBeenCalled();
-        expect(modalServiceMock.openInfoModal).toHaveBeenCalled();
-    });
-
-    it('on add modal shell be opened', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        fixture.componentInstance.onAddAttribute();
-        expect(modalServiceMock.openCustomModal).toHaveBeenCalled();
-    });
-
-    it('on edit modal shell be opened', async () => {
-        await fixture.componentInstance.asyncInitComponent();
-        const event = { stopPropagation: jest.fn() };
-        fixture.componentInstance.onEditAttribute(event, fixture.componentInstance.getAttributes()[0]);
-        expect(event.stopPropagation).toHaveBeenCalled();
-        expect(modalServiceMock.openCustomModal).toHaveBeenCalled();
-    });
-});
index 5abb952..f85d529 100644 (file)
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 import { SdcUiComponentsModule } from 'onap-ui-angular';
 import { GlobalPipesModule } from '../../../pipes/global-pipes.module';
-import { AttributesComponent } from './attributes.component';
+import { AttributesComponent } from '../../../../view-models/workspace/tabs/attributes/attributes.component';
 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
 import { TopologyTemplateService } from '../../../services/component-services/topology-template.service';
 import { AttributeModalComponent } from './attribute-modal.component';
diff --git a/catalog-ui/src/app/ng2/services/attributes.service.ts b/catalog-ui/src/app/ng2/services/attributes.service.ts
new file mode 100644 (file)
index 0000000..5086fbf
--- /dev/null
@@ -0,0 +1,92 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Injectable} from '@angular/core';
+import {AttributeFEModel} from "../../models/attributes-outputs/attribute-fe-model";
+import {AttributeBEModel} from "app/models/attributes-outputs/attribute-be-model";
+import {DerivedFEAttribute} from "../../models/attributes-outputs/derived-fe-attribute";
+import {AttributeDeclareAPIModel} from "app/models/attributes-outputs/attribute-declare-api-model";
+
+@Injectable()
+export class AttributesService {
+
+  constructor() {
+  }
+
+  public getParentAttributeFEModelFromPath = (attributes: Array<AttributeFEModel>, path: string) => {
+    let parent: AttributeFEModel = attributes.find((property: AttributeFEModel): boolean => {
+      return property.name === path.substring(0, path.indexOf('#'));
+    });
+    return parent;
+  }
+
+  //undo disabling of parent and child props=
+  public undoDisableRelatedAttributes = (property: AttributeFEModel, childPath?: string): void => {
+    property.isDisabled = false;
+    if (!childPath) {
+      property.isSelected = false;
+      property.flattenedChildren && property.flattenedChildren.map(child => child.isDisabled = false);
+    } else { //QND - unselect everything and then re-do the disabling of declared props. TODO: put a flag on propertyFEModel instead to indicate who's causing them to be disabled instead
+      property.flattenedChildren.filter(child => child.isDisabled && !child.isDeclared).forEach(child => child.isDisabled = false);
+      property.flattenedChildren.filter(child => child.isDeclared || child.isSelected).forEach((childProp) => { //handle brothers who are selected - redo their disabled relatives as well
+        this.disableRelatedAttributes(property, childProp.attributesName);
+      });
+    }
+  }
+
+  //disable parents and children of prop
+  public disableRelatedAttributes = (property: AttributeFEModel, childPath?: string): void => {
+    if (!childPath) { //selecting the parent property
+      property.isSelected = true;
+      property.flattenedChildren && property.flattenedChildren.map(child => {
+        child.isSelected = false;
+        child.isDisabled = true;
+      });
+    } else {
+      property.isSelected = false;
+      property.isDisabled = true;
+      property.flattenedChildren.filter((childProp: DerivedFEAttribute) => {
+        return (childProp.attributesName.indexOf(childPath + "#") === 0 //is child of prop to disable
+            || childPath.indexOf(childProp.attributesName + "#") === 0); //is parent of prop to disable
+      }).forEach((child: DerivedFEAttribute) => {
+        child.isSelected = false;
+        child.isDisabled = true;
+      });
+    }
+  }
+
+  public getCheckedAttributes = (attributes: Array<AttributeFEModel>): Array<AttributeBEModel> => {
+    let selectedProps: Array<AttributeDeclareAPIModel> = [];
+    attributes.forEach(attrib => {
+      if (attrib.isSelected && !attrib.isDeclared && !attrib.isDisabled) {
+        selectedProps.push(new AttributeDeclareAPIModel(attrib));
+      } else if (attrib.flattenedChildren) {
+        attrib.flattenedChildren.forEach((child) => {
+          if (child.isSelected && !child.isDeclared && !child.isDisabled) {
+            let childProp = new AttributeDeclareAPIModel(attrib, child); //create it from the parent
+            selectedProps.push(childProp);
+          }
+        })
+      }
+    });
+    return selectedProps;
+  }
+
+}
index 74ced7d..5ae2918 100644 (file)
@@ -27,6 +27,8 @@ import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config";
 import { HttpClient, HttpHeaders } from '@angular/common/http';
 import { InputBEModel } from '../../../models/properties-inputs/input-be-model';
 import { HttpHelperService } from '../http-hepler.service';
+import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
+import {OutputBEModel} from "../../../models/attributes-outputs/output-be-model";
 
 @Injectable()
 export class ComponentInstanceServiceNg2 {
@@ -52,6 +54,13 @@ export class ComponentInstanceServiceNg2 {
         })
     }
 
+    getComponentInstanceAttributes(component: Component, componentInstanceId: string): Observable<Array<AttributeBEModel>> {
+        return this.http.get<Array<AttributeBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/attributes')
+            .map(res => {
+                return CommonUtils.initBeAttributes(res);
+        })
+    }
+
     getComponentInstanceInputs(component: Component, componentInstance: ComponentInstance): Observable<Array<PropertyBEModel>> {
         return this.http.get<Array<InputBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/inputs')
             .map(res => {
@@ -59,6 +68,13 @@ export class ComponentInstanceServiceNg2 {
             })
     }
 
+    getComponentInstanceOutputs(component: Component, componentInstance: ComponentInstance): Observable<Array<AttributeBEModel>> {
+        return this.http.get<Array<OutputBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/outputs')
+            .map(res => {
+                return CommonUtils.initOutputs(res);
+            })
+    }
+
     getComponentInstanceArtifactsByGroupType = (componentType:string, componentId:string, componentInstanceId:string, artifactGroupType:string):Observable<ArtifactGroupModel> => {
 
         return this.http.get<ArtifactGroupModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/resourceInstances/" + componentInstanceId + "/artifactsByType/" + artifactGroupType)
@@ -68,7 +84,7 @@ export class ComponentInstanceServiceNg2 {
     };
 
     getArtifactByGroupType = (componentType:string, componentId:string, artifactGroupType:string):Observable<ArtifactGroupModel> => {
-        
+
         return this.http.get<ArtifactGroupModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/artifactsByType/" + artifactGroupType)
             .map(response => new ArtifactGroupModel(response));
     };
@@ -99,6 +115,18 @@ export class ComponentInstanceServiceNg2 {
             });
     }
 
+    updateInstanceAttributes(componentType:string, componentId:string, componentInstanceId: string, attributes: AttributeBEModel[]) {
+
+        return this.http.post<Array<AttributeModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + componentInstanceId + '/attributes', attributes)
+            .map((res) => {
+                return res.map((resAttribute) => {
+                    let newAttrib = new AttributeModel(resAttribute);
+                    newAttrib.resourceInstanceUniqueId = componentInstanceId;
+                    return newAttrib;
+                });
+            });
+    }
+
     getInstanceCapabilityProperties(componentType: string, componentId: string, componentInstanceId: string, capability: Capability): Observable<Array<PropertyModel>> {
 
         return this.http.get<Array<PropertyModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/componentInstances/' + componentInstanceId + '/capability/' + capability.type +
@@ -138,6 +166,14 @@ export class ComponentInstanceServiceNg2 {
             });
     }
 
+    updateInstanceOutputs(component: Component, componentInstanceId: string, outputs: AttributeBEModel[]): Observable<AttributeBEModel[]> {
+
+        return this.http.post<Array<AttributeModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/outputs', outputs)
+            .map((res) => {
+                return res.map((resOutput) => new AttributeBEModel(resOutput));
+            });
+    }
+
     getComponentGroupInstanceProperties(component: Component, groupInstanceId: string): Observable<Array<PropertyBEModel>> {
         return this.http.get<Array<PropertyBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/groups/' + groupInstanceId + '/properties')
             .map((res) => {
index 3093e63..d406cf0 100644 (file)
@@ -43,6 +43,7 @@ import { PolicyInstance } from "../../../models/graph/zones/policy-instance";
 import { ConstraintObject } from "../../components/logic/service-dependencies/service-dependencies.component";
 import { Requirement } from "../../../models/requirement";
 import { Capability } from "../../../models/capability";
+import { OutputBEModel } from "app/models/attributes-outputs/output-be-model";
 
 /*
 PLEASE DO NOT USE THIS SERVICE IN ANGULAR2! Use the topology-template.service instead
@@ -59,7 +60,7 @@ export class ComponentServiceNg2 {
     protected getComponentDataByFieldsName(componentType:string, componentId:string, fields:Array<string>):Observable<ComponentGenericResponse> {
 
         let params: HttpParams = new HttpParams();
-        _.forEach(fields, (field:string):void => {
+        fields.forEach((field:string):void => {
             params = params.append(API_QUERY_PARAMS.INCLUDE, field);
         });
 
@@ -109,6 +110,10 @@ export class ComponentServiceNg2 {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_POLICIES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]);
     }
 
+    getComponentResourceAttributesData(component:Component):Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]);
+    }
+
     getComponentResourceInstances(component:Component):Observable<ComponentGenericResponse> {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]);
     }
@@ -208,8 +213,15 @@ export class ComponentServiceNg2 {
         return this.http.get<any>(this.baseUrl + 'interfaceLifecycleTypes')
             .map((res: any) => {
                 const interfaceMap = {};
-                _.forEach(res, (interf: any) => {
-                    interfaceMap[interf.toscaPresentation.type] = _.keys(interf.toscaPresentation.operations);
+                if (!res) {
+                    return interfaceMap;
+                }
+                Object.keys(res).forEach(interfaceName => {
+                    const interface1 = res[interfaceName];
+                    if (!interface1.toscaPresentation.operations) {
+                        return;
+                    }
+                    interfaceMap[interface1.toscaPresentation.type] = Object.keys(interface1.toscaPresentation.operations);
                 });
                 return interfaceMap;
             });
@@ -297,7 +309,6 @@ export class ComponentServiceNg2 {
         return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/archive', {})
     }
 
-
     deleteInput(component:Component, input:InputBEModel):Observable<InputBEModel> {
 
         return this.http.delete<InputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input')
@@ -306,6 +317,14 @@ export class ComponentServiceNg2 {
             })
     }
 
+    deleteOutput(component:Component, output:OutputBEModel):Observable<OutputBEModel> {
+
+        return this.http.delete<OutputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + output.uniqueId + '/output')
+            .map((res) => {
+                return new OutputBEModel(res);
+            })
+    }
+
     updateComponentInputs(component:Component, inputs:InputBEModel[]):Observable<InputBEModel[]> {
 
         return this.http.post<InputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', inputs)
@@ -314,9 +333,26 @@ export class ComponentServiceNg2 {
             })
     }
 
+    updateComponentOutputs(component:Component, outputs:OutputBEModel[]):Observable<OutputBEModel[]> {
+
+        return this.http.post<OutputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/outputs', outputs)
+            .map((res) => {
+                return res.map((output) => new OutputBEModel(output));
+            })
+    }
+
     filterComponentInstanceProperties(component:Component, filterData:FilterPropertiesAssignmentData):Observable<InstanceBePropertiesMap> {//instance-property-be-map
         let params: HttpParams = new HttpParams();
-        _.forEach(filterData.selectedTypes, (type:string) => {
+        filterData.selectedTypes.forEach((type:string) => {
+            params = params.append('resourceType', type);
+        });
+
+        return this.http.get<InstanceBePropertiesMap>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {params: params});
+    }
+
+    filterComponentInstanceAttributes(component:Component, filterData:FilterPropertiesAssignmentData):Observable<InstanceBePropertiesMap> {//instance-property-be-map
+        let params: HttpParams = new HttpParams();
+        filterData.selectedTypes.forEach((type:string) => {
             params = params.append('resourceType', type);
         });
 
index 9460a32..05384c9 100644 (file)
@@ -89,7 +89,9 @@ export class ServiceServiceNg2 extends ComponentServiceNg2 {
             COMPONENT_FIELDS.COMPONENT_INSTANCES_INTERFACES,
             COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES,
             COMPONENT_FIELDS.COMPONENT_INSTANCES_INPUTS,
+            COMPONENT_FIELDS.COMPONENT_INSTANCES_OUTPUTS,
             COMPONENT_FIELDS.COMPONENT_INPUTS,
+            COMPONENT_FIELDS.COMPONENT_OUTPUTS,
             COMPONENT_FIELDS.COMPONENT_INSTANCES,
             COMPONENT_FIELDS.COMPONENT_CAPABILITIES
         ]);
index 953f0a1..0249912 100644 (file)
@@ -70,6 +70,8 @@ import {
     ComponentInstanceInterfaceModel,
     InterfaceOperationModel
 } from "../../../models/interfaceOperation";
+import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
+import {InstanceAttributesAPIMap} from "../../../models/attributes-outputs/attribute-fe-map";
 
 /* we need to use this service from now, we will remove component.service when we finish remove the angular1.
  The service is duplicated since we can not use downgrades service with NGXS*/
@@ -126,6 +128,11 @@ export class TopologyTemplateService {
             [COMPONENT_FIELDS.COMPONENT_INPUTS, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_PROPERTIES]);
     }
 
+    getComponentOutputsWithAttributes(componentType: string, componentId: string): Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(componentType, componentId,
+            [COMPONENT_FIELDS.COMPONENT_OUTPUTS, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES, COMPONENT_FIELDS.COMPONENT_ATTRIBUTES,COMPONENT_FIELDS.COMPONENT_INSTANCES_OUTPUTS]);
+    }
+
     getComponentDeploymentArtifacts(component: Component): Observable<ComponentGenericResponse> {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS]);
     }
@@ -164,6 +171,11 @@ export class TopologyTemplateService {
         return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputs);
     }
 
+    createOutput(component: Component, outputsToCreate: InstanceAttributesAPIMap, isSelf: boolean): Observable<any> {
+        const outputs = isSelf ? { serviceProperties: outputsToCreate.componentInstanceAttributes } : outputsToCreate;
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/outputs', outputs);
+    }
+
     restoreComponent(componentType: string, componentId: string) {
         return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {});
     }
@@ -206,6 +218,16 @@ export class TopologyTemplateService {
             });
     }
 
+    createServiceAttribute(componentId: string, attributeModel: AttributeBEModel): Observable<AttributeBEModel> {
+        const serverObject = {};
+        serverObject[attributeModel.name] = attributeModel;
+        return this.http.post<AttributeBEModel>(this.baseUrl + 'services/' + componentId + '/attributes', serverObject)
+            .map((res) => {
+                const attribute: AttributeBEModel = new AttributeBEModel(res);
+                return attribute;
+            });
+    }
+
     getServiceProperties(componentId: string): Observable<PropertyBEModel[]> {
         return this.http.get<any>(this.baseUrl + 'services/' + componentId + '/properties')
             .map((res) => {
@@ -216,6 +238,16 @@ export class TopologyTemplateService {
             });
     }
 
+    getServiceAttributes(componentId: string): Observable<AttributeBEModel[]> {
+        return this.http.get<any>(this.baseUrl + 'services/' + componentId + '/attributes')
+            .map((res) => {
+                if (!res) {
+                    return new Array<AttributeBEModel>();
+                }
+                return CommonUtils.initAttributes(res);
+            });
+    }
+
     updateServiceProperties(componentId: string, properties: PropertyBEModel[]) {
         return this.http.put<any>( this.baseUrl + 'services/' + componentId + '/properties', properties)
             .map((res) => {
@@ -225,6 +257,15 @@ export class TopologyTemplateService {
             });
     }
 
+    updateServiceAttributes(componentId: string, attributes: AttributeBEModel[]) {
+        return this.http.put<any>( this.baseUrl + 'services/' + componentId + '/attributes', attributes)
+            .map((res) => {
+                const resJson = res;
+                return _.map(resJson,
+                    (resValue: AttributeBEModel) => new AttributeBEModel(resValue));
+            });
+    }
+
     deleteServiceProperty(componentId: string, property: PropertyBEModel): Observable<string> {
         return this.http.delete(this.baseUrl + 'services/' + componentId + '/properties/' + property.uniqueId )
             .map((res: Response) => {
@@ -242,6 +283,13 @@ export class TopologyTemplateService {
             });
     }
 
+    deleteServiceAttribute(componentId: string, attribute: AttributeBEModel): Observable<string> {
+        return this.http.delete(this.baseUrl + 'services/' + componentId + '/attributes/' + attribute.uniqueId )
+            .map((res: Response) => {
+                return attribute.uniqueId;
+            });
+    }
+
     getDependencies(componentType: string, componentId: string): Observable<IDependenciesServerResponse[]> {
         return this.http.get<IDependenciesServerResponse[]>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/dependencies');
     }
index 0559f35..30eb6f0 100644 (file)
@@ -23,6 +23,7 @@ import { Injectable } from '@angular/core';
 import { DataTypeModel, DataTypesMap, PropertyFEModel, DerivedFEProperty} from "app/models";
 import { DataTypesService } from "app/services/data-types-service";
 import { PROPERTY_DATA } from "app/utils";
+import {DerivedFEAttribute} from "../../models/attributes-outputs/derived-fe-attribute";
 
 /** This is a new service for NG2, to eventually replace app/services/data-types-service.ts
  *
@@ -57,7 +58,6 @@ export class DataTypeService {
         return null;
     }
 
-
     public getDerivedDataTypeProperties(dataTypeObj: DataTypeModel, propertiesArray: Array<DerivedFEProperty>, parentName: string) {
         //push all child properties to array
         if (!dataTypeObj) return;
@@ -76,6 +76,24 @@ export class DataTypeService {
         }
     }
 
+    public getDerivedDataTypeAttributes(dataTypeObj: DataTypeModel, attributesArray: Array<DerivedFEAttribute>, parentName: string) {
+        //push all child properties to array
+        if (!dataTypeObj) return;
+        if (dataTypeObj.attributes) {
+            dataTypeObj.attributes.forEach((derivedAttribute) => {
+                if(dataTypeObj.name !== PROPERTY_DATA.OPENECOMP_ROOT || derivedAttribute.name !== PROPERTY_DATA.SUPPLEMENTAL_DATA){//The requirement is to not display the property supplemental_data
+                    attributesArray.push(new DerivedFEAttribute(derivedAttribute, parentName));
+                }
+                let derivedDataTypeObj: DataTypeModel = this.getDataTypeByTypeName(derivedAttribute.type);
+                this.getDerivedDataTypeAttributes(derivedDataTypeObj, attributesArray, parentName + "#" + derivedAttribute.name);
+            });
+        }
+        //recurse parent (derivedFrom), in case one of parents contains properties
+        if (dataTypeObj.derivedFrom && PROPERTY_DATA.ROOT_DATA_TYPE !== dataTypeObj.derivedFrom.name) {
+            this.getDerivedDataTypeAttributes(dataTypeObj.derivedFrom, attributesArray, parentName);
+        }
+    }
+
     /**
      * Checks for custom behavior for a given data type by checking if a function exists within data-type.service with that name
      * Additional custom behavior can be added by adding a function with the given dataType name
index c6be9c3..09ace56 100644 (file)
@@ -26,11 +26,11 @@ import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, Att
     InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup} from "app/models";
 import {CommonUtils} from "app/utils";
 import {Serializable} from "../utils/serializable";
-import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model";
 import { PolicyInstance } from "app/models/graph/zones/policy-instance";
 import { GroupInstance } from "../../../models/graph/zones/group-instance";
 import { InputsGroup } from "../../../models/inputs";
 import { InterfaceModel } from "../../../models/operation";
+import { OutputBEModel } from "app/models/attributes-outputs/output-be-model";
 
 export class ComponentGenericResponse  implements Serializable<ComponentGenericResponse> {
 
@@ -45,6 +45,7 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
     public componentInstances:Array<ComponentInstance>;
     public componentInstancesInterfaces: Map<string, Array<InterfaceModel>>;
     public inputs:Array<InputBEModel>;
+    public outputs:Array<OutputBEModel>;
     public capabilities:CapabilitiesGroup;
     public requirements:RequirementsGroup;
     public properties:Array<PropertyModel>;
@@ -82,6 +83,9 @@ export class ComponentGenericResponse  implements Serializable<ComponentGenericR
         if(response.inputs) {
             this.inputs = CommonUtils.initInputs(response.inputs);
         }
+        if (response.outputs) {
+            this.outputs = CommonUtils.initOutputs(response.outputs);
+        }
         if(response.attributes) {
             this.attributes = CommonUtils.initAttributes(response.attributes);
         }
index eadb92b..b4e184a 100644 (file)
@@ -25,6 +25,8 @@ import {InputBEModel, PropertyBEModel, RelationshipModel} from "app/models";
 import { PolicyInstance } from "app/models/graph/zones/policy-instance";
 import { GroupInstance } from "../models/graph/zones/group-instance";
 import { InterfaceModel } from "../models/operation";
+import {AttributeBEModel} from "../models/attributes-outputs/attribute-be-model";
+import {OutputBEModel} from "../models/attributes-outputs/output-be-model";
 
 export class CommonUtils {
 
@@ -32,7 +34,7 @@ export class CommonUtils {
 
         let properties = new Array<PropertyModel>();
         if (propertiesObj) {
-            _.forEach(propertiesObj, (property:PropertyModel):void => {
+            propertiesObj.forEach((property:PropertyModel):void => {
                 if (uniqueId) {
                     property.readonly = property.parentUniqueId != uniqueId;
                 }
@@ -46,7 +48,7 @@ export class CommonUtils {
 
         let attributes = new Array<AttributeModel>();
         if (attributesObj) {
-            _.forEach(attributesObj, (attribute:AttributeModel):void => {
+            attributesObj.forEach((attribute:AttributeModel):void => {
                 if (uniqueId) {
                     attribute.readonly = attribute.parentUniqueId != uniqueId;
                 }
@@ -60,7 +62,7 @@ export class CommonUtils {
 
         let componentInstances = new Array<ResourceInstance>();
         if (componentInstanceObj) {
-            _.forEach(componentInstanceObj, (instance:ResourceInstance):void => {
+            componentInstanceObj.forEach((instance:ResourceInstance):void => {
                 componentInstances.push(ComponentInstanceFactory.createComponentInstance(instance));
             });
         }
@@ -72,7 +74,7 @@ export class CommonUtils {
         let modules = new Array<Module>();
 
         if (moduleArrayObj) {
-            _.forEach(moduleArrayObj, (module:Module):void => {
+            moduleArrayObj.forEach((module:Module):void => {
                 if (module.type === "org.openecomp.groups.VfModule") {
                     modules.push(new Module(module));
                 }
@@ -86,7 +88,7 @@ export class CommonUtils {
         let inputs = new Array<InputBEModel>();
 
         if(inputsObj) {
-            _.forEach(inputsObj, (input: InputBEModel):void => {
+            inputsObj.forEach((input: InputBEModel):void => {
                 inputs.push(new InputBEModel(input));
             })
         }
@@ -94,12 +96,25 @@ export class CommonUtils {
         return inputs;
     }
 
+    static initOutputs(outputsObj: Array<OutputBEModel>): Array<OutputBEModel> {
+
+        let outputs = new Array<OutputBEModel>();
+
+        if(outputsObj) {
+            outputsObj.forEach((output: OutputBEModel):void => {
+                outputs.push(new OutputBEModel(output));
+            })
+        }
+
+        return outputs;
+    }
+
     static initBeProperties(propertiesObj: Array<PropertyBEModel>): Array<PropertyBEModel> {
 
         let properties = new Array<PropertyBEModel>();
 
         if (propertiesObj) {
-            _.forEach(propertiesObj, (property: PropertyBEModel): void => {
+            propertiesObj.forEach((property: PropertyBEModel): void => {
                 properties.push(new PropertyBEModel(property));
             })
         }
@@ -107,10 +122,23 @@ export class CommonUtils {
         return properties;
     }
 
+    static initBeAttributes(attributesObj: Array<AttributeBEModel>): Array<AttributeBEModel> {
+
+        let attributes = new Array<AttributeBEModel>();
+
+        if (attributesObj) {
+            attributesObj.forEach((attribute: AttributeBEModel): void => {
+                attributes.push(new AttributeBEModel(attribute));
+            })
+        }
+
+        return attributes;
+    }
+
     static initComponentInstanceRelations = (componentInstanceRelationsObj:Array<RelationshipModel>):Array<RelationshipModel> => {
         if (componentInstanceRelationsObj) {
              let componentInstancesRelations: Array<RelationshipModel> = [];
-            _.forEach(componentInstanceRelationsObj, (instanceRelation:RelationshipModel):void => {
+            componentInstanceRelationsObj.forEach((instanceRelation:RelationshipModel):void => {
                 componentInstancesRelations.push(new RelationshipModel(instanceRelation));
             });
             return componentInstancesRelations;
@@ -121,7 +149,7 @@ export class CommonUtils {
         let policies = new Array<PolicyInstance>();
 
         if (policiesObj) {
-            _.forEach(policiesObj, (policy: PolicyInstance): void => {
+            policiesObj.forEach((policy: PolicyInstance): void => {
                 policies.push(new PolicyInstance(policy));
             })
         }
@@ -132,7 +160,7 @@ export class CommonUtils {
         let groups = new Array<GroupInstance>();
 
         if(groupsObj) {
-            _.forEach(groupsObj, (group: GroupInstance):void => {
+            groupsObj.forEach((group: GroupInstance):void => {
                 groups.push(new GroupInstance(group));
             });
         }
index c794774..230cb54 100644 (file)
@@ -30,399 +30,403 @@ export let PREVIOUS_CSAR_COMPONENT = 'previousCsarComponent'
 
 
 export class GeneralStatus {
-    static OK = 'OK';
-    static GENERAL_ERROR = 'GENERAL_ERROR';
+  static OK = 'OK';
+  static GENERAL_ERROR = 'GENERAL_ERROR';
 }
 
 export class ComponentType {
-    static SERVICE = 'SERVICE';
-    static RESOURCE = 'RESOURCE';
-    static RESOURCE_INSTANCE = 'RESOURCE_INSTANCE';
-    static SERVICE_PROXY = 'ServiceProxy'
-    static SERVICE_SUBSTITUTION = 'ServiceSubstitution'
+  static SERVICE = 'SERVICE';
+  static RESOURCE = 'RESOURCE';
+  static RESOURCE_INSTANCE = 'RESOURCE_INSTANCE';
+  static SERVICE_PROXY = 'ServiceProxy'
+  static SERVICE_SUBSTITUTION = 'ServiceSubstitution'
 }
 
 export class ServerTypeUrl {
-    static RESOURCES = 'resources/';
-    static SERVICES = 'services/';
-
-    public static toServerTypeUrl(componentType: ComponentType) : string {
-        if (componentType == ComponentType.SERVICE) {
-            return ServerTypeUrl.SERVICES.slice(0,-1);
-        } else if (componentType == ComponentType.RESOURCE) {
-            return ServerTypeUrl.RESOURCES.slice(0,-1);
-        } else {
-            return undefined;
-        }
+  static RESOURCES = 'resources/';
+  static SERVICES = 'services/';
+
+  public static toServerTypeUrl(componentType: ComponentType): string {
+    if (componentType == ComponentType.SERVICE) {
+      return ServerTypeUrl.SERVICES.slice(0, -1);
+    } else if (componentType == ComponentType.RESOURCE) {
+      return ServerTypeUrl.RESOURCES.slice(0, -1);
+    } else {
+      return undefined;
     }
+  }
 }
 
 
-
 export class ResourceType {
-    static VF = 'VF';
-    static VL = 'VL';
-    static CP = 'CP';
-    static VFC = 'VFC';
-    static VFCMT = 'VFCMT';
-    static PNF = 'PNF';
-    static CVFC = 'CVFC';
-    static CONFIGURATION = 'Configuration';
-    static CR = 'CR';
+  static VF = 'VF';
+  static VL = 'VL';
+  static CP = 'CP';
+  static VFC = 'VFC';
+  static VFCMT = 'VFCMT';
+  static PNF = 'PNF';
+  static CVFC = 'CVFC';
+  static CONFIGURATION = 'Configuration';
+  static CR = 'CR';
 }
 
 export class SdcElementType {
-    static GROUP = 'GROUP';
-    static POLICY = 'POLICY';
-    static SERVICE_PROXY = 'ServiceProxy'
+  static GROUP = 'GROUP';
+  static POLICY = 'POLICY';
+  static SERVICE_PROXY = 'ServiceProxy'
 }
+
 export class ComponentState {
-    static CERTIFICATION_IN_PROGRESS = 'CERTIFICATION_IN_PROGRESS';
-    static CERTIFIED = 'CERTIFIED';
-    static NOT_CERTIFIED_CHECKOUT = 'NOT_CERTIFIED_CHECKOUT';
-    static NOT_CERTIFIED_CHECKIN = 'NOT_CERTIFIED_CHECKIN';
-    static READY_FOR_CERTIFICATION = 'READY_FOR_CERTIFICATION';
+  static CERTIFICATION_IN_PROGRESS = 'CERTIFICATION_IN_PROGRESS';
+  static CERTIFIED = 'CERTIFIED';
+  static NOT_CERTIFIED_CHECKOUT = 'NOT_CERTIFIED_CHECKOUT';
+  static NOT_CERTIFIED_CHECKIN = 'NOT_CERTIFIED_CHECKIN';
+  static READY_FOR_CERTIFICATION = 'READY_FOR_CERTIFICATION';
 }
 
 export class DistributionStatus {
-    DISTRIBUTION_NOT_APPROVED = 'DISTRIBUTION_NOT_APPROVED';
-    DISTRIBUTION_APPROVED = 'DISTRIBUTION_APPROVED';
-    DISTRIBUTED = 'DISTRIBUTED';
-    DISTRIBUTION_REJECTED = 'DISTRIBUTION_REJECTED';
+  DISTRIBUTION_NOT_APPROVED = 'DISTRIBUTION_NOT_APPROVED';
+  DISTRIBUTION_APPROVED = 'DISTRIBUTION_APPROVED';
+  DISTRIBUTED = 'DISTRIBUTED';
+  DISTRIBUTION_REJECTED = 'DISTRIBUTION_REJECTED';
 }
 
 export class ArtifactGroupType {
-    static DEPLOYMENT = "DEPLOYMENT";
-    static INFORMATION = "INFORMATIONAL";
-    static SERVICE_API = "SERVICE_API";
-    static TOSCA = "TOSCA";
+  static DEPLOYMENT = "DEPLOYMENT";
+  static INFORMATION = "INFORMATIONAL";
+  static SERVICE_API = "SERVICE_API";
+  static TOSCA = "TOSCA";
 }
 
 export class ArtifactType {
 
-    static DEPLOYMENT = "DEPLOYMENT";
-    static INFORMATION = "INFORMATIONAL";
-    static SERVICE_API = "SERVICE_API";
-    static HEAT_ENV = "HEAT_ENV";
-    static HEAT = "HEAT";
-    static HEAT_VOL = "HEAT_VOL";
-    static HEAT_NET = "HEAT_NET";
-    static VF_LICENSE = "VF_LICENSE";
-    static PM_DICTIONARY = "PM_DICTIONARY";
-    static VENDOR_LICENSE = "VENDOR_LICENSE";
-    static THIRD_PARTY_RESERVED_TYPES = {
-        WORKFLOW: "WORKFLOW",
-        NETWORK_CALL_FLOW: "NETWORK_CALL_FLOW",
-        AAI_SERVICE_MODEL: "AAI_SERVICE_MODEL",
-        AAI_VF_MODEL: "AAI_VF_MODEL",
-        AAI_VF_MODULE_MODEL: "AAI_VF_MODULE_MODEL",
-        AAI_VF_INSTANCE_MODEL: "AAI_VF_INSTANCE_MODEL"
-    };
-    static TOSCA = {TOSCA_TEMPLATE: "TOSCA_TEMPLATE", TOSCA_CSAR: "TOSCA_CSAR"};
-    static VES_EVENTS = "VES_EVENTS";
+  static DEPLOYMENT = "DEPLOYMENT";
+  static INFORMATION = "INFORMATIONAL";
+  static SERVICE_API = "SERVICE_API";
+  static HEAT_ENV = "HEAT_ENV";
+  static HEAT = "HEAT";
+  static HEAT_VOL = "HEAT_VOL";
+  static HEAT_NET = "HEAT_NET";
+  static VF_LICENSE = "VF_LICENSE";
+  static PM_DICTIONARY = "PM_DICTIONARY";
+  static VENDOR_LICENSE = "VENDOR_LICENSE";
+  static THIRD_PARTY_RESERVED_TYPES = {
+    WORKFLOW: "WORKFLOW",
+    NETWORK_CALL_FLOW: "NETWORK_CALL_FLOW",
+    AAI_SERVICE_MODEL: "AAI_SERVICE_MODEL",
+    AAI_VF_MODEL: "AAI_VF_MODEL",
+    AAI_VF_MODULE_MODEL: "AAI_VF_MODULE_MODEL",
+    AAI_VF_INSTANCE_MODEL: "AAI_VF_INSTANCE_MODEL"
+  };
+  static TOSCA = {TOSCA_TEMPLATE: "TOSCA_TEMPLATE", TOSCA_CSAR: "TOSCA_CSAR"};
+  static VES_EVENTS = "VES_EVENTS";
 }
 
 export class SEVERITY {
-    public static DEBUG = 'DEBUG';
-    public static INFO = 'INFO';
-    public static WARNING = 'WARNING';
-    public static ERROR = 'ERROR';
+  public static DEBUG = 'DEBUG';
+  public static INFO = 'INFO';
+  public static WARNING = 'WARNING';
+  public static ERROR = 'ERROR';
 }
 
 export class PROPERTY_TYPES {
-    public static STRING = 'string';
-    public static INTEGER = 'integer';
-    public static FLOAT = 'float';
-    public static BOOLEAN = 'boolean';
-    public static JSON = 'json';
-    public static MAP = 'map';
-    public static LIST = 'list';
-    public static SCALAR = 'scalar-unit';
-    public static SCALAR_FREQUENCY = 'scalar-unit.frequency';
-    public static SCALAR_SIZE = 'scalar-unit.size';
-    public static SCALAR_TIME = 'scalar-unit.time';
+  public static STRING = 'string';
+  public static INTEGER = 'integer';
+  public static FLOAT = 'float';
+  public static BOOLEAN = 'boolean';
+  public static JSON = 'json';
+  public static MAP = 'map';
+  public static LIST = 'list';
+  public static SCALAR = 'scalar-unit';
+  public static SCALAR_FREQUENCY = 'scalar-unit.frequency';
+  public static SCALAR_SIZE = 'scalar-unit.size';
+  public static SCALAR_TIME = 'scalar-unit.time';
 }
 
 export class SOURCES {
-    public static A_AND_AI = 'A&AI';
-    public static ORDER = 'Order';
-    public static RUNTIME = 'Runtime';
+  public static A_AND_AI = 'A&AI';
+  public static ORDER = 'Order';
+  public static RUNTIME = 'Runtime';
 }
 
 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";
-    public static SUPPLEMENTAL_DATA = "supplemental_data";
-    public static SOURCES = [SOURCES.A_AND_AI, SOURCES.ORDER, SOURCES.RUNTIME];
+  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";
+  public static SUPPLEMENTAL_DATA = "supplemental_data";
+  public static SOURCES = [SOURCES.A_AND_AI, SOURCES.ORDER, SOURCES.RUNTIME];
 }
 
 export class PROPERTY_VALUE_CONSTRAINTS {
-    public static MAX_LENGTH = 2500;
-    public static JSON_MAX_LENGTH = 4096;
+  public static MAX_LENGTH = 2500;
+  public static JSON_MAX_LENGTH = 4096;
 }
 
 export class Role {
-    public static ADMIN = 'ADMIN';
-    public static DESIGNER = 'DESIGNER';
-    public static TESTER = 'TESTER';
-    public static OPS = 'OPS';
-    public static GOVERNOR = 'GOVERNOR';
+  public static ADMIN = 'ADMIN';
+  public static DESIGNER = 'DESIGNER';
+  public static TESTER = 'TESTER';
+  public static OPS = 'OPS';
+  public static GOVERNOR = 'GOVERNOR';
 }
 
-export enum FormState{
-    CREATE,
-    UPDATE,
-    IMPORT,
-    VIEW
+export enum FormState {
+  CREATE,
+  UPDATE,
+  IMPORT,
+  VIEW
 }
 
 export class instantiationType {
-    public static MACRO = 'Macro';
-    public static A_LA_CARTE = 'A-la-carte';
+  public static MACRO = 'Macro';
+  public static A_LA_CARTE = 'A-la-carte';
 }
 
 export class WorkspaceMode {
-    public static CREATE = 'create';
-    public static EDIT = 'edit';
-    public static IMPORT = 'import';
-    public static VIEW = 'view';
+  public static CREATE = 'create';
+  public static EDIT = 'edit';
+  public static IMPORT = 'import';
+  public static VIEW = 'view';
 }
 
 export class ImagesUrl {
-    public static RESOURCE_ICONS = '/assets/styles/images/resource-icons/';
-    public static SERVICE_ICONS = '/assets/styles/images/service-icons/';
-    public static SERVICE_PROXY_ICONS = '/assets/styles/images/service-proxy-icons/';
-    public static SELECTED_UCPE_INSTANCE = '/assets/styles/images/resource-icons/selectedUcpeInstance.png';
-    public static SELECTED_CP_INSTANCE = '/assets/styles/images/resource-icons/selectedCPInstance.png';
-    public static SELECTED_VL_INSTANCE = '/assets/styles/images/resource-icons/selectedVLInstance.png';
-    public static CANVAS_PLUS_ICON = '/assets/styles/images/resource-icons/canvasPlusIcon.png';
-    public static CANVAS_TAG_ICON = '/assets/styles/images/canvas-tagging-icons/indication.svg';
-    public static CANVAS_POLICY_TAGGED_ICON = '/assets/styles/images/canvas-tagging-icons/policy_added.svg';
-    public static CANVAS_GROUP_TAGGED_ICON = '/assets/styles/images/canvas-tagging-icons/group_added.svg';
-    public static MODULE_ICON = '/assets/styles/images/resource-icons/module.png';
-    public static OPEN_MODULE_ICON = '/assets/styles/images/resource-icons/openModule.png';
-    public static OPEN_MODULE_HOVER_ICON = '/assets/styles/images/resource-icons/openModuleHover.png';
-    public static CLOSE_MODULE_ICON = '/assets/styles/images/resource-icons/closeModule.png';
-    public static CLOSE_MODULE_HOVER_ICON = '/assets/styles/images/resource-icons/closeModuleHover.png';
+  public static RESOURCE_ICONS = '/assets/styles/images/resource-icons/';
+  public static SERVICE_ICONS = '/assets/styles/images/service-icons/';
+  public static SERVICE_PROXY_ICONS = '/assets/styles/images/service-proxy-icons/';
+  public static SELECTED_UCPE_INSTANCE = '/assets/styles/images/resource-icons/selectedUcpeInstance.png';
+  public static SELECTED_CP_INSTANCE = '/assets/styles/images/resource-icons/selectedCPInstance.png';
+  public static SELECTED_VL_INSTANCE = '/assets/styles/images/resource-icons/selectedVLInstance.png';
+  public static CANVAS_PLUS_ICON = '/assets/styles/images/resource-icons/canvasPlusIcon.png';
+  public static CANVAS_TAG_ICON = '/assets/styles/images/canvas-tagging-icons/indication.svg';
+  public static CANVAS_POLICY_TAGGED_ICON = '/assets/styles/images/canvas-tagging-icons/policy_added.svg';
+  public static CANVAS_GROUP_TAGGED_ICON = '/assets/styles/images/canvas-tagging-icons/group_added.svg';
+  public static MODULE_ICON = '/assets/styles/images/resource-icons/module.png';
+  public static OPEN_MODULE_ICON = '/assets/styles/images/resource-icons/openModule.png';
+  public static OPEN_MODULE_HOVER_ICON = '/assets/styles/images/resource-icons/openModuleHover.png';
+  public static CLOSE_MODULE_ICON = '/assets/styles/images/resource-icons/closeModule.png';
+  public static CLOSE_MODULE_HOVER_ICON = '/assets/styles/images/resource-icons/closeModuleHover.png';
 }
 
 
 export class CanvasHandleTypes {
-    public static ADD_EDGE = 'add-edge';
-    public static TAG_AVAILABLE = 'tag-available';
-    public static TAGGED_POLICY = 'tagged-policy';
-    public static TAGGED_GROUP = 'tagged-group';
+  public static ADD_EDGE = 'add-edge';
+  public static TAG_AVAILABLE = 'tag-available';
+  public static TAGGED_POLICY = 'tagged-policy';
+  public static TAGGED_GROUP = 'tagged-group';
 }
 
 export class ModalType {
-    static STANDARD = 'standard';
-    static ERROR = 'error';
-    static ALERT = 'alert';
+  static STANDARD = 'standard';
+  static ERROR = 'error';
+  static ALERT = 'alert';
 }
 
 export class ServerErrors {
-    static ERROR_TITLE = 'Error';
-    static DEFAULT_ERROR = 'Error getting response from server';
-    static MESSAGE_ERROR = 'Wrong error format from server';
-    static DOWNLOAD_ERROR = 'Download error';
+  static ERROR_TITLE = 'Error';
+  static DEFAULT_ERROR = 'Error getting response from server';
+  static MESSAGE_ERROR = 'Wrong error format from server';
+  static DOWNLOAD_ERROR = 'Download error';
 }
 
 export class GraphColors {
-    public static NOT_CERTIFIED_LINK = 'rgb(218,31,61)';
-    public static VL_LINK = 'rgb(216,216,216)';
-    public static ACTIVE_LINK = '#30bdf2';
-    public static BASE_LINK = 'rgb(55,55,55)';
-    public static NODE_BACKGROUND_COLOR = 'rgba(46, 162, 157, 0.24)';
-    public static NODE_SHADOW_COLOR = 'rgba(198, 230, 228, 0.7)';
-    public static NODE_OVERLAPPING_BACKGROUND_COLOR = 'rgba(179, 10, 60, 0.24)';
-    public static NODE_OVERLAPPING_SHADOW_COLOR = 'rgba(236, 194, 206, 0.7)';
-    public static NODE_UCPE_CP = '#9063cd';
-    public static NODE_UCPE = '#fbfbfb';
-    public static NODE_SELECTED_BORDER_COLOR = '#30bdf2';
-    public static SERVICE_PATH_LINK = '#70208a';
+  public static NOT_CERTIFIED_LINK = 'rgb(218,31,61)';
+  public static VL_LINK = 'rgb(216,216,216)';
+  public static ACTIVE_LINK = '#30bdf2';
+  public static BASE_LINK = 'rgb(55,55,55)';
+  public static NODE_BACKGROUND_COLOR = 'rgba(46, 162, 157, 0.24)';
+  public static NODE_SHADOW_COLOR = 'rgba(198, 230, 228, 0.7)';
+  public static NODE_OVERLAPPING_BACKGROUND_COLOR = 'rgba(179, 10, 60, 0.24)';
+  public static NODE_OVERLAPPING_SHADOW_COLOR = 'rgba(236, 194, 206, 0.7)';
+  public static NODE_UCPE_CP = '#9063cd';
+  public static NODE_UCPE = '#fbfbfb';
+  public static NODE_SELECTED_BORDER_COLOR = '#30bdf2';
+  public static SERVICE_PATH_LINK = '#70208a';
 }
+
 export class GraphTransactionLogText {
-    public static REMOVE_TEMP_LINK = "remove tempLink";
-    public static DELETE_LINK = "delete link";
-    public static ADD_LINK = "delete link";
-    public static ADD_NODE = "adding node";
+  public static REMOVE_TEMP_LINK = "remove tempLink";
+  public static DELETE_LINK = "delete link";
+  public static ADD_LINK = "delete link";
+  public static ADD_NODE = "adding node";
 }
 
 export class GraphUIObjects {
-    public static HANDLE_SIZE = 18;
-    public static NODE_OVERLAP_MIN_SIZE = 30;
-    public static DEFAULT_RESOURCE_WIDTH = 65;
-    public static SMALL_RESOURCE_WIDTH = 21;
-    public static LINK_MENU_HEIGHT = 420;
-    public static TOP_HEADER_HEIGHT = 200;
-    public static TOOLTIP_OFFSET_X = 50;
-    public static TOOLTIP_OFFSET_Y = 145;
-    public static TOOLTIP_LINK_OFFSET_X = 35;
-    public static TOOLTIP_LINK_OFFSET_Y = 75;
-    public static MENU_LINK_VL_HEIGHT_OFFSET = 250;
-    public static MENU_LINK_VL_WIDTH_OFFSET = 200;
-    public static MENU_LINK_SIMPLE_HEIGHT_OFFSET = 180;
-    public static MENU_LINK_SIMPLE_WIDTH_OFFSET = 130;
-    public static DIAGRAM_RIGHT_WIDTH_OFFSET = 248;
-    public static DIAGRAM_HEADER_OFFSET = 103;
-    public static DIAGRAM_PALETTE_WIDTH_OFFSET = 247;
-    // public static COMPOSITION_HEADER_OFFSET = 50;
-    // public static COMPOSITION_NODE_MENU_WIDTH = 230;
-    // public static COMPOSITION_NODE_MENU_HEIGHT = 200;
-    // public static COMPOSITION_RIGHT_PANEL_OFFSET = 300;
+  public static HANDLE_SIZE = 18;
+  public static NODE_OVERLAP_MIN_SIZE = 30;
+  public static DEFAULT_RESOURCE_WIDTH = 65;
+  public static SMALL_RESOURCE_WIDTH = 21;
+  public static LINK_MENU_HEIGHT = 420;
+  public static TOP_HEADER_HEIGHT = 200;
+  public static TOOLTIP_OFFSET_X = 50;
+  public static TOOLTIP_OFFSET_Y = 145;
+  public static TOOLTIP_LINK_OFFSET_X = 35;
+  public static TOOLTIP_LINK_OFFSET_Y = 75;
+  public static MENU_LINK_VL_HEIGHT_OFFSET = 250;
+  public static MENU_LINK_VL_WIDTH_OFFSET = 200;
+  public static MENU_LINK_SIMPLE_HEIGHT_OFFSET = 180;
+  public static MENU_LINK_SIMPLE_WIDTH_OFFSET = 130;
+  public static DIAGRAM_RIGHT_WIDTH_OFFSET = 248;
+  public static DIAGRAM_HEADER_OFFSET = 103;
+  public static DIAGRAM_PALETTE_WIDTH_OFFSET = 247;
+  // public static COMPOSITION_HEADER_OFFSET = 50;
+  // public static COMPOSITION_NODE_MENU_WIDTH = 230;
+  // public static COMPOSITION_NODE_MENU_HEIGHT = 200;
+  // public static COMPOSITION_RIGHT_PANEL_OFFSET = 300;
 }
 
 
 export class States {
-    public static WORKSPACE_GENERAL = 'workspace.general';
-    public static WORKSPACE_ACTIVITY_LOG = 'workspace.activity_log';
-    public static WORKSPACE_DEPLOYMENT_ARTIFACTS = 'workspace.deployment_artifacts';
-    public static WORKSPACE_PROPERTIES = 'workspace.properties';
-    public static WORKSPACE_SERVICE_INPUTS = 'workspace.service_inputs';
-    public static WORKSPACE_RESOURCE_INPUTS = 'workspace.resource_inputs';
-    public static WORKSPACE_ATTRIBUTES = 'workspace.attributes';
-    public static WORKSPACE_INFORMATION_ARTIFACTS = 'workspace.information_artifacts';
-    public static WORKSPACE_TOSCA_ARTIFACTS = 'workspace.tosca_artifacts';
-    public static WORKSPACE_COMPOSITION = 'workspace.composition';
-    public static WORKSPACE_INTERFACE_OPERATION = 'workspace.interface_operation';
-    public static WORKSPACE_NETWORK_CALL_FLOW = 'workspace.network_call_flow';
-    public static WORKSPACE_MANAGEMENT_WORKFLOW = 'workspace.management_workflow';
-    public static WORKSPACE_DEPLOYMENT = 'workspace.deployment';
-    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';
+  public static WORKSPACE_GENERAL = 'workspace.general';
+  public static WORKSPACE_ACTIVITY_LOG = 'workspace.activity_log';
+  public static WORKSPACE_DEPLOYMENT_ARTIFACTS = 'workspace.deployment_artifacts';
+  public static WORKSPACE_PROPERTIES = 'workspace.properties';
+  public static WORKSPACE_SERVICE_INPUTS = 'workspace.service_inputs';
+  public static WORKSPACE_RESOURCE_INPUTS = 'workspace.resource_inputs';
+  public static WORKSPACE_ATTRIBUTES = 'workspace.attributes';
+  public static WORKSPACE_ATTRIBUTES_OUTPUTS = 'workspace.attributes_outputs';
+  public static WORKSPACE_INFORMATION_ARTIFACTS = 'workspace.information_artifacts';
+  public static WORKSPACE_TOSCA_ARTIFACTS = 'workspace.tosca_artifacts';
+  public static WORKSPACE_COMPOSITION = 'workspace.composition';
+  public static WORKSPACE_INTERFACE_OPERATION = 'workspace.interface_operation';
+  public static WORKSPACE_NETWORK_CALL_FLOW = 'workspace.network_call_flow';
+  public static WORKSPACE_MANAGEMENT_WORKFLOW = 'workspace.management_workflow';
+  public static WORKSPACE_DEPLOYMENT = 'workspace.deployment';
+  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';
 }
 
 export class EVENTS {
-    static LEFT_PALETTE_UPDATE_EVENT = "leftPanelUpdateEvent";
-    static ON_CSAR_LOADING = "onCsarLoading";
-    static DOWNLOAD_ARTIFACT_FINISH_EVENT = "downloadArtifactFinishEvent";
-    static ON_WORKSPACE_SAVE_BUTTON_CLICK = "onWorkspaceSaveButtonClick";
-    static ON_WORKSPACE_SAVE_BUTTON_SUCCESS = "onWorkspaceSaveButtonSuccess";
-    static ON_WORKSPACE_SAVE_BUTTON_ERROR = "onWorkspaceSaveButtonError";
-    static ON_WORKSPACE_UNSAVED_CHANGES = "onWorkspaceUnsavedChanges";
-    static ON_CHECKOUT = "onCheckout";
-    static ON_LIFECYCLE_CHANGE_WITH_SAVE = "onLifecycleChangeWithSave";
-    static ON_LIFECYCLE_CHANGE = "onCheckout";
+  static LEFT_PALETTE_UPDATE_EVENT = "leftPanelUpdateEvent";
+  static ON_CSAR_LOADING = "onCsarLoading";
+  static DOWNLOAD_ARTIFACT_FINISH_EVENT = "downloadArtifactFinishEvent";
+  static ON_WORKSPACE_SAVE_BUTTON_CLICK = "onWorkspaceSaveButtonClick";
+  static ON_WORKSPACE_SAVE_BUTTON_SUCCESS = "onWorkspaceSaveButtonSuccess";
+  static ON_WORKSPACE_SAVE_BUTTON_ERROR = "onWorkspaceSaveButtonError";
+  static ON_WORKSPACE_UNSAVED_CHANGES = "onWorkspaceUnsavedChanges";
+  static ON_CHECKOUT = "onCheckout";
+  static ON_LIFECYCLE_CHANGE_WITH_SAVE = "onLifecycleChangeWithSave";
+  static ON_LIFECYCLE_CHANGE = "onCheckout";
 
-    //Loader events
-    static SHOW_LOADER_EVENT = "showLoaderEvent";
-    static HIDE_LOADER_EVENT = "hideLoaderEvent";
-    static UPDATE_PANEL = 'updatePanel';
-    static ON_DISTRIBUTION_SUCCESS = 'onDistributionSuccess';
+  //Loader events
+  static SHOW_LOADER_EVENT = "showLoaderEvent";
+  static HIDE_LOADER_EVENT = "hideLoaderEvent";
+  static UPDATE_PANEL = 'updatePanel';
+  static ON_DISTRIBUTION_SUCCESS = 'onDistributionSuccess';
 }
 
 
 export class UNIQUE_GROUP_PROPERTIES_NAME {
-    public static MIN_VF_MODULE_INSTANCES = 'min_vf_module_instances';
-    public static MAX_VF_MODULE_INSTANCES = 'max_vf_module_instances';
-    public static INITIAL_COUNT = 'initial_count';
-    public static IS_BASE = 'isBase';
-    public static VF_MODULE_TYPE = 'vf_module_type';
-    public static VF_MODULE_LABEL = 'vf_module_label';
-    public static VF_MODULE_DESCRIPTION = 'vf_module_description';
-    public static VOLUME_GROUP = 'volume_group';
+  public static MIN_VF_MODULE_INSTANCES = 'min_vf_module_instances';
+  public static MAX_VF_MODULE_INSTANCES = 'max_vf_module_instances';
+  public static INITIAL_COUNT = 'initial_count';
+  public static IS_BASE = 'isBase';
+  public static VF_MODULE_TYPE = 'vf_module_type';
+  public static VF_MODULE_LABEL = 'vf_module_label';
+  public static VF_MODULE_DESCRIPTION = 'vf_module_description';
+  public static VOLUME_GROUP = 'volume_group';
 }
 
 
 export class GRAPH_EVENTS {
-    static ON_COMPOSITION_GRAPH_DATA_LOADED = 'onCompositionGraphDataLoaded';
-    static ON_DEPLOYMENT_GRAPH_DATA_LOADED = 'onDeploymentGraphDataLoaded';
-    static ON_NODE_SELECTED = "onNodeSelected";
-    static ON_ZONE_INSTANCE_SELECTED = "onZoneInstanceSelected";
-    static ON_GRAPH_BACKGROUND_CLICKED = "onGraphBackgroundClicked";
-    static ON_PALETTE_COMPONENT_HOVER_IN = 'onPaletteComponentHoverIn';
-    static ON_PALETTE_COMPONENT_HOVER_OUT = 'onPaletteComponentHoverOut';
-    static ON_PALETTE_COMPONENT_DRAG_START = 'onPaletteComponentDragStart';
-    static ON_PALETTE_COMPONENT_DRAG_ACTION = 'onPaletteComponentDragAction';
-    static ON_PALETTE_COMPONENT_DROP = 'onPaletteComponentDrop';
-    static ON_PALETTE_COMPONENT_SHOW_POPUP_PANEL = 'onPaletteComponentShowPopupPanel';
-    static ON_PALETTE_COMPONENT_HIDE_POPUP_PANEL = 'onPaletteComponentHidePopupPanel';
-    static ON_COMPONENT_INSTANCE_NAME_CHANGED = 'onComponentInstanceNameChanged';
-    static ON_ZONE_INSTANCE_NAME_CHANGED = 'onZoneInstanceNameChanged';
-    static ON_DELETE_COMPONENT_INSTANCE = 'onDeleteComponentInstance';
-    static ON_DELETE_ZONE_INSTANCE = 'onDeleteZoneInstance';
-    static ON_DELETE_COMPONENT_INSTANCE_SUCCESS = 'onDeleteComponentInstanceSuccess';
-    static ON_DELETE_EDGE = 'onDeleteEdge';
-    static ON_INSERT_NODE_TO_UCPE = 'onInsertNodeToUCPE';
-    static ON_REMOVE_NODE_FROM_UCPE = 'onRemoveNodeFromUCPE';
-    static ON_VERSION_CHANGED = 'onVersionChanged';
-    static ON_CREATE_COMPONENT_INSTANCE = 'onCreateComponentInstance';
-    static ON_ADD_ZONE_INSTANCE_FROM_PALETTE = 'onAddZoneInstanceFromPalette';
-    static ON_CANVAS_TAG_START = 'onCanvasTagStart';
-    static ON_CANVAS_TAG_END = 'onCanvasTagEnd';
-    static ON_POLICY_INSTANCE_UPDATE = 'onPolicyInstanceUpdate';
-    static ON_GROUP_INSTANCE_UPDATE = 'onGroupInstanceUpdate';
-    static ON_SERVICE_PATH_CREATED = 'onServicePathCreated';
+  static ON_COMPOSITION_GRAPH_DATA_LOADED = 'onCompositionGraphDataLoaded';
+  static ON_DEPLOYMENT_GRAPH_DATA_LOADED = 'onDeploymentGraphDataLoaded';
+  static ON_NODE_SELECTED = "onNodeSelected";
+  static ON_ZONE_INSTANCE_SELECTED = "onZoneInstanceSelected";
+  static ON_GRAPH_BACKGROUND_CLICKED = "onGraphBackgroundClicked";
+  static ON_PALETTE_COMPONENT_HOVER_IN = 'onPaletteComponentHoverIn';
+  static ON_PALETTE_COMPONENT_HOVER_OUT = 'onPaletteComponentHoverOut';
+  static ON_PALETTE_COMPONENT_DRAG_START = 'onPaletteComponentDragStart';
+  static ON_PALETTE_COMPONENT_DRAG_ACTION = 'onPaletteComponentDragAction';
+  static ON_PALETTE_COMPONENT_DROP = 'onPaletteComponentDrop';
+  static ON_PALETTE_COMPONENT_SHOW_POPUP_PANEL = 'onPaletteComponentShowPopupPanel';
+  static ON_PALETTE_COMPONENT_HIDE_POPUP_PANEL = 'onPaletteComponentHidePopupPanel';
+  static ON_COMPONENT_INSTANCE_NAME_CHANGED = 'onComponentInstanceNameChanged';
+  static ON_ZONE_INSTANCE_NAME_CHANGED = 'onZoneInstanceNameChanged';
+  static ON_DELETE_COMPONENT_INSTANCE = 'onDeleteComponentInstance';
+  static ON_DELETE_ZONE_INSTANCE = 'onDeleteZoneInstance';
+  static ON_DELETE_COMPONENT_INSTANCE_SUCCESS = 'onDeleteComponentInstanceSuccess';
+  static ON_DELETE_EDGE = 'onDeleteEdge';
+  static ON_INSERT_NODE_TO_UCPE = 'onInsertNodeToUCPE';
+  static ON_REMOVE_NODE_FROM_UCPE = 'onRemoveNodeFromUCPE';
+  static ON_VERSION_CHANGED = 'onVersionChanged';
+  static ON_CREATE_COMPONENT_INSTANCE = 'onCreateComponentInstance';
+  static ON_ADD_ZONE_INSTANCE_FROM_PALETTE = 'onAddZoneInstanceFromPalette';
+  static ON_CANVAS_TAG_START = 'onCanvasTagStart';
+  static ON_CANVAS_TAG_END = 'onCanvasTagEnd';
+  static ON_POLICY_INSTANCE_UPDATE = 'onPolicyInstanceUpdate';
+  static ON_GROUP_INSTANCE_UPDATE = 'onGroupInstanceUpdate';
+  static ON_SERVICE_PATH_CREATED = 'onServicePathCreated';
 }
 
 export class DEPENDENCY_EVENTS {
-    static ON_DEPENDENCY_CHANGE = 'onDependencyStatusChange';
+  static ON_DEPENDENCY_CHANGE = 'onDependencyStatusChange';
 }
 
 export class SUBSTITUTION_FILTER_EVENTS {
-    static ON_SUBSTITUTION_FILTER_CHANGE = 'onSubstitutionFilterChange';
+  static ON_SUBSTITUTION_FILTER_CHANGE = 'onSubstitutionFilterChange';
 }
 
 
 export class COMPONENT_FIELDS {
-    static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties";
-    static COMPONENT_INSTANCES_INPUTS = "componentInstancesInputs";
-    static COMPONENT_INSTANCES_ATTRIBUTES = "componentInstancesAttributes";
-    static COMPONENT_ATTRIBUTES = "attributes";
-    static COMPONENT_INSTANCES = "componentInstances";
-    static COMPONENT_INSTANCES_RELATION = "componentInstancesRelations";
-    static COMPONENT_INPUTS = "inputs";
-    static COMPONENT_METADATA = "metadata";
-    static COMPONENT_DEPLOYMENT_ARTIFACTS = "deploymentArtifacts";
-    static COMPONENT_INFORMATIONAL_ARTIFACTS = "artifacts";
-    static COMPONENT_PROPERTIES = "properties";
-    static COMPONENT_CAPABILITIES = "capabilities";
-    static COMPONENT_CAPABILITIES_PROPERTIES = "instanceCapabiltyProperties";
-    static COMPONENT_REQUIREMENTS = "requirements";
-    static COMPONENT_TOSCA_ARTIFACTS = "toscaArtifacts";
-    static COMPONENT_POLICIES = "policies";
-    static COMPONENT_GROUPS = "groups";
-    static COMPONENT_INTERFACE_OPERATIONS = "interfaces";
-    static COMPONENT_INSTANCES_INTERFACES = "componentInstancesInterfaces";
-    static COMPONENT_NON_EXCLUDED_GROUPS = "nonExcludedGroups";
-    static COMPONENT_NON_EXCLUDED_POLICIES = "nonExcludedPolicies";
-    static FORWARDING_PATHS = "forwardingPaths";
-    static SERVICE_API_ARTIFACT = "serviceApiArtifacts";
+  static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties";
+  static COMPONENT_INSTANCES_INPUTS = "componentInstancesInputs";
+  static COMPONENT_INSTANCES_ATTRIBUTES = "componentInstancesAttributes";
+  static COMPONENT_INSTANCES_OUTPUTS = "componentInstancesOutputs";
+  static COMPONENT_ATTRIBUTES = "attributes";
+  static COMPONENT_INSTANCES = "componentInstances";
+  static COMPONENT_INSTANCES_RELATION = "componentInstancesRelations";
+  static COMPONENT_INPUTS = "inputs";
+  static COMPONENT_OUTPUTS = "outputs";
+  static COMPONENT_METADATA = "metadata";
+  static COMPONENT_DEPLOYMENT_ARTIFACTS = "deploymentArtifacts";
+  static COMPONENT_INFORMATIONAL_ARTIFACTS = "artifacts";
+  static COMPONENT_PROPERTIES = "properties";
+  static COMPONENT_CAPABILITIES = "capabilities";
+  static COMPONENT_CAPABILITIES_PROPERTIES = "instanceCapabiltyProperties";
+  static COMPONENT_REQUIREMENTS = "requirements";
+  static COMPONENT_TOSCA_ARTIFACTS = "toscaArtifacts";
+  static COMPONENT_POLICIES = "policies";
+  static COMPONENT_GROUPS = "groups";
+  static COMPONENT_INTERFACE_OPERATIONS = "interfaces";
+  static COMPONENT_INSTANCES_INTERFACES = "componentInstancesInterfaces";
+  static COMPONENT_NON_EXCLUDED_GROUPS = "nonExcludedGroups";
+  static COMPONENT_NON_EXCLUDED_POLICIES = "nonExcludedPolicies";
+  static FORWARDING_PATHS = "forwardingPaths";
+  static SERVICE_API_ARTIFACT = "serviceApiArtifacts";
 }
 
 export class SERVICE_FIELDS {
-    static FORWARDING_PATHS = "forwardingPaths";
-    static NODE_FILTER = "nodeFilter";
-    static SUBSTITUTION_FILTER = "substitutionFilter";
+  static FORWARDING_PATHS = "forwardingPaths";
+  static NODE_FILTER = "nodeFilter";
+  static SUBSTITUTION_FILTER = "substitutionFilter";
 }
 
 export class API_QUERY_PARAMS {
-    static INCLUDE = "include";
+  static INCLUDE = "include";
 }
 
 export enum TargetOrMemberType {
-    COMPONENT_INSTANCES,
-    GROUPS
+  COMPONENT_INSTANCES,
+  GROUPS
 }
 
 export class CANVAS_TAG_MODE {
-    static POLICY_TAGGING = "policy-tagging";
-    static POLICY_TAGGING_HOVER = "policy-tagging-hover";
-    static GROUP_TAGGING = "group-tagging";
-    static GROUP_TAGGING_HOVER= "group-tagging-hover";
+  static POLICY_TAGGING = "policy-tagging";
+  static POLICY_TAGGING_HOVER = "policy-tagging-hover";
+  static GROUP_TAGGING = "group-tagging";
+  static GROUP_TAGGING_HOVER = "group-tagging-hover";
 }
 
 export class DROPDOWN_OPTION_TYPE {
-    static SIMPLE = "Simple";
-    static HEADER = "Header";
-    static DISABLE = "Disable";
-    static HORIZONTAL_LINE = "HorizontalLine";
+  static SIMPLE = "Simple";
+  static HEADER = "Header";
+  static DISABLE = "Disable";
+  static HORIZONTAL_LINE = "HorizontalLine";
 }
@@ -7,11 +7,11 @@ import { ModalComponent } from 'onap-ui-angular/dist/modals/modal.component';
 import { AttributeModel } from '../../../../models';
 import { Resource } from '../../../../models';
 import { ModalsHandler } from '../../../../utils';
-import { TopologyTemplateService } from '../../../services/component-services/topology-template.service';
-import { TranslateService } from '../../../shared/translator/translate.service';
-import { WorkspaceState } from '../../../store/states/workspace.state';
-import { WorkspaceService } from '../workspace.service';
-import { AttributeModalComponent } from './attribute-modal.component';
+import { TopologyTemplateService } from '../../../../ng2/services/component-services/topology-template.service';
+import { TranslateService } from '../../../../ng2/shared/translator/translate.service';
+import { WorkspaceState } from '../../../../ng2/store/states/workspace.state';
+import { WorkspaceService } from '../../../../ng2/pages/workspace/workspace.service';
+import { AttributeModalComponent } from '../../../../ng2/pages/workspace/attributes/attribute-modal.component';
 
 @Component({
     selector: 'attributes',