Change to enable SDC list type input
[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 {DeclareListComponent} from "./declare-list/declare-list.component";
43 import { InstanceFeDetails } from "../../../models/instance-fe-details";
44 import { SdcUiComponents } from "sdc-ui/lib/angular";
45 //import { ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service";
46 import { IModalButtonComponent } from "sdc-ui/lib/angular/modals/models/modal-config";
47 import { UnsavedChangesComponent } from "app/ng2/components/ui/forms/unsaved-changes/unsaved-changes.component";
48 import { DataTypeService } from "app/ng2/services/data-type.service";
49 import { DataTypeModel } from "app/models";
50 import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils";
51 import { PropertyDeclareAPIModel} from "app/models";
52
53 const SERVICE_SELF_TITLE = "SELF";
54 @Component({
55     templateUrl: './properties-assignment.page.component.html',
56     styleUrls: ['./properties-assignment.page.component.less']
57 })
58 export class PropertiesAssignmentComponent {
59     title = "Properties & Inputs";
60
61     component: ComponentData;
62     componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
63
64     propertiesNavigationData = [];
65     instancesNavigationData = [];
66
67     instanceFePropertiesMap:InstanceFePropertiesMap;
68     inputs: Array<InputFEModel> = [];
69     policies: Array<PolicyInstance> = [];
70     instances: Array<ComponentInstance|GroupInstance|PolicyInstance> = [];
71     searchQuery: string;
72     propertyStructureHeader: string;
73
74     selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty();
75     selectedInstanceData: ComponentInstance|GroupInstance|PolicyInstance = null;
76     checkedPropertiesCount: number = 0;
77     checkedChildPropertiesCount: number = 0;
78
79     hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
80     hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
81     displayClearSearch = false;
82     searchPropertyName:string;
83     currentMainTab:Tab;
84     isInputsTabSelected:boolean;
85     isPropertiesTabSelected:boolean;
86     isPoliciesTabSelected:boolean;
87     isReadonly:boolean;
88     resourceIsReadonly:boolean;
89     loadingInstances:boolean = false;
90     loadingInputs:boolean = false;
91     loadingPolicies:boolean = false;
92     loadingProperties:boolean = false;
93     changedData:Array<PropertyFEModel|InputFEModel>;
94     hasChangedData:boolean;
95     isValidChangedData:boolean;
96     savingChangedData:boolean;
97     stateChangeStartUnregister:Function;
98     serviceBePropertiesMap: InstanceBePropertiesMap;
99
100     @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
101     @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
102     @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent;
103    
104     constructor(private propertiesService: PropertiesService,
105                 private hierarchyNavService: HierarchyNavService,
106                 private propertiesUtils:PropertiesUtils,
107                 private inputsUtils:InputsUtils,
108                 private componentServiceNg2:ComponentServiceNg2,
109                 private componentInstanceServiceNg2:ComponentInstanceServiceNg2,
110                 @Inject("$stateParams") _stateParams,
111                 @Inject("$scope") private $scope:ng.IScope,
112                 @Inject("$state") private $state:ng.ui.IStateService,
113                 @Inject("Notification") private Notification:any,
114                 private componentModeService:ComponentModeService,
115                 private ModalService:ModalService,
116                 private EventListenerService:EventListenerService,
117                 private ModalServiceSdcUI: SdcUiComponents.ModalService) {
118
119         this.instanceFePropertiesMap = new InstanceFePropertiesMap();
120
121         /* 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
122         than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
123         this.component = _stateParams.component;
124         this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.onCheckout);
125         this.updateViewMode();
126
127         this.changedData = [];
128         this.updateHasChangedData();
129         this.isValidChangedData = true;
130     }
131
132     ngOnInit() {
133         console.log("==>" + this.constructor.name + ": ngOnInit");
134         this.loadingInputs = true;
135         this.loadingPolicies = true;
136         this.loadingInstances = true;
137         this.loadingProperties = true;
138         this.componentServiceNg2
139             .getComponentInputsWithProperties(this.component)
140             .subscribe(response => {
141                 _.forEach(response.inputs, (input: InputBEModel) => {
142                     const newInput: InputFEModel = new InputFEModel(input);
143                     this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
144                     this.inputs.push(newInput); //only push items that were declared via SDC
145                 });
146                 this.loadingInputs = false;
147
148             });
149         this.componentServiceNg2
150             .getComponentResourcePropertiesData(this.component)
151             .subscribe(response => {
152                 this.loadingPolicies = false;
153                 this.instances = [];
154                 this.instances.push(...response.componentInstances);
155                 this.instances.push(...response.groupInstances);
156
157                 _.forEach(response.policies, (policy: any) => {
158                     const newPolicy: InputFEModel = new InputFEModel(policy);
159                     this.inputsUtils.resetInputDefaultValue(newPolicy, policy.defaultValue);
160                     this.policies.push(policy);
161                 });
162
163                 // add the service self instance to the top of the list.
164                 const serviceInstance = new ComponentInstance();
165                 serviceInstance.name = SERVICE_SELF_TITLE;
166                 serviceInstance.uniqueId = this.component.uniqueId;
167                 this.instances.unshift(serviceInstance);
168
169                 _.forEach(this.instances, (instance) => {
170                     this.instancesNavigationData.push(instance);
171                     this.componentInstanceNamesMap[instance.uniqueId] = <InstanceFeDetails>{name: instance.name, iconClass:instance.iconClass, originArchived:instance.originArchived};
172                 });
173                 this.loadingInstances = false;
174                 if (this.instancesNavigationData[0] == undefined) {
175                     this.loadingProperties = false;
176                 }
177                 this.selectFirstInstanceByDefault();
178             });
179
180         this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
181             // stop if has changed properties
182             if (this.hasChangedData) {
183                 event.preventDefault();
184                 this.showUnsavedChangesAlert().then(() => {
185                     this.$state.go(toState, toParams);
186                 }, () => {});
187             }
188         });
189     };
190
191     ngOnDestroy() {
192         this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
193         this.stateChangeStartUnregister();
194     }
195
196     selectFirstInstanceByDefault = () => {
197         if (this.instancesNavigationData[0] !== undefined) {
198             this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
199         }
200     };
201
202     updateViewMode = () => {
203         this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
204     }
205
206     onCheckout = (component:ComponentData) => {
207         this.component = component;
208         this.updateViewMode();
209     }
210
211     isSelf = ():boolean => {
212         return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
213     }
214
215     getServiceProperties(){
216         this.loadingProperties = false;
217         this.componentServiceNg2
218             .getServiceProperties(this.component)
219             .subscribe(response => {
220                 this.serviceBePropertiesMap = new InstanceBePropertiesMap();
221                 this.serviceBePropertiesMap[this.component.uniqueId] = response;
222                 this.processInstancePropertiesResponse(this.serviceBePropertiesMap, false);
223                 this.loadingProperties = false;
224             }, error => {
225                 this.loadingProperties = false;
226             });
227     }
228
229     onInstanceSelectedUpdate = (instance: ComponentInstance|GroupInstance|PolicyInstance) => {
230         // stop if has changed properties
231         if (this.hasChangedData) {
232             this.showUnsavedChangesAlert().then((resolve)=> {
233                 this.changeSelectedInstance(instance)
234             }, (reject) => {
235             });
236             return;
237         }
238         this.changeSelectedInstance(instance);
239     };
240
241     changeSelectedInstance =  (instance: ComponentInstance|GroupInstance|PolicyInstance) => {
242         this.selectedInstanceData = instance;
243         this.loadingProperties = true;
244         if (instance instanceof ComponentInstance) {
245             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
246             if (this.isInput(instance.originType)) {
247                 this.componentInstanceServiceNg2
248                     .getComponentInstanceInputs(this.component, instance)
249                     .subscribe(response => {
250                         instanceBePropertiesMap[instance.uniqueId] = response;
251                         this.processInstancePropertiesResponse(instanceBePropertiesMap, true);
252                         this.loadingProperties = false;
253                     }, error => {
254                     }); //ignore error
255             } else if (this.isSelf()) {
256                 this.getServiceProperties();
257             } else {
258                 this.componentInstanceServiceNg2
259                     .getComponentInstanceProperties(this.component, instance.uniqueId)
260                     .subscribe(response => {
261                         instanceBePropertiesMap[instance.uniqueId] = response;
262                         this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
263                         this.loadingProperties = false;
264                     }, error => {
265                     }); //ignore error
266             }
267
268             this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
269         } else if (instance instanceof GroupInstance) {
270             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
271             this.componentInstanceServiceNg2
272                 .getComponentGroupInstanceProperties(this.component, this.selectedInstanceData.uniqueId)
273                 .subscribe((response) => {
274                     instanceBePropertiesMap[instance.uniqueId] = response;
275                     this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
276                     this.loadingProperties = false;
277                 });
278         } else if (instance instanceof PolicyInstance) {
279             let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
280             this.componentInstanceServiceNg2
281                 .getComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId)
282                 .subscribe((response) => {
283                     instanceBePropertiesMap[instance.uniqueId] = response;
284                     this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
285                     this.loadingProperties = false;
286                 });
287         } else {
288             this.loadingProperties = false;
289         }
290
291         if (this.searchPropertyName) {
292             this.clearSearch();
293         }
294         //clear selected property from the navigation
295         this.selectedFlatProperty = new SimpleFlatProperty();
296         this.propertiesNavigationData = [];
297     };
298
299     /**
300      * Entry point handling response from server
301      */
302     processInstancePropertiesResponse = (instanceBePropertiesMap: InstanceBePropertiesMap, originTypeIsVF: boolean) => {
303         this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap, originTypeIsVF, this.inputs); //create flattened children, disable declared props, and init values
304         this.checkedPropertiesCount = 0;
305         this.checkedChildPropertiesCount = 0;
306     };
307
308
309     /*** VALUE CHANGE EVENTS ***/
310     dataChanged = (item:PropertyFEModel|InputFEModel) => {
311         let itemHasChanged;
312         if (this.isPropertiesTabSelected && item instanceof PropertyFEModel) {
313             itemHasChanged = item.hasValueObjChanged();
314         } else if (this.isInputsTabSelected && item instanceof InputFEModel) {
315             itemHasChanged = item.hasDefaultValueChanged();
316         } else if (this.isPoliciesTabSelected && item instanceof InputFEModel) {
317             itemHasChanged = item.hasDefaultValueChanged();
318         }
319
320         const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
321         if (itemHasChanged) {
322             if (dataChangedIdx === -1) {
323                 this.changedData.push(item);
324             }
325         } else {
326             if (dataChangedIdx !== -1) {
327                 this.changedData.splice(dataChangedIdx, 1);
328             }
329         }
330
331         if (this.isPropertiesTabSelected) {
332             this.isValidChangedData = this.changedData.every((changedItem) => (<PropertyFEModel>changedItem).valueObjIsValid);
333         } else if (this.isInputsTabSelected || this.isPoliciesTabSelected) {
334             this.isValidChangedData = this.changedData.every((changedItem) => (<InputFEModel>changedItem).defaultValueObjIsValid);
335         }
336         this.updateHasChangedData();
337     };
338
339
340     /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
341
342     /**
343      * Handle select node in navigation area, and select the row in table
344      */
345     onPropertySelectedUpdate = ($event) => {
346         console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate");
347         this.selectedFlatProperty = $event;
348         let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path);
349         parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path;
350     };
351
352     /**
353      * When user select row in table, this will prepare the hirarchy object for the tree.
354      */
355     selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => {
356         console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name);
357         let property = propertyRowSelectedEvent.propertyModel;
358         let instanceName = propertyRowSelectedEvent.instanceName;
359         this.propertyStructureHeader = null;
360
361         // Build hirarchy tree for the navigation and update propertiesNavigationData with it.
362         if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
363             let simpleFlatProperty:Array<SimpleFlatProperty>;
364             if (property instanceof PropertyFEModel) {
365                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(property, instanceName);
366             } else if (property instanceof DerivedFEProperty) {
367                 // Need to find parent PropertyFEModel
368                 let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => {
369                     return property.propertiesName.indexOf(tmpFeProperty.name)===0;
370                 });
371                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(parentPropertyFEModel, instanceName);
372             }
373             this.propertiesNavigationData = simpleFlatProperty;
374         }
375
376         // Update the header in the navigation tree with property name.
377         this.propertyStructureHeader = (property.propertiesName.split('#'))[0];
378
379         // Set selected property in table
380         this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName);
381         this.hierarchyNavTabs.triggerTabChange('Property Structure');
382     };
383
384
385     selectInstanceRow = ($event) => {//get instance name
386         this.selectedInstanceData =  _.find(this.instancesNavigationData, (instance:ComponentInstance) => {
387             return instance.name == $event;
388         });
389         this.hierarchyNavTabs.triggerTabChange('Composition');
390     };
391
392     tabChanged = (event) => {
393         // stop if has changed properties
394         if (this.hasChangedData) {
395             this.propertyInputTabs.triggerTabChange(this.currentMainTab.title);
396             this.showUnsavedChangesAlert().then((proceed) => {
397                 this.propertyInputTabs.selectTab(this.propertyInputTabs.tabs.find((tab) => tab.title === event.title));
398             }, ()=> {
399             });
400             return;
401         }
402
403         console.log("==>" + this.constructor.name + ": tabChanged " + event);
404         this.currentMainTab = this.propertyInputTabs.tabs.find((tab) => tab.title === event.title);
405         this.isPropertiesTabSelected = this.currentMainTab.title === "Properties";
406         this.isInputsTabSelected = this.currentMainTab.title === "Inputs";
407         this.isPoliciesTabSelected = this.currentMainTab.title === "Policies";
408         this.propertyStructureHeader = null;
409         this.searchQuery = '';
410     };
411
412
413
414     /*** DECLARE PROPERTIES/INPUTS ***/
415     declareProperties = (): void => {
416         console.log("==>" + this.constructor.name + ": declareProperties");
417
418         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
419         let selectedGroupInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
420         let selectedPolicyInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
421         let selectedComponentInstancesInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
422         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
423
424         angular.forEach(instancesIds, (instanceId: string): void => {
425             let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
426             if (selectedInstanceData instanceof ComponentInstance) {
427                 if (!this.isInput(selectedInstanceData.originType)) {
428                     // convert Property FE model -> Property BE model, extract only checked
429                     selectedComponentInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
430                 } else {
431                     selectedComponentInstancesInputs[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
432                 }
433             } else if (selectedInstanceData instanceof GroupInstance) {
434                 selectedGroupInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
435             } else if (selectedInstanceData instanceof PolicyInstance) {
436                 selectedPolicyInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
437             }
438         });
439
440         let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties);
441
442         this.componentServiceNg2
443             .createInput(this.component, inputsToCreate, this.isSelf())
444             .subscribe(response => {
445                 this.setInputTabIndication(response.length);
446                 this.checkedPropertiesCount = 0;
447                 this.checkedChildPropertiesCount = 0;
448                 _.forEach(response, (input: InputBEModel) => {
449                     let newInput: InputFEModel = new InputFEModel(input);
450                     this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
451                     this.inputs.push(newInput);
452                     this.updatePropertyValueAfterDeclare(newInput);
453                 });
454             }, error => {}); //ignore error
455     };
456
457     declareListProperties = (): void => {
458         console.log('declareListProperties() - enter');
459
460         // get selected properties
461         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
462         let selectedGroupInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
463         let selectedPolicyInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
464         let selectedComponentInstancesInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
465         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
466         let propertyNameList: Array<string> = [];
467         let insId :string;
468
469         angular.forEach(instancesIds, (instanceId: string): void => {
470             console.log("instanceId="+instanceId);
471             insId = instanceId;
472             let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
473             let checkedProperties: PropertyBEModel[] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
474
475             if (selectedInstanceData instanceof ComponentInstance) {
476                 if (!this.isInput(selectedInstanceData.originType)) {
477                     // convert Property FE model -> Property BE model, extract only checked
478                     selectedComponentInstancesProperties[instanceId] = checkedProperties;
479                 } else {
480                     selectedComponentInstancesInputs[instanceId] = checkedProperties;
481                 }
482             } else if (selectedInstanceData instanceof GroupInstance) {
483                 selectedGroupInstancesProperties[instanceId] = checkedProperties;
484             } else if (selectedInstanceData instanceof PolicyInstance) {
485                 selectedPolicyInstancesProperties[instanceId] = checkedProperties;
486             }
487
488             angular.forEach(checkedProperties, (property: PropertyBEModel) => {
489                 propertyNameList.push(property.name);
490             });
491         });
492
493         let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties);
494
495         let modalTitle = 'Declare Properties as List Input';
496         const modal = this.ModalService.createCustomModal(new ModalModel(
497             'sm', /* size */
498             modalTitle, /* title */
499             null, /* content */
500             [ /* buttons */
501                 new ButtonModel(
502                     'Save', /* text */
503                     'blue', /* css class */
504                     () => { /* callback */
505                         let content:any = modal.instance.dynamicContent.instance;
506
507                         /* listInput */
508                         let reglistInput: InstanceBePropertiesMap = new InstanceBePropertiesMap();
509                         let typelist: any = PROPERTY_TYPES.LIST;
510                         let uniID: any = insId;
511                         let boolfalse: any = false;
512                         let schem :any = {
513                             "empty": boolfalse,
514                             "property": {
515                                 "type": content.propertyModel.simpleType,
516                                 "required": boolfalse
517                             }
518                         }
519                         let schemaProp :any = {
520                             "type": content.propertyModel.simpleType,
521                             "required": boolfalse
522                         }
523
524                         reglistInput.description = content.propertyModel.description;
525                         reglistInput.name = content.propertyModel.name;
526                         reglistInput.type = typelist;
527                         reglistInput.schemaType = content.propertyModel.simpleType;
528                         reglistInput.instanceUniqueId = uniID;
529                         reglistInput.uniqueId = uniID;
530                         reglistInput.required =boolfalse;
531                         reglistInput.schema = schem;
532                         reglistInput.schemaProperty = schemaProp;
533
534                         let input = {
535                             componentInstInputsMap: content.inputsToCreate,
536                             listInput: reglistInput
537                         };
538                         console.log("save button clicked. input=", input);
539
540                         this.componentServiceNg2
541                         .createListInput(this.component, input, this.isSelf())
542                         .subscribe(response => {
543                             this.setInputTabIndication(response.length);
544                             this.checkedPropertiesCount = 0;
545                             this.checkedChildPropertiesCount = 0;
546                             _.forEach(response, (input: InputBEModel) => {
547                                 let newInput: InputFEModel = new InputFEModel(input);
548                                 this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
549                                 this.inputs.push(newInput);
550                                 // create list input does not return updated properties info, so need to reload
551                                 //this.updatePropertyValueAfterDeclare(newInput);
552                                 // Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
553                                 this.changeSelectedInstance(this.selectedInstanceData);
554
555                                 modal.instance.close();
556                             });
557                         }, error => {}); //ignore error
558             
559                     }
560                     /*, getDisabled: function */
561                 ),
562                 new ButtonModel('Cancel', 'outline grey', () => {
563                     modal.instance.close();
564                 }),
565             ],
566             null /* type */
567         ));
568         // 3rd arg is passed to DeclareListComponent instance
569         this.ModalService.addDynamicContentToModal(modal, DeclareListComponent, {properties: inputsToCreate, propertyNameList: propertyNameList});
570         modal.instance.open();
571         console.log('declareListProperties() - leave');
572     };
573
574     /*** DECLARE PROPERTIES/POLICIES ***/
575     declarePropertiesToPolicies = (): void => {
576         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
577         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
578
579         angular.forEach(instancesIds, (instanceId: string): void => {
580             let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
581             if (selectedInstanceData instanceof ComponentInstance) {
582                 if (!this.isInput(selectedInstanceData.originType)) {
583                     selectedComponentInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
584                 }
585             }
586         });
587
588         let policiesToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(null, selectedComponentInstancesProperties, null, null);
589         this.loadingPolicies = true;
590
591         this.componentServiceNg2
592             .createPolicy(this.component, policiesToCreate, this.isSelf())
593             .subscribe(response => {
594                 this.setPolicyTabIndication(response.length);
595                 this.checkedPropertiesCount = 0;
596                 this.displayPoliciesAsDeclared(response);
597                 this.loadingPolicies = false;
598             }); //ignore error
599
600     };
601
602     displayPoliciesAsDeclared = (policies) => {
603         _.forEach(policies, (policy: any) => {
604             let newPolicy: InputFEModel = new InputFEModel(policy);
605             this.inputsUtils.resetInputDefaultValue(newPolicy, policy.defaultValue);
606             newPolicy.relatedPropertyName = policy.name;
607             newPolicy.relatedPropertyValue = policy.value;
608             this.updatePropertyValueAfterDeclare(newPolicy);
609             this.policies.push(policy);
610         });
611     };
612
613
614     saveChangedData = ():Promise<(PropertyBEModel|InputBEModel)[]> => {
615         return new Promise((resolve, reject) => {
616             if (!this.isValidChangedData) {
617                 reject('Changed data is invalid - cannot save!');
618                 return;
619             }
620             if (!this.changedData.length) {
621                 resolve([]);
622                 return;
623             }
624
625             // make request and its handlers
626             let request;
627             let handleSuccess, handleError;
628             if (this.isPropertiesTabSelected) {
629                 const changedProperties: PropertyBEModel[] = this.changedData.map((changedProp) => {
630                     changedProp = <PropertyFEModel>changedProp;
631                     const propBE = new PropertyBEModel(changedProp);
632                     propBE.value = changedProp.getJSONValue();
633                     return propBE;
634                 });
635
636                 if (this.selectedInstanceData instanceof ComponentInstance) {
637                     if (this.isInput(this.selectedInstanceData.originType)) {
638                         request = this.componentInstanceServiceNg2
639                             .updateInstanceInputs(this.component, this.selectedInstanceData.uniqueId, changedProperties);
640                         handleSuccess = (response) => {
641                             // reset each changed property with new value and remove it from changed properties list
642                             response.forEach((resInput) => {
643                                 const changedProp = <PropertyFEModel>this.changedData.shift();
644                                 this.propertiesUtils.resetPropertyValue(changedProp, resInput.value);
645                             });
646                             console.log('updated instance inputs:', response);
647                         };
648                     } else {
649                         if (this.isSelf()) {
650                             request = this.componentServiceNg2.updateServiceProperties(this.component, _.map(changedProperties, cp => {
651                                 delete cp.constraints;
652                                 return cp;
653                             }));
654                         } else {
655                             request = this.componentInstanceServiceNg2
656                                 .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
657                         }
658                         handleSuccess = (response) => {
659                             // reset each changed property with new value and remove it from changed properties list
660                             response.forEach((resProp) => {
661                                 const changedProp = <PropertyFEModel>_.find(this.changedData, changedDataObject => changedDataObject.uniqueId === resProp.uniqueId);
662                                 this.changedData = _.filter(this.changedData, changedDataObject => changedDataObject.uniqueId !== resProp.uniqueId);
663                                 this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
664                             });
665                             resolve(response);
666                             console.log("updated instance properties: ", response);
667                         };
668                     }
669                 } else if (this.selectedInstanceData instanceof GroupInstance) {
670                     request = this.componentInstanceServiceNg2
671                         .updateComponentGroupInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
672                     handleSuccess = (response) => {
673                         // reset each changed property with new value and remove it from changed properties list
674                         response.forEach((resProp) => {
675                             const changedProp = <PropertyFEModel>this.changedData.shift();
676                             this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
677                         });
678                         resolve(response);
679                         console.log("updated group instance properties: ", response);
680                     };
681                 } else if (this.selectedInstanceData instanceof PolicyInstance) {
682                     request = this.componentInstanceServiceNg2
683                         .updateComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties);
684                     handleSuccess = (response) => {
685                         // reset each changed property with new value and remove it from changed properties list
686                         response.forEach((resProp) => {
687                             const changedProp = <PropertyFEModel>this.changedData.shift();
688                             this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
689                         });
690                         resolve(response);
691                         console.log("updated policy instance properties: ", response);
692                     };
693                 }
694             } else if (this.isInputsTabSelected) {
695                 const changedInputs: InputBEModel[] = this.changedData.map((changedInput) => {
696                     changedInput = <InputFEModel>changedInput;
697                     const inputBE = new InputBEModel(changedInput);
698                     inputBE.defaultValue = changedInput.getJSONDefaultValue();
699                     return inputBE;
700                 });
701                 request = this.componentServiceNg2
702                     .updateComponentInputs(this.component, changedInputs);
703                 handleSuccess = (response) => {
704                     // reset each changed property with new value and remove it from changed properties list
705                     response.forEach((resInput) => {
706                         const changedInput = <InputFEModel>this.changedData.shift();
707                         this.inputsUtils.resetInputDefaultValue(changedInput, resInput.defaultValue);
708                     });
709                     console.log("updated the component inputs and got this response: ", response);
710                 }
711             }
712
713             this.savingChangedData = true;
714             request.subscribe(
715                 (response) => {
716                     this.savingChangedData = false;
717                     handleSuccess && handleSuccess(response);
718                     this.updateHasChangedData();
719                     resolve(response);
720                 },
721                 (error) => {
722                     this.savingChangedData = false;
723                     handleError && handleError(error);
724                     this.updateHasChangedData();
725                     reject(error);
726                 }
727             );
728         });
729     };
730
731     reverseChangedData = ():void => {
732         // make reverse item handler
733         let handleReverseItem;
734         if (this.isPropertiesTabSelected) {
735             handleReverseItem = (changedItem) => {
736                 changedItem = <PropertyFEModel>changedItem;
737                 this.propertiesUtils.resetPropertyValue(changedItem, changedItem.value);
738                 this.checkedPropertiesCount = 0;
739                 this.checkedChildPropertiesCount = 0;
740             };
741         } else if (this.isInputsTabSelected) {
742             handleReverseItem = (changedItem) => {
743                 changedItem = <InputFEModel>changedItem;
744                 this.inputsUtils.resetInputDefaultValue(changedItem, changedItem.defaultValue);
745             };
746         }
747
748         this.changedData.forEach(handleReverseItem);
749         this.changedData = [];
750         this.updateHasChangedData();
751     };
752
753     updateHasChangedData = ():boolean => {
754         const curHasChangedData:boolean = (this.changedData.length > 0);
755         if (curHasChangedData !== this.hasChangedData) {
756             this.hasChangedData = curHasChangedData;
757             if(this.hasChangedData) {
758                 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
759             } else {
760                 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
761             }
762         } 
763         return this.hasChangedData;
764     };
765
766     doSaveChangedData = (onSuccessFunction?:Function, onError?:Function):void => {
767         this.saveChangedData().then(
768             () => {
769                 this.Notification.success({
770                     message: 'Successfully saved changes',
771                     title: 'Saved'
772                 });
773                 if(onSuccessFunction) onSuccessFunction();
774                 if(this.isPropertiesTabSelected){
775                     this.checkedPropertiesCount = 0;
776                     this.checkedChildPropertiesCount = 0;
777                 }
778             },
779             () => {
780                 this.Notification.error({
781                     message: 'Failed to save changes!',
782                     title: 'Failure'
783                 });
784                 if(onError) onError();
785             }
786         );
787     };
788
789     showUnsavedChangesAlert = ():Promise<any> => {
790         let modalTitle:string;
791         if (this.isPropertiesTabSelected) {
792             modalTitle = `Unsaved properties for ${this.selectedInstanceData.name}`;
793         } else if (this.isInputsTabSelected) {
794             modalTitle = `Unsaved inputs for ${this.component.name}`;
795         }
796
797         return new Promise<any>((resolve, reject) => {
798             const modal = this.ModalServiceSdcUI.openCustomModal(
799                 {
800                     title: modalTitle,
801                     size: 'sm',
802                     type: 'custom',
803                     testId: "id",
804                     
805                     buttons: [
806                         {id: 'cancelButton', text: 'Cancel', type: 'secondary', size: 'xsm', closeModal: true, callback: () => reject()},
807                         {id: 'discardButton', text: 'Discard', type: 'secondary', size: 'xsm', closeModal: true, callback: () => { this.reverseChangedData(); resolve()}},
808                         {id: 'saveButton', text: 'Save', type: 'primary', size: 'xsm', closeModal: true, disabled: !this.isValidChangedData, callback: () => this.doSaveChangedData(resolve, reject)}
809                 ] as IModalButtonComponent[]
810             }, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
811         });
812
813     }
814
815     updatePropertyValueAfterDeclare = (input: InputFEModel) => {
816         if (this.instanceFePropertiesMap[input.instanceUniqueId]) {
817             let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => {
818                 return feProperty.name == input.relatedPropertyName;
819             });
820             let inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined;
821             propertyForUpdatindVal.setAsDeclared(inputPath); //set prop as declared before assigning value
822             this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, inputPath);
823             this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, input.relatedPropertyValue, inputPath);
824         }
825     }
826
827     //used for declare button, to keep count of newly checked properties (and ignore declared properties)
828     updateCheckedPropertyCount = (increment: boolean): void => {
829         this.checkedPropertiesCount += (increment) ? 1 : -1;
830         console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount);
831     };
832
833     updateCheckedChildPropertyCount = (increment: boolean): void => {
834         this.checkedChildPropertiesCount += (increment) ? 1 : -1;
835     };
836
837     setInputTabIndication = (numInputs: number): void => {
838         this.propertyInputTabs.setTabIndication('Inputs', numInputs);
839     };
840
841     setPolicyTabIndication = (numPolicies: number): void => {
842         this.propertyInputTabs.setTabIndication('Policies', numPolicies);
843     };
844
845     resetUnsavedChangesForInput = (input:InputFEModel) => {
846         this.inputsUtils.resetInputDefaultValue(input, input.defaultValue);
847         this.changedData = this.changedData.filter((changedItem) => changedItem.uniqueId !== input.uniqueId);
848         this.updateHasChangedData();
849     }
850
851     deleteInput = (input: InputFEModel) => {
852         //reset any unsaved changes to the input before deleting it
853         this.resetUnsavedChangesForInput(input);
854
855         console.log("==>" + this.constructor.name + ": deleteInput");
856         let inputToDelete = new InputBEModel(input);
857
858         this.componentServiceNg2
859             .deleteInput(this.component, inputToDelete)
860             .subscribe(response => {
861                 this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId);
862
863                 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
864                 this.changeSelectedInstance(this.selectedInstanceData);
865                 // let instanceFeProperties = this.instanceFePropertiesMap[this.getInstanceUniqueId(input.instanceName)];
866
867                 // if (instanceFeProperties) {
868                 //     let propToEnable: PropertyFEModel = instanceFeProperties.find((prop) => {
869                 //         return prop.name == input.propertyName;
870                 //     });
871
872                 //     if (propToEnable) {
873                 //         if (propToEnable.name == response.inputPath) response.inputPath = null;
874                 //         propToEnable.setNonDeclared(response.inputPath);
875                 //         //this.propertiesUtils.resetPropertyValue(propToEnable, newValue, response.inputPath);
876                 //         this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath);
877                 //     }
878                 // }
879             }, error => {}); //ignore error
880     };
881
882     deletePolicy = (policy: PolicyInstance) => {
883         this.loadingPolicies = true;
884         this.componentServiceNg2
885             .deletePolicy(this.component, policy)
886             .subscribe(response => {
887                 this.policies = this.policies.filter(policy => policy.uniqueId !== response.uniqueId);
888                 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
889                 this.changeSelectedInstance(this.selectedInstanceData);
890                 this.loadingPolicies = false;
891             });
892     };
893
894     deleteProperty = (property: PropertyFEModel) => {
895         let propertyToDelete = new PropertyFEModel(property);
896         this.loadingProperties = true;
897         let feMap = this.instanceFePropertiesMap;
898         this.componentServiceNg2
899             .deleteServiceProperty(this.component, propertyToDelete)
900             .subscribe(response => {
901                 const props = feMap[this.component.uniqueId];
902                 props.splice(props.findIndex(p => p.uniqueId === response),1);
903                 this.loadingProperties = false;
904             }, error => {
905                 this.loadingProperties = false;
906                 console.error(error);
907             });
908     };
909
910     /*** addProperty ***/
911     addProperty = () => {
912         let modalTitle = 'Add Property';
913         const modal = this.ModalService.createCustomModal(new ModalModel(
914             'sm',
915             modalTitle,
916             null,
917             [
918                 new ButtonModel('Save', 'blue', () => {
919                     modal.instance.dynamicContent.instance.isLoading = true;
920                     const newProperty: PropertyBEModel = modal.instance.dynamicContent.instance.propertyModel;
921                     this.componentServiceNg2.createServiceProperty(this.component, newProperty)
922                         .subscribe(response => {
923                             modal.instance.dynamicContent.instance.isLoading = false;
924                             let newProp: PropertyFEModel = this.propertiesUtils.convertAddPropertyBAToPropertyFE(response);
925                             this.instanceFePropertiesMap[this.component.uniqueId].push(newProp);
926                             modal.instance.close();
927                         }, (error) => {
928                             modal.instance.dynamicContent.instance.isLoading = false;
929                             this.Notification.error({
930                                 message: 'Failed to add property:' + error,
931                                 title: 'Failure'
932                             });
933                         });
934
935                 }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
936                 new ButtonModel('Cancel', 'outline grey', () => {
937                     modal.instance.close();
938                 }),
939             ],
940             null
941         ));
942         this.ModalService.addDynamicContentToModal(modal, PropertyCreatorComponent, {});
943         modal.instance.open();
944     };
945
946     /*** SEARCH RELATED FUNCTIONS ***/
947     searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => {
948         let instanceBePropertiesMap:InstanceBePropertiesMap;
949         this.componentServiceNg2
950             .filterComponentInstanceProperties(this.component, filterData)
951             .subscribe(response => {
952
953                 this.processInstancePropertiesResponse(response, false);
954                 this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree
955                 this.searchPropertyName = filterData.propertyName;//mark in table
956                 this.hierarchyNavTabs.triggerTabChange('Composition');
957                 this.propertiesNavigationData = [];
958                 this.displayClearSearch = true;
959             }, error => {}); //ignore error
960
961     };
962
963     clearSearch = () => {
964         this.instancesNavigationData = this.instances;
965         this.searchPropertyName = "";
966         this.hierarchyPropertiesDisplayOptions.searchText = "";
967         this.displayClearSearch = false;
968         this.advanceSearch.clearAll();
969         this.searchQuery = '';
970     };
971
972     clickOnClearSearch = () => {
973         this.clearSearch();
974         this.selectFirstInstanceByDefault();
975         this.hierarchyNavTabs.triggerTabChange('Composition');
976     };
977
978     private isInput = (instanceType:string):boolean =>{
979         return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
980     }
981
982 }