76001a7f283017e622e8b5122d7fe3c6753ee3ef
[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   ngOnDestroy() {
207     this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
208     this.stateChangeStartUnregister();
209   }
210
211   selectFirstInstanceByDefault = () => {
212     if (this.instancesNavigationData.length > 0) {
213       this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
214     }
215   };
216
217   updateViewMode = () => {
218     this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
219   }
220
221   onCheckout = (component: ComponentData) => {
222     this.component = component;
223     this.updateViewMode();
224   }
225
226   isSelf = (): boolean => {
227     return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
228   }
229
230   getServiceAttributes() {
231     this.loadingAttributes = true;
232     this.topologyTemplateService
233     .getServiceAttributes(this.component.uniqueId)
234     .subscribe((response) => {
235       this.serviceBeAttributesMap = new InstanceBeAttributesMap();
236       this.serviceBeAttributesMap[this.component.uniqueId] = response;
237       this.processInstanceAttributesResponse(this.serviceBeAttributesMap, false);
238     }, (error) => {
239       this.Notification.error({
240         message: 'Failed to get Service Attribute:' + error,
241         title: 'Failure'
242       });
243     }, () => {
244       this.loadingAttributes = false;
245     });
246   }
247
248   onInstanceSelectedUpdate = (instance: ComponentInstance) => {
249     // stop if has changed attributes
250     if (this.hasChangedData) {
251       this.showUnsavedChangesAlert().then(() => {
252         this.changeSelectedInstance(instance)
253       });
254       return;
255     }
256     this.changeSelectedInstance(instance);
257   };
258
259   changeSelectedInstance = (instance: ComponentInstance) => {
260     this.selectedInstanceData = instance;
261     this.loadingAttributes = true;
262     if (instance instanceof ComponentInstance) {
263       let instanceBeAttributesMap: InstanceBeAttributesMap = new InstanceBeAttributesMap();
264       if (this.isOutput(instance.originType)) {
265         this.componentInstanceServiceNg2
266         .getComponentInstanceOutputs(this.component, instance)
267         .subscribe(response => {
268           instanceBeAttributesMap[instance.uniqueId] = response;
269           this.processInstanceAttributesResponse(instanceBeAttributesMap, true);
270         }, error => {
271           this.Notification.error({
272             message: 'Failed to change Selected Instance:' + error,
273             title: 'Failure'
274           });
275         }, () => {
276           this.loadingAttributes = false;
277         });
278       } else if (this.isSelf()) {
279         this.getServiceAttributes();
280       } else {
281         this.componentInstanceServiceNg2
282         .getComponentInstanceAttributes(this.component, instance.uniqueId)
283         .subscribe(response => {
284           instanceBeAttributesMap[instance.uniqueId] = response;
285           this.processInstanceAttributesResponse(instanceBeAttributesMap, false);
286         }, error => {
287           this.Notification.error({
288             message: 'Failed to change Selected Instance:' + error,
289             title: 'Failure'
290           });
291         }, () => {
292           this.loadingAttributes = false;
293         });
294       }
295
296       this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
297     } else {
298       this.loadingAttributes = false;
299     }
300
301     //clear selected attribute from the navigation
302     this.selectedFlatAttribute = new SimpleFlatAttribute();
303     this.attributesNavigationData = [];
304   };
305
306   /**
307    * Entry point handling response from server
308    */
309   processInstanceAttributesResponse = (instanceBeAttributesMap: InstanceBeAttributesMap, originTypeIsVF: boolean) => {
310     this.instanceFeAttributesMap = this.attributesUtils.convertAttributesMapToFEAndCreateChildren(instanceBeAttributesMap, originTypeIsVF, this.outputs); //create flattened children, disable declared attribs, and init values
311     this.checkedAttributesCount = 0;
312   };
313
314
315   /*** VALUE CHANGE EVENTS ***/
316   dataChanged = (item: AttributeFEModel | OutputFEModel) => {
317     let itemHasChanged;
318     if (this.isAttributesTabSelected && item instanceof AttributeFEModel) {
319       itemHasChanged = item.hasValueObjChanged();
320     } else if (this.isOutputsTabSelected && item instanceof OutputFEModel) {
321       itemHasChanged = item.hasChanged();
322     }
323
324     const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
325     if (itemHasChanged) {
326       if (dataChangedIdx === -1) {
327         this.changedData.push(item);
328       }
329     } else {
330       if (dataChangedIdx !== -1) {
331         this.changedData.splice(dataChangedIdx, 1);
332       }
333     }
334
335     if (this.isAttributesTabSelected) {
336       this.isValidChangedData = this.changedData.every((changedItem) => (<AttributeFEModel>changedItem).valueObjIsValid);
337     } else if (this.isOutputsTabSelected) {
338       this.isValidChangedData = this.changedData.every((changedItem) => (<OutputFEModel>changedItem).defaultValueObjIsValid);
339     }
340     this.updateHasChangedData();
341   };
342
343
344   /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
345
346   /**
347    * Handle select node in navigation area, and select the row in table
348    */
349   onAttributeSelectedUpdate = ($event) => {
350     this.selectedFlatAttribute = $event;
351     let parentAttribute: AttributeFEModel = this.attributesService.getParentAttributeFEModelFromPath(this.instanceFeAttributesMap[this.selectedFlatAttribute.instanceName], this.selectedFlatAttribute.path);
352     parentAttribute.expandedChildAttributeId = this.selectedFlatAttribute.path;
353   };
354
355   /**
356    * When user select row in table, this will prepare the hierarchy object for the tree.
357    */
358   selectAttributeRow = (attributeRowSelectedEvent: AttributeRowSelectedEvent) => {
359     let attribute = attributeRowSelectedEvent.attributeModel;
360     let instanceName = attributeRowSelectedEvent.instanceName;
361     this.attributeStructureHeader = null;
362
363     // Build hierarchy tree for the navigation and update attributesNavigationData with it.
364     if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
365       let simpleFlatAttributes: Array<SimpleFlatAttribute>;
366       if (attribute instanceof AttributeFEModel) {
367         simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(attribute, instanceName);
368       } else if (attribute instanceof DerivedFEAttribute) {
369         // Need to find parent AttributeFEModel
370         let parentAttributeFEModel: AttributeFEModel = _.find(this.instanceFeAttributesMap[instanceName], (tmpFeAttribute): boolean => {
371           return attribute.attributesName.indexOf(tmpFeAttribute.name) === 0;
372         });
373         simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(parentAttributeFEModel, instanceName);
374       }
375       this.attributesNavigationData = simpleFlatAttributes;
376     }
377
378     // Update the header in the navigation tree with attribute name.
379     this.attributeStructureHeader = (attribute.attributesName.split('#'))[0];
380
381     // Set selected attribute in table
382     this.selectedFlatAttribute = this.hierarchyNavService.createSimpleFlatAttribute(attribute, instanceName);
383     this.hierarchyNavTabs.triggerTabChange('Attribute Structure');
384   };
385
386   tabChanged = (event) => {
387     // stop if has changed attributes
388     if (this.hasChangedData) {
389       this.attributeOutputTabs.triggerTabChange(this.currentMainTab.title);
390       this.showUnsavedChangesAlert().then(() => {
391         this.attributeOutputTabs.selectTab(this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title));
392       });
393       return;
394     }
395
396     this.currentMainTab = this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title);
397     this.isAttributesTabSelected = this.currentMainTab.title === "Attributes";
398     this.isOutputsTabSelected = this.currentMainTab.title === "Outputs";
399     this.attributeStructureHeader = null;
400     this.searchQuery = '';
401   };
402
403
404   /*** DECLARE ATTRIBUTES/OUTPUTS ***/
405   declareAttributes = (): void => {
406     let selectedComponentInstancesAttributes: InstanceBeAttributesMap = new InstanceBeAttributesMap();
407     let selectedComponentInstancesOutputs: InstanceBeAttributesMap = new InstanceBeAttributesMap();
408     let instancesIds = this.keysPipe.transform(this.instanceFeAttributesMap, []);
409
410     angular.forEach(instancesIds, (instanceId: string): void => {
411       let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
412       if (selectedInstanceData instanceof ComponentInstance) {
413         if (!this.isOutput(selectedInstanceData.originType)) {
414           // convert Attribute FE model -> Attribute BE model, extract only checked
415           selectedComponentInstancesAttributes[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
416         } else {
417           selectedComponentInstancesOutputs[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
418         }
419       }
420     });
421
422     let outputsToCreate: InstanceAttributesAPIMap = new InstanceAttributesAPIMap(selectedComponentInstancesOutputs, selectedComponentInstancesAttributes);
423     this.topologyTemplateService
424     .createOutput(this.component, outputsToCreate, this.isSelf())
425     .subscribe((response) => {
426       this.setOutputTabIndication(response.length);
427       this.checkedAttributesCount = 0;
428       response.forEach((output: OutputBEModel) => {
429         const newOutput: OutputFEModel = new OutputFEModel(output);
430         this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
431         this.outputs.push(newOutput);
432         this.updateAttributeValueAfterDeclare(newOutput);
433       });
434     });
435   };
436
437   saveChangedData = (): Promise<(AttributeBEModel | OutputBEModel)[]> => {
438     return new Promise((resolve, reject) => {
439       if (!this.isValidChangedData) {
440         reject('Changed data is invalid - cannot save!');
441         return;
442       }
443       if (!this.changedData.length) {
444         resolve([]);
445         return;
446       }
447
448       // make request and its handlers
449       let request;
450       let handleSuccess, handleError;
451       if (this.isAttributesTabSelected) {
452         const changedAttribs = this.changedData.map((changedAttrib) => {
453           changedAttrib = <AttributeFEModel>changedAttrib;
454           const attribBE = new AttributeBEModel(changedAttrib);
455           attribBE.toscaPresentation = new ToscaPresentationData();
456           attribBE.toscaPresentation.ownerId = changedAttrib.parentUniqueId;
457           attribBE.value = changedAttrib.getJSONValue();
458           attribBE.name = changedAttrib.origName || changedAttrib.name;
459           delete attribBE.origName;
460           return attribBE;
461         });
462
463         if (this.selectedInstanceData instanceof ComponentInstance) {
464           if (this.isSelf()) {
465             console.log("changedAttribs", changedAttribs);
466             request = this.topologyTemplateService.updateServiceAttributes(this.component.uniqueId, _.map(changedAttribs, cp => {
467               delete cp.constraints;
468               return cp;
469             }));
470           } else {
471             request = this.componentInstanceServiceNg2
472             .updateInstanceAttributes(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedAttribs);
473           }
474           handleSuccess = (response) => {
475             // reset each changed attribute with new value and remove it from changed attributes list
476             response.forEach((resAttrib) => {
477               const changedAttrib = <AttributeFEModel>this.changedData.shift();
478               this.attributesUtils.resetAttributeValue(changedAttrib, resAttrib.value);
479             });
480             resolve(response);
481             console.log("updated instance attributes: ", response);
482           };
483         }
484       } else if (this.isOutputsTabSelected) {
485         const changedOutputs: OutputBEModel[] = this.changedData.map((changedOutput) => {
486           changedOutput = <OutputFEModel>changedOutput;
487           const outputBE = new OutputBEModel(changedOutput);
488           outputBE.defaultValue = changedOutput.getJSONDefaultValue();
489           return outputBE;
490         });
491         request = this.componentServiceNg2.updateComponentOutputs(this.component, changedOutputs);
492         handleSuccess = (response) => {
493           // reset each changed attribute with new value and remove it from changed attributes list
494           response.forEach((resOutput) => {
495             const changedOutput = <OutputFEModel>this.changedData.shift();
496             this.outputsUtils.resetOutputDefaultValue(changedOutput, resOutput.defaultValue);
497             changedOutput.required = resOutput.required;
498           });
499         }
500       }
501
502       this.savingChangedData = true;
503       request.subscribe(
504           (response) => {
505             this.savingChangedData = false;
506             handleSuccess && handleSuccess(response);
507             this.updateHasChangedData();
508             resolve(response);
509           },
510           (error) => {
511             this.savingChangedData = false;
512             handleError && handleError(error);
513             this.updateHasChangedData();
514             reject(error);
515           }
516       );
517
518     });
519   };
520
521
522   reverseChangedData = (): void => {
523     // make reverse item handler
524     let handleReverseItem;
525     if (this.isAttributesTabSelected) {
526       handleReverseItem = (changedItem) => {
527         changedItem = <AttributeFEModel>changedItem;
528         this.attributesUtils.resetAttributeValue(changedItem, changedItem.value);
529         this.checkedAttributesCount = 0;
530       };
531     } else if (this.isOutputsTabSelected) {
532       handleReverseItem = (changedItem) => {
533         changedItem = <OutputFEModel>changedItem;
534         this.outputsUtils.resetOutputDefaultValue(changedItem, changedItem.defaultValue);
535         changedItem.required = changedItem.requiredOrig;
536       };
537     }
538
539     this.changedData.forEach(handleReverseItem);
540     this.changedData = [];
541     this.updateHasChangedData();
542   };
543
544   updateHasChangedData = (): boolean => {
545     const curHasChangedData: boolean = (this.changedData.length > 0);
546     if (curHasChangedData !== this.hasChangedData) {
547       this.hasChangedData = curHasChangedData;
548       if (this.hasChangedData) {
549         this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
550       } else {
551         this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
552       }
553     }
554     return this.hasChangedData;
555   };
556
557   doSaveChangedData = (onSuccessFunction?: Function, onError?: Function): void => {
558     this.saveChangedData().then(
559         () => {
560           this.Notification.success({
561             message: 'Successfully saved changes',
562             title: 'Saved'
563           });
564           if (onSuccessFunction) onSuccessFunction();
565           if (this.isAttributesTabSelected) {
566             this.checkedAttributesCount = 0;
567           }
568         },
569         () => {
570           this.Notification.error({
571             message: 'Failed to save changes!',
572             title: 'Failure'
573           });
574           if (onError) onError();
575         }
576     );
577   };
578
579   showUnsavedChangesAlert = (): Promise<any> => {
580     let modalTitle: string;
581     if (this.isAttributesTabSelected) {
582       modalTitle = `Unsaved attributes for ${this.selectedInstanceData.name}`;
583     } else if (this.isOutputsTabSelected) {
584       modalTitle = `Unsaved outputs for ${this.component.name}`;
585     }
586
587     return new Promise<any>((resolve, reject) => {
588       this.ModalServiceSdcUI.openCustomModal(
589           {
590             title: modalTitle,
591             size: 'sm',
592             type: SdcUiCommon.ModalType.custom,
593             testId: "navigate-modal",
594
595             buttons: [
596               {
597                 id: 'cancelButton',
598                 text: 'Cancel',
599                 type: SdcUiCommon.ButtonType.secondary,
600                 size: 'xsm',
601                 closeModal: true,
602                 callback: () => reject()
603               },
604               {
605                 id: 'discardButton',
606                 text: 'Discard',
607                 type: SdcUiCommon.ButtonType.secondary,
608                 size: 'xsm',
609                 closeModal: true,
610                 callback: () => {
611                   this.reverseChangedData();
612                   resolve()
613                 }
614               },
615               {
616                 id: 'saveButton',
617                 text: 'Save',
618                 type: SdcUiCommon.ButtonType.primary,
619                 size: 'xsm',
620                 closeModal: true,
621                 disabled: !this.isValidChangedData,
622                 callback: () => this.doSaveChangedData(resolve, reject)
623               }
624             ] as SdcUiCommon.IModalButtonComponent[]
625           } as SdcUiCommon.IModalConfig, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
626     });
627
628   }
629
630   updateAttributeValueAfterDeclare = (output: OutputFEModel) => {
631     const attributeList = this.instanceFeAttributesMap[output.instanceUniqueId];
632     if (attributeList) {
633       const instanceName = output.instanceUniqueId.slice(output.instanceUniqueId.lastIndexOf('.') + 1);
634       const attributeForUpdatingVal = attributeList.find((feAttribute: AttributeFEModel) => {
635         return feAttribute.name == output.relatedAttributeName &&
636             (feAttribute.name == output.relatedAttributeName || output.name === instanceName.concat('_').concat(feAttribute.name.replace(/[.]/g, '_')));
637       });
638       const outputPath = (output.outputPath && output.outputPath != attributeForUpdatingVal.name) ? output.outputPath : undefined;
639       attributeForUpdatingVal.setAsDeclared(outputPath); //set attribute as declared before assigning value
640       // this.attributesService.disableRelatedAttributes(attributeForUpdatingVal, outputPath);
641       this.attributesUtils.resetAttributeValue(attributeForUpdatingVal, output.relatedAttributeValue, outputPath);
642     }
643   }
644
645   //used for declare button, to keep count of newly checked attributes (and ignore declared attributes)
646   updateCheckedAttributeCount = (increment: boolean): void => {
647     this.checkedAttributesCount += (increment) ? 1 : -1;
648   };
649
650   setOutputTabIndication = (numOutputs: number): void => {
651     this.attributeOutputTabs.setTabIndication('Outputs', numOutputs);
652   };
653
654   deleteOutput = (output: OutputFEModel) => {
655     let outputToDelete = new OutputBEModel(output);
656
657     this.componentServiceNg2
658     .deleteOutput(this.component, outputToDelete)
659     .subscribe(response => {
660       this.outputs = this.outputs.filter(output => output.uniqueId !== response.uniqueId);
661
662       //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning attributes within the response, use commented code below instead!
663       this.changeSelectedInstance(this.selectedInstanceData);
664     }, error => {
665       this.Notification.error({
666         message: 'Failed to delete Output:' + error,
667         title: 'Failure'
668       });
669     });
670   };
671
672   deleteAttribute = (attribute: AttributeFEModel) => {
673     const attributeToDelete = new AttributeFEModel(attribute);
674     this.loadingAttributes = true;
675     const feMap = this.instanceFeAttributesMap;
676     this.topologyTemplateService
677     .deleteServiceAttribute(this.component.uniqueId, attributeToDelete)
678     .subscribe((response) => {
679       const attribs = feMap[this.component.uniqueId];
680       attribs.splice(attribs.findIndex(p => p.uniqueId === response), 1);
681     }, (error) => {
682       this.Notification.error({
683         message: 'Failed to delete Attribute:' + error,
684         title: 'Failure'
685       });
686     }, () => {
687       this.loadingAttributes = false;
688     });
689   }
690
691   addAttribute = () => {
692     let modalTitle = 'Add Attribute';
693     let modal = this.ModalService.createCustomModal(new ModalModel(
694         'sm',
695         modalTitle,
696         null,
697         [
698           new ButtonModel('Save', 'blue', () => {
699             modal.instance.dynamicContent.instance.isLoading = true;
700             const newAttribute: AttributeBEModel = modal.instance.dynamicContent.instance.attributeModel;
701             this.topologyTemplateService.createServiceAttribute(this.component.uniqueId, newAttribute)
702             .subscribe((response) => {
703               modal.instance.dynamicContent.instance.isLoading = false;
704               const newAttrib: AttributeFEModel = this.attributesUtils.convertAddAttributeBEToAttributeFE(response);
705               this.instanceFeAttributesMap[this.component.uniqueId].push(newAttrib);
706               modal.instance.close();
707             }, (error) => {
708               modal.instance.dynamicContent.instance.isLoading = false;
709               this.Notification.error({
710                 message: 'Failed to add Attribute:' + error,
711                 title: 'Failure'
712               });
713             });
714           }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
715           new ButtonModel('Cancel', 'outline grey', () => {
716             modal.instance.close();
717           }),
718         ],
719         null
720     ));
721     this.ModalService.addDynamicContentToModal(modal, AttributeCreatorComponent, {});
722     modal.instance.open();
723   }
724
725   private isOutput = (instanceType: string): boolean => {
726     return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
727   }
728
729 }