2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
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, PropertyBEModel, DerivedFEProperty, ResourceInstance, 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";
43 templateUrl: './properties-assignment.page.component.html',
44 styleUrls: ['./properties-assignment.page.component.less']
46 export class PropertiesAssignmentComponent {
47 title = "Properties & Inputs";
49 component: ComponentData;
50 componentInstanceNamesMap: Map<string, string> = new Map<string, string>();//instanceUniqueId, name
52 propertiesNavigationData = [];
53 instancesNavigationData = [];
55 instanceFePropertiesMap:InstanceFePropertiesMap;
56 inputs: Array<InputFEModel> = [];
57 instances: Array<ComponentInstance> = [];
59 propertyStructureHeader: string;
61 selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty();
62 selectedInstanceType: string;
63 selectedInstanceData: ComponentInstance = new ComponentInstance();
64 checkedPropertiesCount: number = 0;
66 hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
67 hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name');
68 displayClearSearch = false;
69 searchPropertyName:string;
71 isInputsTabSelected:boolean;
72 isPropertiesTabSelected:boolean;
74 loadingInstances:boolean = false;
75 loadingInputs:boolean = false;
76 loadingProperties:boolean = false;
77 changedData:Array<PropertyFEModel|InputFEModel>;
78 hasChangedData:boolean;
79 isValidChangedData:boolean;
80 savingChangedData:boolean;
81 stateChangeStartUnregister:Function;
83 @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
84 @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
85 @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent;
86 @ViewChild('saveChangedDataModalContentTemplate') saveChangedDataModalContentTemplateRef: TemplateRef<void>;
88 constructor(private propertiesService: PropertiesService,
89 private hierarchyNavService: HierarchyNavService,
90 private propertiesUtils:PropertiesUtils,
91 private inputsUtils:InputsUtils,
92 private componentServiceNg2:ComponentServiceNg2,
93 private componentInstanceServiceNg2:ComponentInstanceServiceNg2,
94 @Inject("$stateParams") _stateParams,
95 @Inject("$scope") private $scope:ng.IScope,
96 @Inject("$state") private $state:ng.ui.IStateService,
97 @Inject("Notification") private Notification:any,
98 private componentModeService:ComponentModeService,
99 private ModalService:ModalService,
100 private EventListenerService:EventListenerService) {
102 this.instanceFePropertiesMap = new InstanceFePropertiesMap();
104 /* 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
105 than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
106 this.component = _stateParams.component;
107 this.EventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, this.onCheckout);
108 this.updateViewMode();
110 this.changedData = [];
111 this.updateHasChangedData();
112 this.isValidChangedData = true;
116 console.log("==>" + this.constructor.name + ": ngOnInit");
117 this.loadingInputs = true;
118 this.loadingInstances = true;
119 this.loadingProperties = true;
120 this.componentServiceNg2
121 .getComponentInputs(this.component)
122 .subscribe(response => {
123 _.forEach(response.inputs, (input: InputBEModel) => {
124 const newInput: InputFEModel = new InputFEModel(input);
125 this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
126 this.inputs.push(newInput); //only push items that were declared via SDC
128 this.loadingInputs = false;
130 }, error => {}); //ignore error
131 this.componentServiceNg2
132 .getComponentResourceInstances(this.component)
133 .subscribe(response => {
134 this.instances = response.componentInstances;
136 _.forEach(this.instances, (instance) => {
137 this.instancesNavigationData.push(instance);
138 this.componentInstanceNamesMap[instance.uniqueId] = instance.name;
140 this.loadingInstances = false;
141 if (this.instancesNavigationData[0] == undefined) {
142 this.loadingProperties = false;
144 this.selectFirstInstanceByDefault();
145 }, error => {}); //ignore error
147 this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
148 // stop if has changed properties
149 if (this.hasChangedData) {
150 event.preventDefault();
151 this.openChangedDataModal().then((proceed) => {
153 this.$state.go(toState, toParams);
161 this.EventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT);
162 this.stateChangeStartUnregister();
165 selectFirstInstanceByDefault = () => {
166 if (this.instancesNavigationData[0] !== undefined) {
167 this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
171 updateViewMode = () => {
172 this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
175 onCheckout = (component:ComponentData) => {
176 this.component = component;
177 this.updateViewMode();
181 onInstanceSelectedUpdate = (resourceInstance: ResourceInstance) => {
182 console.log("==>" + this.constructor.name + ": onInstanceSelectedUpdate");
184 // stop if has changed properties
185 if (this.hasChangedData) {
186 this.openChangedDataModal().then((proceed) => {
188 this.onInstanceSelectedUpdate(resourceInstance);
194 let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
195 this.selectedInstanceData = resourceInstance;
196 this.selectedInstanceType = resourceInstance.originType;
198 this.loadingProperties = true;
199 if (this.isInput(resourceInstance.originType)) {
200 this.componentInstanceServiceNg2
201 .getComponentInstanceInputs(this.component, resourceInstance)
202 .subscribe(response => {
203 instanceBePropertiesMap[resourceInstance.uniqueId] = response;
204 this.processInstancePropertiesResponse(instanceBePropertiesMap, true);
205 this.loadingProperties = false;
210 this.componentInstanceServiceNg2
211 .getComponentInstanceProperties(this.component, resourceInstance.uniqueId)
212 .subscribe(response => {
213 instanceBePropertiesMap[resourceInstance.uniqueId] = response;
214 this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
215 this.loadingProperties = false;
220 if (resourceInstance.componentName === "vnfConfiguration") {
221 this.isReadonly = true;
224 if (this.searchPropertyName) {
227 //clear selected property from the navigation
228 this.selectedFlatProperty = new SimpleFlatProperty();
229 this.propertiesNavigationData = [];
233 * Entry point handling response from server
235 processInstancePropertiesResponse = (instanceBePropertiesMap: InstanceBePropertiesMap, originTypeIsVF: boolean) => {
236 this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap, originTypeIsVF, this.inputs); //create flattened children, disable declared props, and init values
237 this.checkedPropertiesCount = 0;
241 /*** VALUE CHANGE EVENTS ***/
242 dataChanged = (item:PropertyFEModel|InputFEModel) => {
244 if (this.isPropertiesTabSelected && item instanceof PropertyFEModel) {
245 itemHasChanged = item.hasValueObjChanged();
246 } else if (this.isInputsTabSelected && item instanceof InputFEModel) {
247 itemHasChanged = item.hasDefaultValueChanged();
250 const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
251 if (itemHasChanged) {
252 if (dataChangedIdx === -1) {
253 this.changedData.push(item);
256 if (dataChangedIdx !== -1) {
257 this.changedData.splice(dataChangedIdx, 1);
261 if (this.isPropertiesTabSelected) {
262 this.isValidChangedData = this.changedData.every((changedItem) => (<PropertyFEModel>changedItem).valueObjIsValid);
263 } else if (this.isInputsTabSelected) {
264 this.isValidChangedData = this.changedData.every((changedItem) => (<InputFEModel>changedItem).defaultValueObjIsValid);
266 this.updateHasChangedData();
270 /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
273 * Handle select node in navigation area, and select the row in table
275 onPropertySelectedUpdate = ($event) => {
276 console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate");
277 this.selectedFlatProperty = $event;
278 let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path);
279 parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path;
283 * When user select row in table, this will prepare the hirarchy object for the tree.
285 selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => {
286 console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name);
287 let property = propertyRowSelectedEvent.propertyModel;
288 let instanceName = propertyRowSelectedEvent.instanceName;
289 this.propertyStructureHeader = null;
291 // Build hirarchy tree for the navigation and update propertiesNavigationData with it.
292 if(this.selectedInstanceData.originType !== ResourceType.VF) {
293 let simpleFlatProperty:Array<SimpleFlatProperty>;
294 if (property instanceof PropertyFEModel) {
295 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(property, instanceName);
296 } else if (property instanceof DerivedFEProperty) {
297 // Need to find parent PropertyFEModel
298 let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => {
299 return property.propertiesName.indexOf(tmpFeProperty.name)===0;
301 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(parentPropertyFEModel, instanceName);
303 this.propertiesNavigationData = simpleFlatProperty;
306 // Update the header in the navigation tree with property name.
307 this.propertyStructureHeader = (property.propertiesName.split('#'))[0];
309 // Set selected property in table
310 this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName);
311 this.hierarchyNavTabs.triggerTabChange('Property Structure');
315 selectInstanceRow = ($event) => {//get instance name
316 this.selectedInstanceData = _.find(this.instancesNavigationData, (instance:ComponentInstance) => {
317 return instance.name == $event;
319 this.hierarchyNavTabs.triggerTabChange('Composition');
322 tabChanged = (event) => {
323 // stop if has changed properties
324 if (this.hasChangedData) {
325 this.openChangedDataModal().then((proceed) => {
327 this.propertyInputTabs.selectTab(this.propertyInputTabs.tabs.find((tab) => tab.title === event.title));
331 // return to show the current tab
332 this.propertyInputTabs.triggerTabChange(this.currentMainTab.title);
336 console.log("==>" + this.constructor.name + ": tabChanged " + event);
337 this.currentMainTab = this.propertyInputTabs.tabs.find((tab) => tab.title === event.title);
338 this.isPropertiesTabSelected = this.currentMainTab.title === "Properties";
339 this.isInputsTabSelected = this.currentMainTab.title === "Inputs";
340 this.propertyStructureHeader = null;
341 this.searchQuery = '';
346 /*** DECLARE PROPERTIES/INPUTS ***/
347 declareProperties = (): void => {
348 console.log("==>" + this.constructor.name + ": declareProperties");
350 let selectedProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
351 let selectedInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
352 let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
354 angular.forEach(instancesIds, (instanceId: string): void => {
355 let selectedInstanceData: ResourceInstance = this.instances.find(instance => instance.uniqueId == instanceId);
356 let originType: string = (selectedInstanceData) ? selectedInstanceData.originType : this.selectedInstanceType;
357 if (!this.isInput(originType)) {
358 selectedProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
360 selectedInputs[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
364 let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedInputs, selectedProperties);
366 this.componentServiceNg2
367 .createInput(this.component, inputsToCreate)
368 .subscribe(response => {
369 this.setInputTabIndication(response.length);
370 this.checkedPropertiesCount = 0;
371 _.forEach(response, (input: InputBEModel) => {
372 let newInput: InputFEModel = new InputFEModel(input);
373 this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
374 this.inputs.push(newInput);
375 this.updatePropertyValueAfterDeclare(newInput);
377 }, error => {}); //ignore error
380 saveChangedData = ():Promise<(PropertyBEModel|InputBEModel)[]> => {
381 return new Promise((resolve, reject) => {
382 if (!this.isValidChangedData) {
383 reject('Changed data is invalid - cannot save!');
386 if (!this.changedData.length) {
391 // make request and its handlers
393 let handleSuccess, handleError;
394 if (this.isPropertiesTabSelected) {
395 const changedProperties: PropertyBEModel[] = this.changedData.map((changedProp) => {
396 changedProp = <PropertyFEModel>changedProp;
397 const propBE = new PropertyBEModel(changedProp);
398 propBE.value = changedProp.getJSONValue();
402 if (this.isInput(this.selectedInstanceData.originType)) {
403 request = this.componentInstanceServiceNg2
404 .updateInstanceInputs(this.component, this.selectedInstanceData.uniqueId, changedProperties);
405 handleSuccess = (response) => {
406 // reset each changed property with new value and remove it from changed properties list
407 response.forEach((resInput) => {
408 const changedProp = <PropertyFEModel>this.changedData.shift();
409 this.propertiesUtils.resetPropertyValue(changedProp, resInput.value);
411 console.log('updated instance inputs:', response);
414 request = this.componentInstanceServiceNg2
415 .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties)
416 handleSuccess = (response) => {
417 // reset each changed property with new value and remove it from changed properties list
418 response.forEach((resProp) => {
419 const changedProp = <PropertyFEModel>this.changedData.shift();
420 this.propertiesUtils.resetPropertyValue(changedProp, resProp.value);
423 console.log("updated instance properties: ", response);
426 } else if (this.isInputsTabSelected) {
427 const changedInputs: InputBEModel[] = this.changedData.map((changedInput) => {
428 changedInput = <InputFEModel>changedInput;
429 const inputBE = new InputBEModel(changedInput);
430 inputBE.defaultValue = changedInput.getJSONDefaultValue();
433 request = this.componentServiceNg2
434 .updateComponentInputs(this.component, changedInputs);
435 handleSuccess = (response) => {
436 // reset each changed property with new value and remove it from changed properties list
437 response.forEach((resInput) => {
438 const changedInput = <InputFEModel>this.changedData.shift();
439 this.inputsUtils.resetInputDefaultValue(changedInput, resInput.defaultValue);
441 console.log("updated the component inputs and got this response: ", response);
445 this.savingChangedData = true;
448 this.savingChangedData = false;
449 handleSuccess && handleSuccess(response);
450 this.updateHasChangedData();
454 this.savingChangedData = false;
455 handleError && handleError(error);
456 this.updateHasChangedData();
463 reverseChangedData = ():void => {
464 // make reverse item handler
465 let handleReverseItem;
466 if (this.isPropertiesTabSelected) {
467 handleReverseItem = (changedItem) => {
468 changedItem = <PropertyFEModel>changedItem;
469 this.propertiesUtils.resetPropertyValue(changedItem, changedItem.value);
471 } else if (this.isInputsTabSelected) {
472 handleReverseItem = (changedItem) => {
473 changedItem = <InputFEModel>changedItem;
474 this.inputsUtils.resetInputDefaultValue(changedItem, changedItem.defaultValue);
478 this.changedData.forEach(handleReverseItem);
479 this.changedData = [];
480 this.updateHasChangedData();
483 updateHasChangedData = ():boolean => {
484 const curHasChangedData:boolean = (this.changedData.length > 0);
485 if (curHasChangedData !== this.hasChangedData) {
486 this.hasChangedData = curHasChangedData;
487 this.$scope.$emit('setWorkspaceTopBarActive', !this.hasChangedData);
489 return this.hasChangedData;
492 doSaveChangedData = ():void => {
493 this.saveChangedData().then(
495 this.Notification.success({
496 message: 'Successfully saved changes',
501 this.Notification.error({
502 message: 'Failed to save changes!',
509 openChangedDataModal = ():Promise<boolean> => {
511 if (this.isPropertiesTabSelected) {
512 modalTitle = `Unsaved properties for ${this.selectedInstanceData.name}`;
513 } else if (this.isInputsTabSelected) {
514 modalTitle = `Unsaved inputs for ${this.component.name}`;
517 return new Promise<boolean>((resolve) => {
518 const modal = this.ModalService.createCustomModal(new ModalModel(
523 new ButtonModel('Cancel', 'outline grey', () => {
524 modal.instance.close();
527 new ButtonModel('Discard', 'outline blue', () => {
528 this.reverseChangedData();
529 modal.instance.close();
532 new ButtonModel('Save', 'blue', () => {
533 this.saveChangedData().then(() => {
534 modal.instance.close();
537 modal.instance.close();
540 }, () => !this.isValidChangedData)
543 this.ModalService.addDynamicTemplateToModal(modal, this.saveChangedDataModalContentTemplateRef);
544 modal.instance.open();
548 updatePropertyValueAfterDeclare = (input: InputFEModel) => {
549 if (this.instanceFePropertiesMap[input.instanceUniqueId]) {
550 let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => {
551 return feProperty.name == input.relatedPropertyName;
553 let inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined;
554 propertyForUpdatindVal.setAsDeclared(inputPath); //set prop as declared before assigning value
555 this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, inputPath);
556 this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, input.relatedPropertyValue, inputPath);
560 //used for declare button, to keep count of newly checked properties (and ignore declared properties)
561 updateCheckedPropertyCount = (increment: boolean): void => {
562 this.checkedPropertiesCount += (increment) ? 1 : -1;
563 console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount);
566 setInputTabIndication = (numInputs: number): void => {
567 this.propertyInputTabs.setTabIndication('Inputs', numInputs);
570 deleteInput = (input: InputFEModel) => {
571 console.log("==>" + this.constructor.name + ": deleteInput");
572 let inputToDelete = new InputBEModel(input);
574 this.componentServiceNg2
575 .deleteInput(this.component, inputToDelete)
576 .subscribe(response => {
577 this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId);
579 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
580 this.onInstanceSelectedUpdate(this.selectedInstanceData);
581 // let instanceFeProperties = this.instanceFePropertiesMap[this.getInstanceUniqueId(input.instanceName)];
583 // if (instanceFeProperties) {
584 // let propToEnable: PropertyFEModel = instanceFeProperties.find((prop) => {
585 // return prop.name == input.propertyName;
588 // if (propToEnable) {
589 // if (propToEnable.name == response.inputPath) response.inputPath = null;
590 // propToEnable.setNonDeclared(response.inputPath);
591 // //this.propertiesUtils.resetPropertyValue(propToEnable, newValue, response.inputPath);
592 // this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath);
595 }, error => {}); //ignore error
600 /*** SEARCH RELATED FUNCTIONS ***/
601 searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => {
602 let instanceBePropertiesMap:InstanceBePropertiesMap;
603 this.componentServiceNg2
604 .filterComponentInstanceProperties(this.component, filterData)
605 .subscribe(response => {
607 this.processInstancePropertiesResponse(response, false);
608 this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree
609 this.searchPropertyName = filterData.propertyName;//mark in table
610 this.hierarchyNavTabs.triggerTabChange('Composition');
611 this.propertiesNavigationData = [];
612 this.displayClearSearch = true;
613 }, error => {}); //ignore error
617 clearSearch = () => {
618 this.instancesNavigationData = this.instances;
619 this.searchPropertyName = "";
620 this.hierarchyPropertiesDisplayOptions.searchText = "";
621 this.displayClearSearch = false;
622 this.advanceSearch.clearAll();
623 this.searchQuery = '';
626 clickOnClearSearch = () => {
628 this.selectFirstInstanceByDefault();
629 this.hierarchyNavTabs.triggerTabChange('Composition');
632 private isInput = (instanceType:string):boolean =>{
633 return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;