Add property mapping feature to ONAP
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / properties-assignment.page.component.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. 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 * as _ from "lodash";
22 import {Component, ViewChild, Inject, TemplateRef} from "@angular/core";
23 import { PropertiesService } from "../../services/properties.service";
24 import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData, ModalModel, ButtonModel } from "app/models";
25 import { ResourceType } from "app/utils";
26 import {ComponentServiceNg2} from "../../services/component-services/component.service";
27 import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service"
28 import { InputBEModel, InputFEModel, ComponentInstance, GroupInstance, PolicyInstance, PropertyBEModel, DerivedFEProperty, SimpleFlatProperty } from "app/models";
29 import { KeysPipe } from 'app/ng2/pipes/keys.pipe';
30 import {WorkspaceMode, EVENTS} from "../../../utils/constants";
31 import {EventListenerService} from "app/services/event-listener-service"
32 import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
33 import {FilterPropertiesAssignmentComponent} from "../../components/logic/filter-properties-assignment/filter-properties-assignment.component";
34 import {PropertyRowSelectedEvent} from "../../components/logic/properties-table/properties-table.component";
35 import {HierarchyNavService} from "./services/hierarchy-nav.service";
36 import {PropertiesUtils} from "./services/properties.utils";
37 import {ComponentModeService} from "../../services/component-services/component-mode.service";
38 import {ModalService} from "../../services/modal.service";
39 import {Tabs, Tab} from "../../components/ui/tabs/tabs.component";
40 import {InputsUtils} from "./services/inputs.utils";
41 import {PropertyCreatorComponent} from "./property-creator/property-creator.component";
42 import { InstanceFeDetails } from "../../../models/instance-fe-details";
43 import { SdcUiComponents } from "sdc-ui/lib/angular";
44 //import { ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service";
45 import { IModalButtonComponent } from "sdc-ui/lib/angular/modals/models/modal-config";
46 import { UnsavedChangesComponent } from "app/ng2/components/ui/forms/unsaved-changes/unsaved-changes.component";
47
48 const SERVICE_SELF_TITLE = "SELF";
49 @Component({
50     templateUrl: './properties-assignment.page.component.html',
51     styleUrls: ['./properties-assignment.page.component.less']
52 })
53 export class PropertiesAssignmentComponent {
54     title = "Properties & Inputs";
55
56     component: ComponentData;
57     componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
58
59     propertiesNavigationData = [];
60     instancesNavigationData = [];
61
62     instanceFePropertiesMap:InstanceFePropertiesMap;
63     inputs: Array<InputFEModel> = [];
64     instances: Array<ComponentInstance|GroupInstance|PolicyInstance> = [];
65     searchQuery: string;
66     propertyStructureHeader: string;
67
68     selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty();
69     selectedInstanceData: ComponentInstance|GroupInstance|PolicyInstance = null;
70     checkedPropertiesCount: number = 0;
71
72     hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
73     hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
74     displayClearSearch = false;
75     searchPropertyName:string;
76     currentMainTab:Tab;
77     isInputsTabSelected:boolean;
78     isPropertiesTabSelected:boolean;
79     isReadonly:boolean;
80     resourceIsReadonly:boolean;
81     loadingInstances:boolean = false;
82     loadingInputs:boolean = false;
83     loadingProperties:boolean = false;
84     changedData:Array<PropertyFEModel|InputFEModel>;
85     hasChangedData:boolean;
86     isValidChangedData:boolean;
87     savingChangedData:boolean;
88     stateChangeStartUnregister:Function;
89     serviceBePropertiesMap: InstanceBePropertiesMap;
90
91     @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
92     @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
93     @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent;
94    
95     constructor(private propertiesService: PropertiesService,
96                 private hierarchyNavService: HierarchyNavService,
97                 private propertiesUtils:PropertiesUtils,
98                 private inputsUtils:InputsUtils,
99                 private componentServiceNg2:ComponentServiceNg2,
100                 private componentInstanceServiceNg2:ComponentInstanceServiceNg2,
101                 @Inject("$stateParams") _stateParams,
102                 @Inject("$scope") private $scope:ng.IScope,
103                 @Inject("$state") private $state:ng.ui.IStateService,
104                 @Inject("Notification") private Notification:any,
105                 private componentModeService:ComponentModeService,
106                 private ModalService:ModalService,
107                 private EventListenerService:EventListenerService,
108                 private ModalServiceSdcUI: SdcUiComponents.ModalService) {
109
110         this.instanceFePropertiesMap = new InstanceFePropertiesMap();
111
112         /* 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
113         than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
114         this.component = _stateParams.component;
115         this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.onCheckout);
116         this.updateViewMode();
117
118         this.changedData = [];
119         this.updateHasChangedData();
120         this.isValidChangedData = true;
121     }
122
123     ngOnInit() {
124         console.log("==>" + this.constructor.name + ": ngOnInit");
125         this.loadingInputs = true;
126         this.loadingInstances = true;
127         this.loadingProperties = true;
128         this.componentServiceNg2
129             .getComponentInputs(this.component)
130             .subscribe(response => {
131                 _.forEach(response.inputs, (input: InputBEModel) => {
132                     const newInput: InputFEModel = new InputFEModel(input);
133                     this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
134                     this.inputs.push(newInput); //only push items that were declared via SDC
135                 });
136                 this.loadingInputs = false;
137
138             });
139         this.componentServiceNg2
140             .getComponentResourcePropertiesData(this.component)
141             .subscribe(response => {
142                 this.instances = [];
143                 this.instances.push(...response.componentInstances);
144                 this.instances.push(...response.groupInstances);
145                 this.instances.push(...response.policies);
146
147                 // add the service self instance to the top of the list.
148                 const serviceInstance = new ComponentInstance();
149                 serviceInstance.name = SERVICE_SELF_TITLE;
150                 serviceInstance.uniqueId = this.component.uniqueId;
151                 this.instances.unshift(serviceInstance);
152
153                 _.forEach(this.instances, (instance) => {
154                     this.instancesNavigationData.push(instance);
155                     this.componentInstanceNamesMap[instance.uniqueId] = <InstanceFeDetails>{name: instance.name, iconClass:instance.iconClass, originArchived:instance.originArchived};
156                 });
157                 this.loadingInstances = false;
158                 if (this.instancesNavigationData[0] == undefined) {
159                     this.loadingProperties = false;
160                 }
161                 this.selectFirstInstanceByDefault();
162             });
163
164         this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
165             // stop if has changed properties
166             if (this.hasChangedData) {
167                 event.preventDefault();
168                 this.showUnsavedChangesAlert().then(() => {
169                     this.$state.go(toState, toParams);
170                 }, () => {});
171             }
172         });
173     };
174
175     ngOnDestroy() {
176         this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
177         this.stateChangeStartUnregister();
178     }
179
180     selectFirstInstanceByDefault = () => {
181         if (this.instancesNavigationData[0] !== undefined) {
182             this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
183         }
184     };
185
186     updateViewMode = () => {
187         this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
188     }
189
190     onCheckout = (component:ComponentData) => {
191         this.component = component;
192         this.updateViewMode();
193     }
194
195     isSelf = ():boolean => {
196         return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
197     }
198
199     getServiceProperties(){
200         this.loadingProperties = false;
201         this.componentServiceNg2
202             .getServiceProperties(this.component)
203             .subscribe(response => {
204                 this.serviceBePropertiesMap = new InstanceBePropertiesMap();
205                 this.serviceBePropertiesMap[this.component.uniqueId] = response;
206                 this.processInstancePropertiesResponse(this.serviceBePropertiesMap, false);
207                 this.loadingProperties = false;
208             }, error => {
209                 this.loadingProperties = false;
210             });
211     }
212
213     onInstanceSelectedUpdate = (instance: ComponentInstance|GroupInstance|PolicyInstance) => {
214         // stop if has changed properties
215         if (this.hasChangedData) {
216             this.showUnsavedChangesAlert().then((resolve)=> {
217                 this.changeSelectedInstance(instance)
218             }, (reject) => {
219             });
220             return;
221         }
222         this.changeSelectedInstance(instance);
223     };
224
225     changeSelectedInstance =  (instance: ComponentInstance|GroupInstance|PolicyInstance) => {
226         this.selectedInstanceData = instance;
227         this.loadingProperties = true;
228         if (instance instanceof ComponentInstance) {
229             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
230             if (this.isInput(instance.originType)) {
231                 this.componentInstanceServiceNg2
232                     .getComponentInstanceInputs(this.component, instance)
233                     .subscribe(response => {
234                         instanceBePropertiesMap[instance.uniqueId] = response;
235                         this.processInstancePropertiesResponse(instanceBePropertiesMap, true);
236                         this.loadingProperties = false;
237                     }, error => {
238                     }); //ignore error
239             } else if (this.isSelf()) {
240                 this.getServiceProperties();
241             } else {
242                 this.componentInstanceServiceNg2
243                     .getComponentInstanceProperties(this.component, instance.uniqueId)
244                     .subscribe(response => {
245                         instanceBePropertiesMap[instance.uniqueId] = response;
246                         this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
247                         this.loadingProperties = false;
248                     }, error => {
249                     }); //ignore error
250             }
251
252             this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
253         } else if (instance instanceof GroupInstance) {
254             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
255             this.componentInstanceServiceNg2
256                 .getComponentGroupInstanceProperties(this.component, this.selectedInstanceData.uniqueId)
257                 .subscribe((response) => {
258                     instanceBePropertiesMap[instance.uniqueId] = response;
259                     this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
260                     this.loadingProperties = false;
261                 });
262         } else if (instance instanceof PolicyInstance) {
263             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
264             this.componentInstanceServiceNg2
265                 .getComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId)
266                 .subscribe((response) => {
267                     instanceBePropertiesMap[instance.uniqueId] = response;
268                     this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
269                     this.loadingProperties = false;
270                 });
271         } else {
272             this.loadingProperties = false;
273         }
274
275         if (this.searchPropertyName) {
276             this.clearSearch();
277         }
278         //clear selected property from the navigation
279         this.selectedFlatProperty = new SimpleFlatProperty();
280         this.propertiesNavigationData = [];
281     };
282
283     /**
284      * Entry point handling response from server
285      */
286     processInstancePropertiesResponse = (instanceBePropertiesMap: InstanceBePropertiesMap, originTypeIsVF: boolean) => {
287         this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap, originTypeIsVF, this.inputs); //create flattened children, disable declared props, and init values
288         this.checkedPropertiesCount = 0;
289     };
290
291
292     /*** VALUE CHANGE EVENTS ***/
293     dataChanged = (item:PropertyFEModel|InputFEModel) => {
294         let itemHasChanged;
295         if (this.isPropertiesTabSelected && item instanceof PropertyFEModel) {
296             itemHasChanged = item.hasValueObjChanged();
297         } else if (this.isInputsTabSelected && item instanceof InputFEModel) {
298             itemHasChanged = item.hasDefaultValueChanged();
299         }
300
301         const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
302         if (itemHasChanged) {
303             if (dataChangedIdx === -1) {
304                 this.changedData.push(item);
305             }
306         } else {
307             if (dataChangedIdx !== -1) {
308                 this.changedData.splice(dataChangedIdx, 1);
309             }
310         }
311
312         if (this.isPropertiesTabSelected) {
313             this.isValidChangedData = this.changedData.every((changedItem) => (<PropertyFEModel>changedItem).valueObjIsValid);
314         } else if (this.isInputsTabSelected) {
315             this.isValidChangedData = this.changedData.every((changedItem) => (<InputFEModel>changedItem).defaultValueObjIsValid);
316         }
317         this.updateHasChangedData();
318     };
319
320
321     /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
322
323     /**
324      * Handle select node in navigation area, and select the row in table
325      */
326     onPropertySelectedUpdate = ($event) => {
327         console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate");
328         this.selectedFlatProperty = $event;
329         let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path);
330         parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path;
331     };
332
333     /**
334      * When user select row in table, this will prepare the hirarchy object for the tree.
335      */
336     selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => {
337         console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name);
338         let property = propertyRowSelectedEvent.propertyModel;
339         let instanceName = propertyRowSelectedEvent.instanceName;
340         this.propertyStructureHeader = null;
341
342         // Build hirarchy tree for the navigation and update propertiesNavigationData with it.
343         if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
344             let simpleFlatProperty:Array<SimpleFlatProperty>;
345             if (property instanceof PropertyFEModel) {
346                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(property, instanceName);
347             } else if (property instanceof DerivedFEProperty) {
348                 // Need to find parent PropertyFEModel
349                 let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => {
350                     return property.propertiesName.indexOf(tmpFeProperty.name)===0;
351                 });
352                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(parentPropertyFEModel, instanceName);
353             }
354             this.propertiesNavigationData = simpleFlatProperty;
355         }
356
357         // Update the header in the navigation tree with property name.
358         this.propertyStructureHeader = (property.propertiesName.split('#'))[0];
359
360         // Set selected property in table
361         this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName);
362         this.hierarchyNavTabs.triggerTabChange('Property Structure');
363     };
364
365
366     selectInstanceRow = ($event) => {//get instance name
367         this.selectedInstanceData =  _.find(this.instancesNavigationData, (instance:ComponentInstance) => {
368             return instance.name == $event;
369         });
370         this.hierarchyNavTabs.triggerTabChange('Composition');
371     };
372
373     tabChanged = (event) => {
374         // stop if has changed properties
375         if (this.hasChangedData) {
376             this.propertyInputTabs.triggerTabChange(this.currentMainTab.title);
377             this.showUnsavedChangesAlert().then((proceed) => {
378                 this.propertyInputTabs.selectTab(this.propertyInputTabs.tabs.find((tab) => tab.title === event.title));
379             }, ()=> {
380             });
381             return;
382         }
383
384         console.log("==>" + this.constructor.name + ": tabChanged " + event);
385         this.currentMainTab = this.propertyInputTabs.tabs.find((tab) => tab.title === event.title);
386         this.isPropertiesTabSelected = this.currentMainTab.title === "Properties";
387         this.isInputsTabSelected = this.currentMainTab.title === "Inputs";
388         this.propertyStructureHeader = null;
389         this.searchQuery = '';
390     };
391
392
393
394     /*** DECLARE PROPERTIES/INPUTS ***/
395     declareProperties = (): void => {
396         console.log("==>" + this.constructor.name + ": declareProperties");
397
398         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
399         let selectedGroupInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
400         let selectedPolicyInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
401         let selectedComponentInstancesInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
402         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
403
404         angular.forEach(instancesIds, (instanceId: string): void => {
405             let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
406             if (selectedInstanceData instanceof ComponentInstance) {
407                 if (!this.isInput(selectedInstanceData.originType)) {
408                     selectedComponentInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
409                 } else {
410                     selectedComponentInstancesInputs[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
411                 }
412             } else if (selectedInstanceData instanceof GroupInstance) {
413                 selectedGroupInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
414             } else if (selectedInstanceData instanceof PolicyInstance) {
415                 selectedPolicyInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
416             }
417         });
418
419         let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties);
420
421         this.componentServiceNg2
422             .createInput(this.component, inputsToCreate, this.isSelf())
423             .subscribe(response => {
424                 this.setInputTabIndication(response.length);
425                 this.checkedPropertiesCount = 0;
426                 _.forEach(response, (input: InputBEModel) => {
427                     let newInput: InputFEModel = new InputFEModel(input);
428                     this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
429                     this.inputs.push(newInput);
430                     this.updatePropertyValueAfterDeclare(newInput);
431                 });
432             }, error => {}); //ignore error
433     };
434
435     saveChangedData = ():Promise<(PropertyBEModel|InputBEModel)[]> => {
436         return new Promise((resolve, reject) => {
437             if (!this.isValidChangedData) {
438                 reject('Changed data is invalid - cannot save!');
439                 return;
440             }
441             if (!this.changedData.length) {
442                 resolve([]);
443                 return;
444             }
445
446             // make request and its handlers
447             let request;
448             let handleSuccess, handleError;
449             if (this.isPropertiesTabSelected) {
450                 const changedProperties: PropertyBEModel[] = this.changedData.map((changedProp) => {
451                     changedProp = <PropertyFEModel>changedProp;
452                     const propBE = new PropertyBEModel(changedProp);
453                     propBE.value = changedProp.getJSONValue();
454                     return propBE;
455                 });
456
457                 if (this.selectedInstanceData instanceof ComponentInstance) {
458                     if (this.isInput(this.selectedInstanceData.originType)) {
459                         request = this.componentInstanceServiceNg2
460                             .updateInstanceInputs(this.component, this.selectedInstanceData.uniqueId, changedProperties);
461                         handleSuccess = (response) => {
462                             // reset each changed property with new value and remove it from changed properties list
463                             response.forEach((resInput) => {
464                                 const changedProp = <PropertyFEModel>this.changedData.shift();
465                                 this.propertiesUtils.resetPropertyValue(changedProp, resInput.value);
466                             });
467                             console.log('updated instance inputs:', response);
468                         };
469                     } else {
470                         if (this.isSelf()) {
471                             request = this.componentServiceNg2.updateServiceProperties(this.component, changedProperties);
472                         } else {
473                             request = this.componentInstanceServiceNg2
474                                 .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
475                         }
476                         handleSuccess = (response) => {
477                             // reset each changed property with new value and remove it from changed properties list
478                             response.forEach((resProp) => {
479                                 const changedProp = <PropertyFEModel>this.changedData.shift();
480                                 this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
481                             });
482                             resolve(response);
483                             console.log("updated instance properties: ", response);
484                         };
485                     }
486                 } else if (this.selectedInstanceData instanceof GroupInstance) {
487                     request = this.componentInstanceServiceNg2
488                         .updateComponentGroupInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
489                     handleSuccess = (response) => {
490                         // reset each changed property with new value and remove it from changed properties list
491                         response.forEach((resProp) => {
492                             const changedProp = <PropertyFEModel>this.changedData.shift();
493                             this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
494                         });
495                         resolve(response);
496                         console.log("updated group instance properties: ", response);
497                     };
498                 } else if (this.selectedInstanceData instanceof PolicyInstance) {
499                     request = this.componentInstanceServiceNg2
500                         .updateComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
501                     handleSuccess = (response) => {
502                         // reset each changed property with new value and remove it from changed properties list
503                         response.forEach((resProp) => {
504                             const changedProp = <PropertyFEModel>this.changedData.shift();
505                             this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
506                         });
507                         resolve(response);
508                         console.log("updated policy instance properties: ", response);
509                     };
510                 }
511             } else if (this.isInputsTabSelected) {
512                 const changedInputs: InputBEModel[] = this.changedData.map((changedInput) => {
513                     changedInput = <InputFEModel>changedInput;
514                     const inputBE = new InputBEModel(changedInput);
515                     inputBE.defaultValue = changedInput.getJSONDefaultValue();
516                     return inputBE;
517                 });
518                 request = this.componentServiceNg2
519                     .updateComponentInputs(this.component, changedInputs);
520                 handleSuccess = (response) => {
521                     // reset each changed property with new value and remove it from changed properties list
522                     response.forEach((resInput) => {
523                         const changedInput = <InputFEModel>this.changedData.shift();
524                         this.inputsUtils.resetInputDefaultValue(changedInput, resInput.defaultValue);
525                     });
526                     console.log("updated the component inputs and got this response: ", response);
527                 }
528             }
529
530             this.savingChangedData = true;
531             request.subscribe(
532                 (response) => {
533                     this.savingChangedData = false;
534                     handleSuccess && handleSuccess(response);
535                     this.updateHasChangedData();
536                     resolve(response);
537                 },
538                 (error) => {
539                     this.savingChangedData = false;
540                     handleError && handleError(error);
541                     this.updateHasChangedData();
542                     reject(error);
543                 }
544             );
545         });
546     };
547
548     reverseChangedData = ():void => {
549         // make reverse item handler
550         let handleReverseItem;
551         if (this.isPropertiesTabSelected) {
552             handleReverseItem = (changedItem) => {
553                 changedItem = <PropertyFEModel>changedItem;
554                 this.propertiesUtils.resetPropertyValue(changedItem, changedItem.value);
555             };
556         } else if (this.isInputsTabSelected) {
557             handleReverseItem = (changedItem) => {
558                 changedItem = <InputFEModel>changedItem;
559                 this.inputsUtils.resetInputDefaultValue(changedItem, changedItem.defaultValue);
560             };
561         }
562
563         this.changedData.forEach(handleReverseItem);
564         this.changedData = [];
565         this.updateHasChangedData();
566     };
567
568     updateHasChangedData = ():boolean => {
569         const curHasChangedData:boolean = (this.changedData.length > 0);
570         if (curHasChangedData !== this.hasChangedData) {
571             this.hasChangedData = curHasChangedData;
572             if(this.hasChangedData) {
573                 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
574             } else {
575                 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
576             }
577         } 
578         return this.hasChangedData;
579     };
580
581     doSaveChangedData = (onSuccessFunction?:Function, onError?:Function):void => {
582         this.saveChangedData().then(
583             () => {
584                 this.Notification.success({
585                     message: 'Successfully saved changes',
586                     title: 'Saved'
587                 });
588                 if(onSuccessFunction) onSuccessFunction();
589             },
590             () => {
591                 this.Notification.error({
592                     message: 'Failed to save changes!',
593                     title: 'Failure'
594                 });
595                 if(onError) onError();
596             }
597         );
598     };
599
600     showUnsavedChangesAlert = ():Promise<any> => {
601         let modalTitle:string;
602         if (this.isPropertiesTabSelected) {
603             modalTitle = `Unsaved properties for ${this.selectedInstanceData.name}`;
604         } else if (this.isInputsTabSelected) {
605             modalTitle = `Unsaved inputs for ${this.component.name}`;
606         }
607
608         return new Promise<any>((resolve, reject) => {
609             const modal = this.ModalServiceSdcUI.openCustomModal(
610                 {
611                     title: modalTitle,
612                     size: 'sm',
613                     type: 'custom',
614                     testId: "id",
615                     
616                     buttons: [
617                         {id: 'cancelButton', text: 'Cancel', type: 'secondary', size: 'xsm', closeModal: true, callback: () => reject()},
618                         {id: 'discardButton', text: 'Discard', type: 'secondary', size: 'xsm', closeModal: true, callback: () => { this.reverseChangedData(); resolve()}},
619                         {id: 'saveButton', text: 'Save', type: 'primary', size: 'xsm', closeModal: true, disabled: !this.isValidChangedData, callback: () => this.doSaveChangedData(resolve, reject)}
620                 ] as IModalButtonComponent[]
621             }, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
622         });
623
624     }
625
626     updatePropertyValueAfterDeclare = (input: InputFEModel) => {
627         if (this.instanceFePropertiesMap[input.instanceUniqueId]) {
628             let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => {
629                 return feProperty.name == input.relatedPropertyName;
630             });
631             let inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined;
632             propertyForUpdatindVal.setAsDeclared(inputPath); //set prop as declared before assigning value
633             this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, inputPath);
634             this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, input.relatedPropertyValue, inputPath);
635         }
636     }
637
638     //used for declare button, to keep count of newly checked properties (and ignore declared properties)
639     updateCheckedPropertyCount = (increment: boolean): void => {
640         this.checkedPropertiesCount += (increment) ? 1 : -1;
641         console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount);
642     };
643
644     setInputTabIndication = (numInputs: number): void => {
645         this.propertyInputTabs.setTabIndication('Inputs', numInputs);
646     };
647
648     resetUnsavedChangesForInput = (input:InputFEModel) => {
649         this.inputsUtils.resetInputDefaultValue(input, input.defaultValue);
650         this.changedData = this.changedData.filter((changedItem) => changedItem.uniqueId !== input.uniqueId);
651         this.updateHasChangedData();
652     }
653
654     deleteInput = (input: InputFEModel) => {
655         //reset any unsaved changes to the input before deleting it
656         this.resetUnsavedChangesForInput(input);
657
658         console.log("==>" + this.constructor.name + ": deleteInput");
659         let inputToDelete = new InputBEModel(input);
660
661         this.componentServiceNg2
662             .deleteInput(this.component, inputToDelete)
663             .subscribe(response => {
664                 this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId);
665
666                 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
667                 this.changeSelectedInstance(this.selectedInstanceData);
668                 // let instanceFeProperties = this.instanceFePropertiesMap[this.getInstanceUniqueId(input.instanceName)];
669
670                 // if (instanceFeProperties) {
671                 //     let propToEnable: PropertyFEModel = instanceFeProperties.find((prop) => {
672                 //         return prop.name == input.propertyName;
673                 //     });
674
675                 //     if (propToEnable) {
676                 //         if (propToEnable.name == response.inputPath) response.inputPath = null;
677                 //         propToEnable.setNonDeclared(response.inputPath);
678                 //         //this.propertiesUtils.resetPropertyValue(propToEnable, newValue, response.inputPath);
679                 //         this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath);
680                 //     }
681                 // }
682             }, error => {}); //ignore error
683     };
684
685     deleteProperty = (property: PropertyFEModel) => {
686         let propertyToDelete = new PropertyFEModel(property);
687         this.loadingProperties = true;
688         let feMap = this.instanceFePropertiesMap;
689         this.componentServiceNg2
690             .deleteServiceProperty(this.component, propertyToDelete)
691             .subscribe(response => {
692                 const props = feMap[this.component.uniqueId];
693                 props.splice(props.findIndex(p => p.uniqueId === response),1);
694                 this.loadingProperties = false;
695             }, error => {
696                 this.loadingProperties = false;
697                 console.error(error);
698             });
699     };
700
701     /*** addProperty ***/
702     addProperty = () => {
703         let modalTitle = 'Add Property';
704         const modal = this.ModalService.createCustomModal(new ModalModel(
705             'sm',
706             modalTitle,
707             null,
708             [
709                 new ButtonModel('Save', 'blue', () => {
710                     modal.instance.dynamicContent.instance.isLoading = true;
711                     const newProperty: PropertyBEModel = modal.instance.dynamicContent.instance.propertyModel;
712                     this.componentServiceNg2.createServiceProperty(this.component, newProperty)
713                         .subscribe(response => {
714                             modal.instance.dynamicContent.instance.isLoading = false;
715                             let newProp: PropertyFEModel = this.propertiesUtils.convertAddPropertyBAToPropertyFE(response);
716                             this.instanceFePropertiesMap[this.component.uniqueId].push(newProp);
717                             modal.instance.close();
718                         }, (error) => {
719                             modal.instance.dynamicContent.instance.isLoading = false;
720                             this.Notification.error({
721                                 message: 'Failed to add property:' + error,
722                                 title: 'Failure'
723                             });
724                         });
725
726                 }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
727                 new ButtonModel('Cancel', 'outline grey', () => {
728                     modal.instance.close();
729                 }),
730             ],
731             null
732         ));
733         this.ModalService.addDynamicContentToModal(modal, PropertyCreatorComponent, {});
734         modal.instance.open();
735     };
736
737     /*** SEARCH RELATED FUNCTIONS ***/
738     searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => {
739         let instanceBePropertiesMap:InstanceBePropertiesMap;
740         this.componentServiceNg2
741             .filterComponentInstanceProperties(this.component, filterData)
742             .subscribe(response => {
743
744                 this.processInstancePropertiesResponse(response, false);
745                 this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree
746                 this.searchPropertyName = filterData.propertyName;//mark in table
747                 this.hierarchyNavTabs.triggerTabChange('Composition');
748                 this.propertiesNavigationData = [];
749                 this.displayClearSearch = true;
750             }, error => {}); //ignore error
751
752     };
753
754     clearSearch = () => {
755         this.instancesNavigationData = this.instances;
756         this.searchPropertyName = "";
757         this.hierarchyPropertiesDisplayOptions.searchText = "";
758         this.displayClearSearch = false;
759         this.advanceSearch.clearAll();
760         this.searchQuery = '';
761     };
762
763     clickOnClearSearch = () => {
764         this.clearSearch();
765         this.selectFirstInstanceByDefault();
766         this.hierarchyNavTabs.triggerTabChange('Composition');
767     };
768
769     private isInput = (instanceType:string):boolean =>{
770         return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
771     }
772
773 }