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