Implement Attributes/Outputs FE
[sdc.git] / catalog-ui / src / app / ng2 / pages / attributes-outputs / attributes-outputs.page.component.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2021 Nordix Foundation. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import {Component, Inject, ViewChild} from '@angular/core';
22 import {
23   ButtonModel,
24   Component as ComponentData,
25   ComponentInstance,
26   ModalModel,
27   ToscaPresentationData
28 } from 'app/models';
29 import {SdcUiCommon, SdcUiServices} from 'onap-ui-angular';
30 import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
31 import {Tab, Tabs} from "../../components/ui/tabs/tabs.component";
32 import * as _ from 'lodash';
33 import {OutputFEModel} from "../../../models/attributes-outputs/output-fe-model";
34 import {OutputBEModel} from "../../../models/attributes-outputs/output-be-model";
35 import {EVENTS, ResourceType, WorkspaceMode} from "../../../utils/constants";
36 import {ComponentModeService} from "../../services/component-services/component-mode.service";
37 import {EventListenerService} from "app/services";
38 import {HierarchyNavService} from "./services/hierarchy-nav.service";
39 import {ComponentServiceNg2} from "../../services/component-services/component.service";
40 import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service";
41 import {KeysPipe} from "../../pipes/keys.pipe";
42 import {
43   InstanceAttributesAPIMap,
44   InstanceBeAttributesMap,
45   InstanceFeAttributesMap
46 } from "app/models/attributes-outputs/attribute-fe-map";
47 import {ModalService} from "../../services/modal.service";
48 import {InstanceFeDetails} from "../../../models/instance-fe-details";
49 import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
50 import {UnsavedChangesComponent} from "../../components/ui/forms/unsaved-changes/unsaved-changes.component";
51 import {SimpleFlatAttribute} from "app/models/attributes-outputs/simple-flat-attribute";
52 import {AttributeFEModel} from "../../../models/attributes-outputs/attribute-fe-model";
53 import {AttributesUtils} from "./services/attributes.utils";
54 import {OutputsUtils} from "app/ng2/pages/attributes-outputs/services/outputs.utils";
55 import {AttributesService} from "app/ng2/services/attributes.service";
56 import {DerivedFEAttribute} from "../../../models/attributes-outputs/derived-fe-attribute";
57 import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
58 import {AttributeCreatorComponent} from "app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component";
59 import {AttributeRowSelectedEvent} from "app/ng2/components/logic/attributes-table/attributes-table.component";
60
61 const SERVICE_SELF_TITLE = "SELF";
62
63 @Component({
64   selector: 'attributes-outputs',
65   templateUrl: './attributes-outputs.page.component.html',
66   styleUrls: ['./attributes-outputs.page.component.less', '../../../../assets/styles/table-style.less']
67 })
68 export class AttributesOutputsComponent {
69   title = "Attributes & Outputs";
70
71   @ViewChild('componentAttributesTable')
72   private table: any;
73
74   component: ComponentData;
75   componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
76
77   attributesNavigationData = [];
78   instancesNavigationData = [];
79
80   instanceFeAttributesMap: InstanceFeAttributesMap;
81   outputs: Array<OutputFEModel> = [];
82   instances: Array<ComponentInstance> = [];
83   searchQuery: string;
84   attributeStructureHeader: string;
85
86   selectedFlatAttribute: SimpleFlatAttribute = new SimpleFlatAttribute();
87   selectedInstanceData: ComponentInstance = null;
88   checkedAttributesCount: number = 0;
89
90   hierarchyAttributesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
91   hierarchyInstancesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
92   searchAttributeName: string;
93   currentMainTab: Tab;
94   isOutputsTabSelected: boolean;
95   isAttributesTabSelected: boolean;
96   isReadonly: boolean;
97   resourceIsReadonly: boolean;
98   loadingInstances: boolean = false;
99   loadingOutputs: boolean = false;
100   loadingAttributes: boolean = false;
101   changedData: Array<AttributeFEModel | OutputFEModel>;
102   hasChangedData: boolean;
103   isValidChangedData: boolean;
104   savingChangedData: boolean;
105   stateChangeStartUnregister: Function;
106   serviceBeAttributesMap: InstanceBeAttributesMap;
107
108   @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
109   @ViewChild('attributeOutputTabs') attributeOutputTabs: Tabs;
110
111
112   constructor(private attributesService: AttributesService,
113               private hierarchyNavService: HierarchyNavService,
114               private attributesUtils: AttributesUtils,
115               private outputsUtils: OutputsUtils,
116               private componentServiceNg2: ComponentServiceNg2,
117               private componentInstanceServiceNg2: ComponentInstanceServiceNg2,
118               @Inject("$stateParams") _stateParams,
119               @Inject("$scope") private $scope: ng.IScope,
120               @Inject("$state") private $state: ng.ui.IStateService,
121               @Inject("Notification") private Notification: any,
122               private componentModeService: ComponentModeService,
123               private EventListenerService: EventListenerService,
124               private ModalServiceSdcUI: SdcUiServices.ModalService,
125               private ModalService: ModalService,
126               private keysPipe: KeysPipe,
127               private topologyTemplateService: TopologyTemplateService) {
128
129     this.instanceFeAttributesMap = new InstanceFeAttributesMap();
130     /* 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
131     than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
132     this.component = _stateParams.component;
133     this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.onCheckout);
134     this.updateViewMode();
135
136     this.changedData = [];
137     this.updateHasChangedData();
138     this.isValidChangedData = true;
139   }
140
141   ngOnInit() {
142     this.loadingOutputs = true;
143     this.loadingInstances = true;
144     this.loadingAttributes = true;
145     this.topologyTemplateService
146     .getComponentOutputsWithAttributes(this.component.componentType, this.component.uniqueId)
147     .subscribe(response => {
148       if (response.outputs) {
149         response.outputs.forEach(output => {
150           const newOutput: OutputFEModel = new OutputFEModel(output);
151           this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
152           this.outputs.push(newOutput);
153         });
154       }
155     }, error => {
156       this.Notification.error({
157         message: 'Failed to Initialize:' + error,
158         title: 'Failure'
159       });
160     }, () => {
161       this.loadingOutputs = false;
162     });
163     this.componentServiceNg2
164     .getComponentResourceAttributesData(this.component)
165     .subscribe(response => {
166       this.instances = [];
167       this.instances.push(...response.componentInstances);
168
169       // add the service self instance to the top of the list.
170       const serviceInstance = new ComponentInstance();
171       serviceInstance.name = SERVICE_SELF_TITLE;
172       serviceInstance.uniqueId = this.component.uniqueId;
173       this.instances.unshift(serviceInstance);
174       if (this.instances) {
175         this.instances.forEach(instance => {
176           this.instancesNavigationData.push(instance);
177           this.componentInstanceNamesMap[instance.uniqueId] = <InstanceFeDetails>{
178             name: instance.name,
179             iconClass: instance.iconClass,
180             originArchived: instance.originArchived
181           };
182         });
183       }
184       this.selectFirstInstanceByDefault();
185     }, (error) => {
186       this.Notification.error({
187         message: 'Failed to Initialize:' + error,
188         title: 'Failure'
189       });
190     }, () => {
191       this.loadingInstances = false;
192       this.loadingAttributes = false;
193     });
194
195     this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
196       // stop if has changed attributes
197       if (this.hasChangedData) {
198         event.preventDefault();
199         this.showUnsavedChangesAlert().then(() => {
200           this.$state.go(toState, toParams);
201         }, () => {
202         });
203       }
204     });
205   }
206
207   ngOnDestroy() {
208     this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
209     this.stateChangeStartUnregister();
210   }
211
212   selectFirstInstanceByDefault = () => {
213     if (this.instancesNavigationData.length > 0) {
214       this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
215     }
216   };
217
218   updateViewMode = () => {
219     this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
220   }
221
222   onCheckout = (component: ComponentData) => {
223     this.component = component;
224     this.updateViewMode();
225   }
226
227   isSelf = (): boolean => {
228     return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
229   }
230
231   getServiceAttributes() {
232     this.loadingAttributes = true;
233     this.topologyTemplateService
234     .getServiceAttributes(this.component.uniqueId)
235     .subscribe((response) => {
236       this.serviceBeAttributesMap = new InstanceBeAttributesMap();
237       this.serviceBeAttributesMap[this.component.uniqueId] = response;
238       this.processInstanceAttributesResponse(this.serviceBeAttributesMap, false);
239     }, (error) => {
240       this.Notification.error({
241         message: 'Failed to get Service Attribute:' + error,
242         title: 'Failure'
243       });
244     }, () => {
245       this.loadingAttributes = false;
246     });
247   }
248
249   onInstanceSelectedUpdate = (instance: ComponentInstance) => {
250     // stop if has changed attributes
251     if (this.hasChangedData) {
252       this.showUnsavedChangesAlert().then(() => {
253         this.changeSelectedInstance(instance)
254       });
255       return;
256     }
257     this.changeSelectedInstance(instance);
258   };
259
260   changeSelectedInstance = (instance: ComponentInstance) => {
261     this.selectedInstanceData = instance;
262     this.loadingAttributes = true;
263     if (instance instanceof ComponentInstance) {
264       let instanceBeAttributesMap: InstanceBeAttributesMap = new InstanceBeAttributesMap();
265       if (this.isOutput(instance.originType)) {
266         this.componentInstanceServiceNg2
267         .getComponentInstanceOutputs(this.component, instance)
268         .subscribe(response => {
269           instanceBeAttributesMap[instance.uniqueId] = response;
270           this.processInstanceAttributesResponse(instanceBeAttributesMap, true);
271         }, error => {
272           this.Notification.error({
273             message: 'Failed to change Selected Instance:' + error,
274             title: 'Failure'
275           });
276         }, () => {
277           this.loadingAttributes = false;
278         });
279       } else if (this.isSelf()) {
280         this.getServiceAttributes();
281       } else {
282         this.componentInstanceServiceNg2
283         .getComponentInstanceAttributes(this.component, instance.uniqueId)
284         .subscribe(response => {
285           instanceBeAttributesMap[instance.uniqueId] = response;
286           this.processInstanceAttributesResponse(instanceBeAttributesMap, false);
287         }, error => {
288           this.Notification.error({
289             message: 'Failed to change Selected Instance:' + error,
290             title: 'Failure'
291           });
292         }, () => {
293           this.loadingAttributes = false;
294         });
295       }
296
297       this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
298     } else {
299       this.loadingAttributes = false;
300     }
301
302     //clear selected attribute from the navigation
303     this.selectedFlatAttribute = new SimpleFlatAttribute();
304     this.attributesNavigationData = [];
305   };
306
307   /**
308    * Entry point handling response from server
309    */
310   processInstanceAttributesResponse = (instanceBeAttributesMap: InstanceBeAttributesMap, originTypeIsVF: boolean) => {
311     this.instanceFeAttributesMap = this.attributesUtils.convertAttributesMapToFEAndCreateChildren(instanceBeAttributesMap, originTypeIsVF, this.outputs); //create flattened children, disable declared attribs, and init values
312     this.checkedAttributesCount = 0;
313   };
314
315
316   /*** VALUE CHANGE EVENTS ***/
317   dataChanged = (item: AttributeFEModel | OutputFEModel) => {
318     let itemHasChanged;
319     if (this.isAttributesTabSelected && item instanceof AttributeFEModel) {
320       itemHasChanged = item.hasValueObjChanged();
321     } else if (this.isOutputsTabSelected && item instanceof OutputFEModel) {
322       itemHasChanged = item.hasChanged();
323     }
324
325     const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
326     if (itemHasChanged) {
327       if (dataChangedIdx === -1) {
328         this.changedData.push(item);
329       }
330     } else {
331       if (dataChangedIdx !== -1) {
332         this.changedData.splice(dataChangedIdx, 1);
333       }
334     }
335
336     if (this.isAttributesTabSelected) {
337       this.isValidChangedData = this.changedData.every((changedItem) => (<AttributeFEModel>changedItem).valueObjIsValid);
338     } else if (this.isOutputsTabSelected) {
339       this.isValidChangedData = this.changedData.every((changedItem) => (<OutputFEModel>changedItem).defaultValueObjIsValid);
340     }
341     this.updateHasChangedData();
342   };
343
344
345   /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
346
347   /**
348    * Handle select node in navigation area, and select the row in table
349    */
350   onAttributeSelectedUpdate = ($event) => {
351     this.selectedFlatAttribute = $event;
352     let parentAttribute: AttributeFEModel = this.attributesService.getParentAttributeFEModelFromPath(this.instanceFeAttributesMap[this.selectedFlatAttribute.instanceName], this.selectedFlatAttribute.path);
353     parentAttribute.expandedChildAttributeId = this.selectedFlatAttribute.path;
354   };
355
356   /**
357    * When user select row in table, this will prepare the hierarchy object for the tree.
358    */
359   selectAttributeRow = (attributeRowSelectedEvent: AttributeRowSelectedEvent) => {
360     let attribute = attributeRowSelectedEvent.attributeModel;
361     let instanceName = attributeRowSelectedEvent.instanceName;
362     this.attributeStructureHeader = null;
363
364     // Build hierarchy tree for the navigation and update attributesNavigationData with it.
365     if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
366       let simpleFlatAttributes: Array<SimpleFlatAttribute>;
367       if (attribute instanceof AttributeFEModel) {
368         simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(attribute, instanceName);
369       } else if (attribute instanceof DerivedFEAttribute) {
370         // Need to find parent AttributeFEModel
371         let parentAttributeFEModel: AttributeFEModel = _.find(this.instanceFeAttributesMap[instanceName], (tmpFeAttribute): boolean => {
372           return attribute.attributesName.indexOf(tmpFeAttribute.name) === 0;
373         });
374         simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(parentAttributeFEModel, instanceName);
375       }
376       this.attributesNavigationData = simpleFlatAttributes;
377     }
378
379     // Update the header in the navigation tree with attribute name.
380     this.attributeStructureHeader = (attribute.attributesName.split('#'))[0];
381
382     // Set selected attribute in table
383     this.selectedFlatAttribute = this.hierarchyNavService.createSimpleFlatAttribute(attribute, instanceName);
384     this.hierarchyNavTabs.triggerTabChange('Attribute Structure');
385   };
386
387   tabChanged = (event) => {
388     // stop if has changed attributes
389     if (this.hasChangedData) {
390       this.attributeOutputTabs.triggerTabChange(this.currentMainTab.title);
391       this.showUnsavedChangesAlert().then(() => {
392         this.attributeOutputTabs.selectTab(this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title));
393       }, () => {
394       });
395       return;
396     }
397
398     this.currentMainTab = this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title);
399     this.isAttributesTabSelected = this.currentMainTab.title === "Attributes";
400     this.isOutputsTabSelected = this.currentMainTab.title === "Outputs";
401     this.attributeStructureHeader = null;
402     this.searchQuery = '';
403   };
404
405
406   /*** DECLARE ATTRIBUTES/OUTPUTS ***/
407   declareAttributes = (): void => {
408     let selectedComponentInstancesAttributes: InstanceBeAttributesMap = new InstanceBeAttributesMap();
409     let selectedComponentInstancesOutputs: InstanceBeAttributesMap = new InstanceBeAttributesMap();
410     let instancesIds = this.keysPipe.transform(this.instanceFeAttributesMap, []);
411
412     angular.forEach(instancesIds, (instanceId: string): void => {
413       let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
414       if (selectedInstanceData instanceof ComponentInstance) {
415         if (!this.isOutput(selectedInstanceData.originType)) {
416           // convert Attribute FE model -> Attribute BE model, extract only checked
417           selectedComponentInstancesAttributes[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
418         } else {
419           selectedComponentInstancesOutputs[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
420         }
421       }
422     });
423
424     let outputsToCreate: InstanceAttributesAPIMap = new InstanceAttributesAPIMap(selectedComponentInstancesOutputs, selectedComponentInstancesAttributes);
425     this.topologyTemplateService
426     .createOutput(this.component, outputsToCreate, this.isSelf())
427     .subscribe((response) => {
428       this.setOutputTabIndication(response.length);
429       this.checkedAttributesCount = 0;
430       response.forEach((output: OutputBEModel) => {
431         const newOutput: OutputFEModel = new OutputFEModel(output);
432         this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
433         this.outputs.push(newOutput);
434         this.updateAttributeValueAfterDeclare(newOutput);
435       });
436     });
437   };
438
439   saveChangedData = (): Promise<(AttributeBEModel | OutputBEModel)[]> => {
440     return new Promise((resolve, reject) => {
441       if (!this.isValidChangedData) {
442         reject('Changed data is invalid - cannot save!');
443         return;
444       }
445       if (!this.changedData.length) {
446         resolve([]);
447         return;
448       }
449
450       // make request and its handlers
451       let request;
452       let handleSuccess, handleError;
453       if (this.isAttributesTabSelected) {
454         this.changedData.map((changedAttrib) => {
455           changedAttrib = <AttributeFEModel>changedAttrib;
456           const attribBE = new AttributeBEModel(changedAttrib);
457           attribBE.toscaPresentation = new ToscaPresentationData();
458           attribBE.toscaPresentation.ownerId = changedAttrib.parentUniqueId;
459           attribBE.value = changedAttrib.getJSONValue();
460           attribBE.name = changedAttrib.origName || changedAttrib.name;
461           delete attribBE.origName;
462           return attribBE;
463         });
464       } else if (this.isOutputsTabSelected) {
465         const changedOutputs: OutputBEModel[] = this.changedData.map((changedOutput) => {
466           changedOutput = <OutputFEModel>changedOutput;
467           const outputBE = new OutputBEModel(changedOutput);
468           outputBE.defaultValue = changedOutput.getJSONDefaultValue();
469           return outputBE;
470         });
471         request = this.componentServiceNg2
472         .updateComponentOutputs(this.component, changedOutputs);
473         handleSuccess = (response) => {
474           // reset each changed attribute with new value and remove it from changed attributes list
475           response.forEach((resOutput) => {
476             const changedOutput = <OutputFEModel>this.changedData.shift();
477             this.outputsUtils.resetOutputDefaultValue(changedOutput, resOutput.defaultValue);
478             changedOutput.required = resOutput.required;
479           });
480         }
481         this.savingChangedData = true;
482         request.subscribe(
483             (response) => {
484               this.savingChangedData = false;
485               handleSuccess && handleSuccess(response);
486               this.updateHasChangedData();
487               resolve(response);
488             },
489             (error) => {
490               this.savingChangedData = false;
491               handleError && handleError(error);
492               this.updateHasChangedData();
493               reject(error);
494             }
495         );
496       }
497
498     });
499   };
500
501
502   reverseChangedData = (): void => {
503     // make reverse item handler
504     let handleReverseItem;
505     if (this.isAttributesTabSelected) {
506       handleReverseItem = (changedItem) => {
507         changedItem = <AttributeFEModel>changedItem;
508         this.attributesUtils.resetAttributeValue(changedItem, changedItem.value);
509         this.checkedAttributesCount = 0;
510       };
511     } else if (this.isOutputsTabSelected) {
512       handleReverseItem = (changedItem) => {
513         changedItem = <OutputFEModel>changedItem;
514         this.outputsUtils.resetOutputDefaultValue(changedItem, changedItem.defaultValue);
515         changedItem.required = changedItem.requiredOrig;
516       };
517     }
518
519     this.changedData.forEach(handleReverseItem);
520     this.changedData = [];
521     this.updateHasChangedData();
522   };
523
524   updateHasChangedData = (): boolean => {
525     const curHasChangedData: boolean = (this.changedData.length > 0);
526     if (curHasChangedData !== this.hasChangedData) {
527       this.hasChangedData = curHasChangedData;
528       if (this.hasChangedData) {
529         this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
530       } else {
531         this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
532       }
533     }
534     return this.hasChangedData;
535   };
536
537   doSaveChangedData = (onSuccessFunction?: Function, onError?: Function): void => {
538     this.saveChangedData().then(
539         () => {
540           this.Notification.success({
541             message: 'Successfully saved changes',
542             title: 'Saved'
543           });
544           if (onSuccessFunction) onSuccessFunction();
545           if (this.isAttributesTabSelected) {
546             this.checkedAttributesCount = 0;
547           }
548         },
549         () => {
550           this.Notification.error({
551             message: 'Failed to save changes!',
552             title: 'Failure'
553           });
554           if (onError) onError();
555         }
556     );
557   };
558
559   showUnsavedChangesAlert = (): Promise<any> => {
560     let modalTitle: string;
561     if (this.isAttributesTabSelected) {
562       modalTitle = `Unsaved attributes for ${this.selectedInstanceData.name}`;
563     } else if (this.isOutputsTabSelected) {
564       modalTitle = `Unsaved outputs for ${this.component.name}`;
565     }
566
567     return new Promise<any>((resolve, reject) => {
568       this.ModalServiceSdcUI.openCustomModal(
569           {
570             title: modalTitle,
571             size: 'sm',
572             type: SdcUiCommon.ModalType.custom,
573             testId: "navigate-modal",
574
575             buttons: [
576               {
577                 id: 'cancelButton',
578                 text: 'Cancel',
579                 type: SdcUiCommon.ButtonType.secondary,
580                 size: 'xsm',
581                 closeModal: true,
582                 callback: () => reject()
583               },
584               {
585                 id: 'discardButton',
586                 text: 'Discard',
587                 type: SdcUiCommon.ButtonType.secondary,
588                 size: 'xsm',
589                 closeModal: true,
590                 callback: () => {
591                   this.reverseChangedData();
592                   resolve()
593                 }
594               },
595               {
596                 id: 'saveButton',
597                 text: 'Save',
598                 type: SdcUiCommon.ButtonType.primary,
599                 size: 'xsm',
600                 closeModal: true,
601                 disabled: !this.isValidChangedData,
602                 callback: () => this.doSaveChangedData(resolve, reject)
603               }
604             ] as SdcUiCommon.IModalButtonComponent[]
605           } as SdcUiCommon.IModalConfig, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
606     });
607
608   }
609
610   updateAttributeValueAfterDeclare = (output: OutputFEModel) => {
611     const attributeList = this.instanceFeAttributesMap[output.instanceUniqueId];
612     if (attributeList) {
613       const instanceName = output.instanceUniqueId.slice(output.instanceUniqueId.lastIndexOf('.') + 1);
614       const attributeForUpdatingVal = attributeList.find((feAttribute: AttributeFEModel) => {
615         return feAttribute.name == output.relatedAttributeName &&
616             (feAttribute.name == output.relatedAttributeName || output.name === instanceName.concat('_').concat(feAttribute.name.replace(/[.]/g, '_')));
617       });
618       const outputPath = (output.outputPath && output.outputPath != attributeForUpdatingVal.name) ? output.outputPath : undefined;
619       attributeForUpdatingVal.setAsDeclared(outputPath); //set attribute as declared before assigning value
620       // this.attributesService.disableRelatedAttributes(attributeForUpdatingVal, outputPath);
621       this.attributesUtils.resetAttributeValue(attributeForUpdatingVal, output.relatedAttributeValue, outputPath);
622     }
623   }
624
625   //used for declare button, to keep count of newly checked attributes (and ignore declared attributes)
626   updateCheckedAttributeCount = (increment: boolean): void => {
627     this.checkedAttributesCount += (increment) ? 1 : -1;
628   };
629
630   setOutputTabIndication = (numOutputs: number): void => {
631     this.attributeOutputTabs.setTabIndication('Outputs', numOutputs);
632   };
633
634
635   resetUnsavedChangesForOutput = (output: OutputFEModel) => {
636     this.outputsUtils.resetOutputDefaultValue(output, output.defaultValue);
637     this.changedData = this.changedData.filter((changedItem) => changedItem.uniqueId !== output.uniqueId);
638     this.updateHasChangedData();
639   }
640
641   deleteOutput = (output: OutputFEModel) => {
642     //reset any unsaved changes to the output before deleting it
643     this.resetUnsavedChangesForOutput(output);
644
645     let outputToDelete = new OutputBEModel(output);
646
647     this.componentServiceNg2
648     .deleteOutput(this.component, outputToDelete)
649     .subscribe(response => {
650       this.outputs = this.outputs.filter(output => output.uniqueId !== response.uniqueId);
651
652       //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning attributes within the response, use commented code below instead!
653       this.changeSelectedInstance(this.selectedInstanceData);
654     }, error => {
655       this.Notification.error({
656         message: 'Failed to delete Output:' + error,
657         title: 'Failure'
658       });
659     });
660   };
661
662   deleteAttribute = (attribute: AttributeFEModel) => {
663     const attributeToDelete = new AttributeFEModel(attribute);
664     this.loadingAttributes = true;
665     const feMap = this.instanceFeAttributesMap;
666     this.topologyTemplateService
667     .deleteServiceAttribute(this.component.uniqueId, attributeToDelete)
668     .subscribe((response) => {
669       const attribs = feMap[this.component.uniqueId];
670       attribs.splice(attribs.findIndex(p => p.uniqueId === response), 1);
671     }, (error) => {
672       this.Notification.error({
673         message: 'Failed to delete Attribute:' + error,
674         title: 'Failure'
675       });
676     }, () => {
677       this.loadingAttributes = false;
678     });
679   }
680
681   addAttribute = () => {
682     let modalTitle = 'Add Attribute';
683     let modal = this.ModalService.createCustomModal(new ModalModel(
684         'sm',
685         modalTitle,
686         null,
687         [
688           new ButtonModel('Save', 'blue', () => {
689             modal.instance.dynamicContent.instance.isLoading = true;
690             const newAttribute: AttributeBEModel = modal.instance.dynamicContent.instance.attributeModel;
691             this.topologyTemplateService.createServiceAttribute(this.component.uniqueId, newAttribute)
692             .subscribe((response) => {
693               modal.instance.dynamicContent.instance.isLoading = false;
694               const newAttrib: AttributeFEModel = this.attributesUtils.convertAddAttributeBEToAttributeFE(response);
695               this.instanceFeAttributesMap[this.component.uniqueId].push(newAttrib);
696               modal.instance.close();
697             }, (error) => {
698               modal.instance.dynamicContent.instance.isLoading = false;
699               this.Notification.error({
700                 message: 'Failed to add Attribute:' + error,
701                 title: 'Failure'
702               });
703             });
704           }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
705           new ButtonModel('Cancel', 'outline grey', () => {
706             modal.instance.close();
707           }),
708         ],
709         null
710     ));
711     this.ModalService.addDynamicContentToModal(modal, AttributeCreatorComponent, {});
712     modal.instance.open();
713   }
714
715   private isOutput = (instanceType: string): boolean => {
716     return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
717   }
718
719 }