Fixing the issue which prevents from clicking the [+] button on templates 33/99933/2
authorIttay Stern <ittay.stern@att.com>
Thu, 26 Dec 2019 13:21:17 +0000 (15:21 +0200)
committerIttay Stern <ittay.stern@att.com>
Mon, 30 Dec 2019 04:18:18 +0000 (06:18 +0200)
  i) Use modelUniqueName instead of obsolete originalName
 ii) Add RECREATE as "editable" case
iii) Lift vfModules limitation in model to 3

Issue-ID: VID-724

Change-Id: I9e55142ee379f4eead3c8634f5b816d2d1db1f8e
Signed-off-by: Ittay Stern <ittay.stern@att.com>
vid-automation/src/test/resources/asyncInstantiation/templates__instance_from_template__set_without_modify1.json
vid-webpack-master/cypress/integration/iFrames/instantiation-templates.e2e.ts
vid-webpack-master/cypress/support/jsonBuilders/mocks/jsons/instantiationTemplates/templates__service_model.json
vid-webpack-master/src/app/drawingBoard/service-planning/available-models-tree/available-models-tree.component.ts
vid-webpack-master/src/app/drawingBoard/service-planning/available-models-tree/available-models-tree.service.ts
vid-webpack-master/src/app/drawingBoard/service-planning/objectsToTree/models/vfModule/vfModule.model.info.spec.ts
vid-webpack-master/src/app/drawingBoard/service-planning/objectsToTree/models/vfModule/vfModule.model.info.ts
vid-webpack-master/src/app/drawingBoard/service-planning/objectsToTree/shared.tree.service.ts

index 68e7e75..e8b9d3c 100644 (file)
@@ -63,7 +63,9 @@ describe('Drawing Board: Instantiation Templates', function () {
         .getElementByDataTestsId("sdncPreLoad").should('have.value', 'on')
         .getElementByDataTestsId("cancelButton").click();
 
-        assertThatBodyFromDeployRequestEqualsToTemplateFromBackEnd();
+        assertThatBodyFromDeployRequestEqualsToTemplateFromBackEnd([
+          {path: [...vnfPath, "vnfStoreKey"], value: "vProbe_NC_VNF 0"}, // side-effect
+        ]);
         });
 
       it(`Given a stored template - when "edit" service is opened - then template’s details are visible as expected`,  function ()  {
@@ -81,9 +83,7 @@ describe('Drawing Board: Instantiation Templates', function () {
 
       });
 
-      it.skip(`Given a stored template - add one VfModule, edit it's details, and deploy - deploy is added with the vfModule details`, () => {
-        // Test is disabled, as an issue currently prevents from clicking the [+] button.
-
+      it(`Given a stored template - add one VfModule, edit its details, and deploy - deploy is added with the vfModule details`, () => {
         loadDrawingBoardWithRecreateMode();
 
         let newVfModuleName = "new.vfmodule.name";
@@ -106,10 +106,6 @@ describe('Drawing Board: Instantiation Templates', function () {
 
         // Then...
         cy.getReduxState().then((state) => {
-          let vnfPath = [
-            "vnfs", "vProbe_NC_VNF 0"
-          ];
-
           let vfModules_1Path = [
             ...vnfPath, "vfModules", module1CustomizationId,
           ];
@@ -131,6 +127,7 @@ describe('Drawing Board: Instantiation Templates', function () {
           );
 
           assertThatBodyFromDeployRequestEqualsToTemplateFromBackEnd([
+            {path: [...vnfPath, "vnfStoreKey"], value: "vProbe_NC_VNF 0"},   // side-effect
             {path: ["existingNames", newVfModuleName], value: ""},
             {path: ["existingNames", `${newVfModuleName}_vol`], value: ""},
             {path: latestVfModule_1Path, value: latestVfModule_1ExpectedValue},
@@ -174,9 +171,6 @@ describe('Drawing Board: Instantiation Templates', function () {
           cy.getElementByDataTestsId('form-set').click();
 
           // Then...
-          let vnfPath = [
-            "vnfs", "vProbe_NC_VNF 0"
-          ];
           let vfModule_0Path = [
             ...vnfPath, "vfModules",
             "vprobe_nc_vnf0..VprobeNcVnf..FE_base_module..module-0",
@@ -203,6 +197,10 @@ describe('Drawing Board: Instantiation Templates', function () {
 
 const serviceModelId = '6cfeeb18-c2b0-49df-987a-da47493c8e38';
 
+const vnfPath = [
+  "vnfs", "vProbe_NC_VNF 0"
+];
+
 function loadDrawingBoardWithRecreateMode() {
   const templateUuid = "46390edd-7100-46b2-9f18-419bd24fb60b";
 
index 96c29a0..8234a76 100644 (file)
@@ -82,7 +82,7 @@
           "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1",
           "properties": {
             "minCountInstances": 0,
-            "maxCountInstances": null,
+            "maxCountInstances": 3,
             "initialCount": 0,
             "vfModuleLabel": "FE_Add_On_Module_vlbagent_eph",
             "baseModule": false
           "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1",
           "properties": {
             "minCountInstances": 0,
-            "maxCountInstances": null,
+            "maxCountInstances": 3,
             "initialCount": 0,
             "vfModuleLabel": "FE_Add_On_Module_vlbagent_eph",
             "baseModule": false
       "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1",
       "properties": {
         "minCountInstances": 0,
-        "maxCountInstances": null,
+        "maxCountInstances": 3,
         "initialCount": 0,
         "vfModuleLabel": "FE_Add_On_Module_vlbagent_eph",
         "baseModule": false
       "modelCustomizationName": "VprobeNcVnf..FE_Add_On_Module_vlbagent_eph..module-1",
       "properties": {
         "minCountInstances": 0,
-        "maxCountInstances": null,
+        "maxCountInstances": 3,
         "initialCount": 0,
         "vfModuleLabel": "FE_Add_On_Module_vlbagent_eph",
         "baseModule": false
index e277cb2..13147b7 100644 (file)
@@ -166,11 +166,11 @@ export class AvailableModelsTreeComponent {
         if (this._sharedTreeService.selectedVNF) {
           this.store.dispatch(createVFModuleInstance(vfModule, node.data.name, this.serviceModelId, null, this._sharedTreeService.selectedVNF));
           DrawingBoardTreeComponent.triggerreCalculateIsDirty.next(this.serviceModelId);
-        } else if (this._availableModelsTreeService.getOptionalVNFs(this.serviceModelId, node.parent.data.name).length === 1) {
+        } else if (this._availableModelsTreeService.getOptionalVNFs(this.serviceModelId, node.parent.data.modelUniqueId).length === 1) {
           let existVnf = this._store.getState().service.serviceInstance[this.serviceModelId].vnfs;
           if(!_.isNil(existVnf)){
             for(let vnfKey in existVnf){
-              const modelUniqueId =  existVnf[vnfKey]['modelInfo'].modelCustomizationId || existVnf[vnfKey]['modelInfo'].modelInvariantId;
+              const modelUniqueId = this._sharedTreeService.modelUniqueId(existVnf[vnfKey]);
               if(modelUniqueId === node.parent.data.id){
                 this.store.dispatch(createVFModuleInstance(vfModule, node.data.name, this.serviceModelId, null, vnfKey));
                 DrawingBoardTreeComponent.triggerreCalculateIsDirty.next(this.serviceModelId);
index 39a3c50..c9a89cf 100644 (file)
@@ -4,11 +4,11 @@ import {NgRedux} from "@angular-redux/store";
 import {AppState} from "../../../shared/store/reducers";
 import {MessageBoxData} from "../../../shared/components/messageBox/messageBox.data";
 import {MessageBoxService} from "../../../shared/components/messageBox/messageBox.service";
-import * as _ from "lodash";
-import { SdcUiCommon} from "onap-ui-angular";
+import {SdcUiCommon} from "onap-ui-angular";
 import {SharedTreeService} from "../objectsToTree/shared.tree.service";
 import {VrfModel} from "../../../shared/models/vrfModel";
 import {clearAllGenericModalhelper} from "../../../shared/storeUtil/utils/global/global.actions";
+import * as _ from "lodash";
 
 export class AvailableNodeIcons {
    addIcon: boolean;
@@ -25,7 +25,7 @@ export class AvailableNodeIcons {
 export class AvailableModelsTreeService {
   constructor(private _defaultDataGeneratorService: DefaultDataGeneratorService,
               private store: NgRedux<AppState>,
-              public _shareTreeService : SharedTreeService) {
+              public _sharedTreeService : SharedTreeService) {
   }
 
 
@@ -45,19 +45,17 @@ export class AvailableModelsTreeService {
     return false;
   }
 
-  getOptionalVNFs(serviceUUID: string, vnfOriginalModelName : string) : any[] {
+  getOptionalVNFs(serviceUUID: string, vnfModelUniqueId: string) : any[] {
     let result = [];
     if(!_.isNil(this.store.getState().service.serviceInstance) && !_.isNil(this.store.getState().service.serviceInstance[serviceUUID])){
       const serviceVNFsInstances = this.store.getState().service.serviceInstance[serviceUUID].vnfs;
       for(let vnfKey in serviceVNFsInstances){
-        if(serviceVNFsInstances[vnfKey].originalName === vnfOriginalModelName){
+        if (this._sharedTreeService.modelUniqueId(serviceVNFsInstances[vnfKey]) === vnfModelUniqueId) {
           serviceVNFsInstances[vnfKey].vnfStoreKey = vnfKey;
           result.push(serviceVNFsInstances[vnfKey]);
         }
       }
     }
-
-
     return result;
   }
 
index 8bdab11..2162318 100644 (file)
@@ -195,21 +195,21 @@ describe('VFModule Model Info', () => {
   });
 
   test('showNodeIcons should return true, false if reachLimit of max', ()=>{
-    let serviceId : string = 'servicedId';
     let node = {
       data : {
         id : 'vfModuleId',
-        name : 'vfModuleName'
+        modelUniqueId : 'vfModuleCustomizationId'
       },
       parent : {
         data : {
           id : 'vnfId',
-          name : 'vnfName'
+          modelUniqueId : 'vnfCustomizationId'
         }
       }
     };
     jest.spyOn(_sharedTreeService, 'getSelectedVNF').mockReturnValue('vnfName');
-    jest.spyOn(vfModuleModel, 'getOptionalVNFs').mockReturnValue(['vnfName']);
+    jest.spyOn(_sharedTreeService, 'modelUniqueId').mockReturnValue('vnfCustomizationId');
+    jest.spyOn(vfModuleModel, 'getOptionalVNFs').mockReturnValue([{vnfStoreKey: 'vnfName'}]);
     jest.spyOn(MockNgRedux.getInstance(), 'getState').mockReturnValue({
       global : {},
       service : {
@@ -217,6 +217,9 @@ describe('VFModule Model Info', () => {
           'servicedId' : {
             'vnfs' : {
               'vnfName' : {
+                modelInfo: {
+                  modelCustomizationId: 'vnfCustomizationId'
+                },
                 'properties' : {
                   'max_instances' : 1
                 }
@@ -224,8 +227,11 @@ describe('VFModule Model Info', () => {
             },
             'vfModules' : {
               'vfModuleName' : {
+                modelInfo: {
+                  modelCustomizationId: 'vfModuleCustomizationId'
+                },
                 'properties' : {
-                  maxCountInstances : 1
+                  maxCountInstances : 2
                 }
               }
             }
@@ -238,7 +244,6 @@ describe('VFModule Model Info', () => {
             },
             'vnfs' : {
               'vnfName' :{
-                  'originalName' : 'vnfName',
                   'vfModules' : {
                     'vfModuleName' : {
 
@@ -256,68 +261,6 @@ describe('VFModule Model Info', () => {
   });
 
 
-  test('showNodeIcons should return false, true if reachLimit of max', ()=>{
-    let serviceId : string = 'servicedId';
-    let node = {
-      data : {
-        id : 'vfModuleId',
-        name : 'vfModuleName'
-      },
-      parent : {
-        data : {
-          id : 'vnfId',
-          name : 'vnfName'
-        }
-      }
-    };
-    jest.spyOn(_sharedTreeService, 'getSelectedVNF').mockReturnValue('vnfName');
-    jest.spyOn(vfModuleModel, 'getOptionalVNFs').mockReturnValue(['vnfName']);
-    jest.spyOn(MockNgRedux.getInstance(), 'getState').mockReturnValue({
-      global : {},
-      service : {
-        serviceHierarchy : {
-          'servicedId' : {
-            'vnfs' : {
-              'vnfName' : {
-                'properties' : {
-                  'max_instances' : 1
-                }
-              }
-            },
-            'vfModules' : {
-              'vfModuleName' : {
-                'properties' : {
-                  maxCountInstances : 2
-                }
-              }
-            }
-          }
-        },
-        serviceInstance : {
-          'servicedId' : {
-            'existingVNFCounterMap' : {
-              'vnfId' : 1
-            },
-            'vnfs' : {
-              'vnfName' :{
-                'originalName' : 'vnfName',
-                'vfModules' : {
-                  'vfModuleName' : {
-
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    });
-
-    let result = vfModuleModel.showNodeIcons(<any>node, 'servicedId');
-    expect(result).toEqual(new AvailableNodeIcons(true , false));
-  });
-
-
   test('getOptionalVNFs should instance if exist', ()=>{
     let serviceId : string = 'servicedId';
     jest.spyOn(MockNgRedux.getInstance(), 'getState').mockReturnValue({
@@ -379,6 +322,8 @@ describe('VFModule Model Info', () => {
 
   test('getNodeCount should return number of nodes', ()=>{
     let serviceId : string = 'servicedId';
+    let vfModuleModelUniqueId = 'vfModuleCustomizationId';
+    jest.spyOn(_sharedTreeService, 'modelUniqueId').mockReturnValue(vfModuleModelUniqueId);
     jest.spyOn(MockNgRedux.getInstance(), 'getState').mockReturnValue({
       global : {},
       service : {
@@ -386,6 +331,9 @@ describe('VFModule Model Info', () => {
           'servicedId' : {
             'vnfs' : {
               'vnfName' : {
+                modelInfo: {
+                  modelCustomizationId: 'vnfCustomizationId'
+                },
                 'properties' : {
                   'max_instances' : 1
                 }
@@ -393,6 +341,9 @@ describe('VFModule Model Info', () => {
             },
             'vfModules' : {
               'vfModuleName' : {
+                modelInfo: {
+                  modelCustomizationId: vfModuleModelUniqueId
+                },
                 'properties' : {
                   maxCountInstances : 2
                 }
@@ -414,13 +365,13 @@ describe('VFModule Model Info', () => {
                       'vnfModuleName_111': {
                         'action': 'Create',
                         'modelInfo' : {
-                          modelVersionId : 'vfModuleId'
+                          modelCustomizationId : vfModuleModelUniqueId
                         }
                       },
                       'vnfModuleName_111_1': {
                         'action': 'Create',
                         'modelInfo' : {
-                          modelVersionId : 'vfModuleId'
+                          modelCustomizationId : vfModuleModelUniqueId
                         }
                       }
                   }
@@ -434,13 +385,13 @@ describe('VFModule Model Info', () => {
                     'vnfModuleName_111': {
                       'action': 'Create',
                       'modelInfo' : {
-                        modelVersionId : 'vfModuleId'
+                        modelCustomizationId : vfModuleModelUniqueId
                       }
                     },
                     'vnfModuleName_111_1': {
                       'action': 'Create',
                       'modelInfo' : {
-                        modelVersionId : 'vfModuleId'
+                        modelCustomizationId : vfModuleModelUniqueId
                       }
                     }
                   }
@@ -455,13 +406,13 @@ describe('VFModule Model Info', () => {
     let node = {
       data : {
         id : 'vfModuleId',
-        name : 'vfModuleName',
+        modelUniqueId: vfModuleModelUniqueId,
         'action': 'Create',
       },
       parent : {
         data : {
           id : 'vnfId',
-          name : 'vnfName',
+          modelUniqueId: 'vnfCustomizationId',
           'action': 'Create',
         }
       }
@@ -473,6 +424,8 @@ describe('VFModule Model Info', () => {
 
   test('getNodeCount should return number of nodes : there is selectedVNF', ()=>{
     let serviceId : string = 'servicedId';
+    let vfModuleModelUniqueId = 'vfModuleCustomizationId';
+    jest.spyOn(_sharedTreeService, 'modelUniqueId').mockReturnValue(vfModuleModelUniqueId);
     jest.spyOn(MockNgRedux.getInstance(), 'getState').mockReturnValue({
       global : {},
       service : {
@@ -508,13 +461,13 @@ describe('VFModule Model Info', () => {
                     'vnfModuleName_111': {
                       'action': 'Create',
                       'modelInfo' : {
-                        modelVersionId : 'vfModuleId'
+                        modelCustomizationId : vfModuleModelUniqueId
                       }
                     },
                     'vnfModuleName_111_1': {
                       'action': 'Create',
                       'modelInfo' : {
-                        modelVersionId : 'vfModuleId'
+                        modelCustomizationId : vfModuleModelUniqueId
                       }
                     }
                   }
@@ -528,7 +481,7 @@ describe('VFModule Model Info', () => {
                     'vnfModuleName_111': {
                       'action': 'Create',
                       'modelInfo' : {
-                        modelVersionId : 'vfModuleId'
+                        modelCustomizationId: vfModuleModelUniqueId
                       }
                     }
                   }
@@ -544,13 +497,13 @@ describe('VFModule Model Info', () => {
     let node = {
       data : {
         id : 'vfModuleId',
-        name : 'vfModuleName',
+        modelUniqueId: vfModuleModelUniqueId,
         'action': 'Create',
       },
       parent : {
         data : {
           id : 'vnfId',
-          name : 'vnfName_1',
+          modelUniqueId: 'vnfCustomizationId',
           'action': 'Create',
         }
       }
index 73f3527..36b5ed0 100644 (file)
@@ -8,7 +8,10 @@ import {VfModuleInstance} from "../../../../../shared/models/vfModuleInstance";
 import {VfModule} from "../../../../../shared/models/vfModule";
 import {NgRedux} from "@angular-redux/store";
 import {ITreeNode} from "angular-tree-component/dist/defs/api";
-import {GenericFormPopupComponent, PopupType} from "../../../../../shared/components/genericFormPopup/generic-form-popup.component";
+import {
+  GenericFormPopupComponent,
+  PopupType
+} from "../../../../../shared/components/genericFormPopup/generic-form-popup.component";
 import {DialogService} from "ng2-bootstrap-modal";
 import {VfModulePopupService} from "../../../../../shared/components/genericFormPopup/genericFormServices/vfModule/vfModule.popup.service";
 import {AppState} from "../../../../../shared/store/reducers";
@@ -16,7 +19,15 @@ import {MessageBoxData} from "../../../../../shared/components/messageBox/messag
 import {MessageBoxService} from "../../../../../shared/components/messageBox/messageBox.service";
 import {AvailableNodeIcons} from "../../../available-models-tree/available-models-tree.service";
 import {IframeService} from "../../../../../shared/utils/iframe.service";
-import {deleteActionVfModuleInstance, deleteVFModuleField, removeVfModuleInstance, undoDeleteVfModuleInstance, undoUgradeVFModule, updateVFModulePosition, upgradeVFModule} from "../../../../../shared/storeUtil/utils/vfModule/vfModule.actions";
+import {
+  deleteActionVfModuleInstance,
+  deleteVFModuleField,
+  removeVfModuleInstance,
+  undoDeleteVfModuleInstance,
+  undoUgradeVFModule,
+  updateVFModulePosition,
+  upgradeVFModule
+} from "../../../../../shared/storeUtil/utils/vfModule/vfModule.actions";
 import {ComponentInfoService} from "../../../component-info/component-info.service";
 import {ComponentInfoType} from "../../../component-info/component-info-model";
 import {ModelInformationItem} from "../../../../../shared/components/model-information/model-information.component";
@@ -166,7 +177,7 @@ export class VFModuleModelInfo implements ILevelNodeInfo {
 
   getDefaultVNF(node: ITreeNode, serviceModelId: string): string {
     let keys = _.keys(_.pickBy(this._store.getState().service.serviceInstance[serviceModelId].vnfs, vnf => {
-      return (vnf.originalName == node.data.name);
+      return (this._sharedTreeService.modelUniqueId(vnf) === node.data.modelUniqueId);
     }));
     return keys.length === 1 ? this._store.getState().service.serviceInstance[serviceModelId].vnfs[keys[0]].vnfStoreKey : null;
   }
@@ -198,7 +209,7 @@ export class VFModuleModelInfo implements ILevelNodeInfo {
     let count = 0;
     for (let vfModuleKey in vnf['vfModules']) {
       for (let vfModule in vnf['vfModules'][vfModuleKey]) {
-        if (vnf['vfModules'][vfModuleKey][vfModule]['modelInfo'].modelCustomizationId === node.data.modelUniqueId) {
+        if (this._sharedTreeService.modelUniqueId(vnf['vfModules'][vfModuleKey][vfModule]) === node.data.modelUniqueId) {
           const vfModuleObj = vnf['vfModules'][vfModuleKey][vfModule];
           if (!(!_.isNil(vfModuleObj) && !_.isNil(vfModuleObj.action) && vfModuleObj.action.split('_').pop() === 'Delete')) count++;
         }
@@ -228,7 +239,7 @@ export class VFModuleModelInfo implements ILevelNodeInfo {
     if (selectedVNF) {
       return this.showVFModuleOnSelectedVNF(node, selectedVNF, serviceModelId);
     } else {
-      const optionalSelected = this.getOptionalVNFs(serviceModelId, node.parent.data.name);
+      const optionalSelected = this.getOptionalVNFs(serviceModelId, node.parent.data.modelUniqueId);
       if (optionalSelected.length === 1) {
         return this.showVFModuleOnSelectedVNF(node, optionalSelected[0].vnfStoreKey, serviceModelId);
       } else {
@@ -239,7 +250,8 @@ export class VFModuleModelInfo implements ILevelNodeInfo {
 
 
   showVFModuleOnSelectedVNF(node: ITreeNode, selectedVNF: string, serviceModelId: string): AvailableNodeIcons {
-    if (!_.isNil(this._store.getState().service.serviceInstance[serviceModelId].vnfs[selectedVNF]) && node.parent.data.name === this._store.getState().service.serviceInstance[serviceModelId].vnfs[selectedVNF].originalName) {
+    if (!_.isNil(this._store.getState().service.serviceInstance[serviceModelId].vnfs[selectedVNF])
+        && node.parent.data.modelUniqueId === this._sharedTreeService.modelUniqueId(this._store.getState().service.serviceInstance[serviceModelId].vnfs[selectedVNF])) {
       const existingVFModules = this.getCountVFModuleOfSelectedVNF(node, selectedVNF, serviceModelId);
       const reachedLimit = this.isVFModuleReachedLimit(node, this._store.getState().service.serviceHierarchy, serviceModelId, existingVFModules);
       const showAddIcon = this._sharedTreeService.shouldShowAddIcon() && !reachedLimit;
@@ -249,12 +261,12 @@ export class VFModuleModelInfo implements ILevelNodeInfo {
 
   }
 
-  getOptionalVNFs(serviceUUID: string, vnfOriginalModelName: string): any[] {
+  getOptionalVNFs(serviceUUID: string, vnfModelUniqueId: string): any[] {
     let result = [];
     if (!_.isNil(this._store.getState().service.serviceInstance) && !_.isNil(this._store.getState().service.serviceInstance[serviceUUID])) {
       const serviceVNFsInstances = this._store.getState().service.serviceInstance[serviceUUID].vnfs;
       for (let vnfKey in serviceVNFsInstances) {
-        if (serviceVNFsInstances[vnfKey].originalName === vnfOriginalModelName) {
+        if (this._sharedTreeService.modelUniqueId(serviceVNFsInstances[vnfKey]) === vnfModelUniqueId) {
           serviceVNFsInstances[vnfKey].vnfStoreKey = vnfKey;
           result.push(serviceVNFsInstances[vnfKey]);
         }
index 7f5b6e3..1115d1b 100644 (file)
@@ -15,6 +15,7 @@ import {VNFMethods} from "../../../shared/storeUtil/utils/vnf/vnf.actions";
 import {FeatureFlagsService, Features} from "../../../shared/services/featureFlag/feature-flags.service";
 import {Utils} from "../../../shared/utils/utils";
 import {Constants} from "../../../shared/utils/constants";
+import {NodeInstance} from "../../../shared/models/nodeInstance";
 
 @Injectable()
 export class SharedTreeService {
@@ -43,6 +44,16 @@ export class SharedTreeService {
     }
   }
 
+  /**
+   * Determines a consistent unique ID for a given right-tree
+   * node instance.
+   */
+  modelUniqueId = (nodeInstance: NodeInstance): string => {
+    return _.isNil(nodeInstance.modelInfo)
+      ? null
+      : (nodeInstance.modelInfo.modelCustomizationId || nodeInstance.modelInfo.modelInvariantId);
+  };
+
   hasMissingData(instance, dynamicInputs: any, isEcompGeneratedNaming: boolean, requiredFields: string[]): boolean {
     if (!isEcompGeneratedNaming && _.isEmpty(instance.instanceName)) {
       return true;
@@ -239,7 +250,7 @@ export class SharedTreeService {
    **********************************************/
   shouldShowAddIcon(): boolean{
     const mode = this._store.getState().global.drawingBoardStatus;
-    return mode === DrawingBoardModes.EDIT || mode=== DrawingBoardModes.CREATE;
+    return mode === DrawingBoardModes.EDIT || mode=== DrawingBoardModes.CREATE || mode=== DrawingBoardModes.RECREATE;
   }