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