2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2021 Nordix Foundation. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
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 {Component, Inject, ViewChild} from '@angular/core';
24 Component as ComponentData,
29 import {SdcUiCommon, SdcUiServices} from 'onap-ui-angular';
30 import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
31 import {Tab, Tabs} from "../../components/ui/tabs/tabs.component";
32 import * as _ from 'lodash';
33 import {OutputFEModel} from "../../../models/attributes-outputs/output-fe-model";
34 import {OutputBEModel} from "../../../models/attributes-outputs/output-be-model";
35 import {EVENTS, ResourceType, WorkspaceMode} from "../../../utils/constants";
36 import {ComponentModeService} from "../../services/component-services/component-mode.service";
37 import {EventListenerService} from "app/services";
38 import {HierarchyNavService} from "./services/hierarchy-nav.service";
39 import {ComponentServiceNg2} from "../../services/component-services/component.service";
40 import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service";
41 import {KeysPipe} from "../../pipes/keys.pipe";
43 InstanceAttributesAPIMap,
44 InstanceBeAttributesMap,
45 InstanceFeAttributesMap
46 } from "app/models/attributes-outputs/attribute-fe-map";
48 InstanceBePropertiesMap
49 } from "app/models/properties-inputs/property-fe-map";
50 import {ModalService} from "../../services/modal.service";
51 import {InstanceFeDetails} from "../../../models/instance-fe-details";
52 import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
53 import {UnsavedChangesComponent} from "../../components/ui/forms/unsaved-changes/unsaved-changes.component";
54 import {SimpleFlatAttribute} from "app/models/attributes-outputs/simple-flat-attribute";
55 import {AttributeFEModel} from "../../../models/attributes-outputs/attribute-fe-model";
56 import {AttributesUtils} from "./services/attributes.utils";
57 import {OutputsUtils} from "app/ng2/pages/attributes-outputs/services/outputs.utils";
58 import {AttributesService} from "app/ng2/services/attributes.service";
59 import {DerivedFEAttribute} from "../../../models/attributes-outputs/derived-fe-attribute";
60 import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
61 import {AttributeCreatorComponent} from "app/ng2/pages/attributes-outputs/attribute-creator/attribute-creator.component";
62 import {AttributeRowSelectedEvent} from "app/ng2/components/logic/attributes-table/attributes-table.component";
63 import { DeclareInputComponent } from '../properties-assignment/declare-input/declare-input.component';
65 const SERVICE_SELF_TITLE = "SELF";
68 selector: 'attributes-outputs',
69 templateUrl: './attributes-outputs.page.component.html',
70 styleUrls: ['./attributes-outputs.page.component.less', '../../../../assets/styles/table-style.less']
72 export class AttributesOutputsComponent {
73 title = "Attributes & Outputs";
75 @ViewChild('componentAttributesTable')
78 component: ComponentData;
79 componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
81 attributesNavigationData = [];
82 instancesNavigationData = [];
84 instanceFeAttributesMap: InstanceFeAttributesMap;
85 outputs: Array<OutputFEModel> = [];
86 instances: Array<ComponentInstance> = [];
88 attributeStructureHeader: string;
90 selectedFlatAttribute: SimpleFlatAttribute = new SimpleFlatAttribute();
91 selectedInstanceData: ComponentInstance = null;
92 checkedAttributesCount: number = 0;
94 hierarchyAttributesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
95 hierarchyInstancesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
96 searchAttributeName: string;
98 isOutputsTabSelected: boolean;
99 isAttributesTabSelected: boolean;
101 resourceIsReadonly: boolean;
102 loadingInstances: boolean = false;
103 loadingOutputs: boolean = false;
104 loadingAttributes: boolean = false;
105 changedData: Array<AttributeFEModel | OutputFEModel>;
106 hasChangedData: boolean;
107 isValidChangedData: boolean;
108 savingChangedData: boolean;
109 stateChangeStartUnregister: Function;
110 serviceBeAttributesMap: InstanceBeAttributesMap;
112 @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
113 @ViewChild('attributeOutputTabs') attributeOutputTabs: Tabs;
117 private attributesService: AttributesService,
118 private hierarchyNavService: HierarchyNavService,
119 private attributesUtils: AttributesUtils,
120 private outputsUtils: OutputsUtils,
121 private componentServiceNg2: ComponentServiceNg2,
122 private componentInstanceServiceNg2: ComponentInstanceServiceNg2,
123 @Inject("$stateParams") _stateParams,
124 @Inject("$scope") private $scope: ng.IScope,
125 @Inject("$state") private $state: ng.ui.IStateService,
126 @Inject("Notification") private Notification: any,
127 private componentModeService: ComponentModeService,
128 private EventListenerService: EventListenerService,
129 private ModalServiceSdcUI: SdcUiServices.ModalService,
130 private ModalService: ModalService,
131 private keysPipe: KeysPipe,
132 private topologyTemplateService: TopologyTemplateService
135 this.instanceFeAttributesMap = new InstanceFeAttributesMap();
136 /* 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
137 than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
138 this.component = _stateParams.component;
139 this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.onCheckout);
140 this.updateViewMode();
142 this.changedData = [];
143 this.updateHasChangedData();
144 this.isValidChangedData = true;
148 this.loadingOutputs = true;
149 this.loadingInstances = true;
150 this.loadingAttributes = true;
151 this.topologyTemplateService
152 .getComponentOutputsWithAttributes(this.component.componentType, this.component.uniqueId)
153 .subscribe(response => {
154 if (response.outputs) {
155 response.outputs.forEach(output => {
156 const newOutput: OutputFEModel = new OutputFEModel(output);
157 this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
158 this.outputs.push(newOutput);
162 this.Notification.error({
163 message: 'Failed to Initialize:' + error,
167 this.loadingOutputs = false;
169 this.componentServiceNg2
170 .getComponentResourceAttributesData(this.component)
171 .subscribe(response => {
173 this.instances.push(...response.componentInstances);
175 // add the service self instance to the top of the list.
176 const serviceInstance = new ComponentInstance();
177 serviceInstance.name = SERVICE_SELF_TITLE;
178 serviceInstance.uniqueId = this.component.uniqueId;
179 this.instances.unshift(serviceInstance);
180 if (this.instances) {
181 this.instances.forEach(instance => {
182 this.instancesNavigationData.push(instance);
183 this.componentInstanceNamesMap[instance.uniqueId] = <InstanceFeDetails>{
185 iconClass: instance.iconClass,
186 originArchived: instance.originArchived
190 this.selectFirstInstanceByDefault();
192 this.Notification.error({
193 message: 'Failed to Initialize:' + error,
197 this.loadingInstances = false;
198 this.loadingAttributes = false;
201 this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
202 // stop if has changed attributes
203 if (this.hasChangedData) {
204 event.preventDefault();
205 this.showUnsavedChangesAlert().then(() => {
206 this.$state.go(toState, toParams);
213 this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
214 this.stateChangeStartUnregister();
217 selectFirstInstanceByDefault = () => {
218 if (this.instancesNavigationData.length > 0) {
219 this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
223 updateViewMode = () => {
224 this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
227 onCheckout = (component: ComponentData) => {
228 this.component = component;
229 this.updateViewMode();
232 isSelf = (): boolean => {
233 return this.selectedInstanceData && this.selectedInstanceData.uniqueId == this.component.uniqueId;
236 getServiceAttributes() {
237 this.loadingAttributes = true;
238 this.topologyTemplateService
239 .getServiceAttributes(this.component.uniqueId)
240 .subscribe((response) => {
241 this.serviceBeAttributesMap = new InstanceBeAttributesMap();
242 this.serviceBeAttributesMap[this.component.uniqueId] = response;
243 this.processInstanceAttributesResponse(this.serviceBeAttributesMap, false, null);
245 this.Notification.error({
246 message: 'Failed to get Service Attribute:' + error,
250 this.loadingAttributes = false;
254 onInstanceSelectedUpdate = (instance: ComponentInstance) => {
255 // stop if has changed attributes
256 if (this.hasChangedData) {
257 this.showUnsavedChangesAlert().then(() => {
258 this.changeSelectedInstance(instance)
262 this.changeSelectedInstance(instance);
265 changeSelectedInstance = (instance: ComponentInstance) => {
266 this.selectedInstanceData = instance;
267 this.loadingAttributes = true;
268 this.instanceFeAttributesMap = undefined;
269 if (instance instanceof ComponentInstance) {
270 let instanceBeAttributesMap: InstanceBeAttributesMap | InstanceBePropertiesMap = new InstanceBeAttributesMap();
271 if (this.isOutput(instance.originType)) {
272 this.componentInstanceServiceNg2
273 .getComponentInstanceOutputs(this.component, instance)
274 .subscribe(response => {
275 instanceBeAttributesMap[instance.uniqueId] = response;
276 this.processInstanceAttributesResponse(instanceBeAttributesMap, true, instance.uniqueId);
278 this.Notification.error({
279 message: 'Failed to change Selected Instance:' + error,
283 this.loadingAttributes = false;
285 } else if (this.isSelf()) {
286 this.getServiceAttributes();
288 this.componentInstanceServiceNg2
289 .getComponentInstanceAttributes(this.component, instance.uniqueId)
290 .subscribe(response => {
291 instanceBeAttributesMap = new InstanceBeAttributesMap();
292 instanceBeAttributesMap[instance.uniqueId] = response;
293 this.processInstanceAttributesResponse(instanceBeAttributesMap, false, instance.uniqueId);
295 this.Notification.error({
296 message: 'Failed to change Selected Instance:' + error,
300 this.loadingAttributes = false;
302 this.componentInstanceServiceNg2
303 .getComponentInstanceProperties(this.component, instance.uniqueId)
304 .subscribe(response => {
305 instanceBeAttributesMap = new InstanceBePropertiesMap();
306 instanceBeAttributesMap[instance.uniqueId] = response;
307 this.processInstanceAttributesResponse(instanceBeAttributesMap, false, instance.uniqueId);
309 this.Notification.error({
310 message: 'Failed to change Selected Instance:' + error,
314 this.loadingAttributes = false;
317 this.resourceIsReadonly = (instance.componentName === "vnfConfiguration");
319 this.loadingAttributes = false;
322 //clear selected attribute from the navigation
323 this.selectedFlatAttribute = new SimpleFlatAttribute();
324 this.attributesNavigationData = [];
328 * Entry point handling response from server
330 processInstanceAttributesResponse = (instanceBeAttributesMap: InstanceBePropertiesMap | InstanceBeAttributesMap, originTypeIsVF: boolean, instanceId: string) => {
331 let attributesMap = this.attributesUtils.convertAttributesMapToFEAndCreateChildren(instanceBeAttributesMap, originTypeIsVF, this.outputs); //create flattened children, disable declared attribs, and init values
332 if (!this.instanceFeAttributesMap) {
333 this.instanceFeAttributesMap = attributesMap;
334 } else if (instanceId) {
335 let toAdd: AttributeFEModel[];
336 if (instanceBeAttributesMap instanceof InstanceBeAttributesMap) {
337 toAdd = this.instanceFeAttributesMap[instanceId].filter(currentAtr => !attributesMap[instanceId].some(newAtr => newAtr.name === currentAtr.name))
338 this.instanceFeAttributesMap[instanceId] = attributesMap[instanceId];
340 toAdd = attributesMap[instanceId].filter(currentAtr => !this.instanceFeAttributesMap[instanceId].some(newAtr => newAtr.name === currentAtr.name))
342 this.instanceFeAttributesMap[instanceId] = this.instanceFeAttributesMap[instanceId].concat(toAdd);
344 this.checkedAttributesCount = 0;
348 /*** VALUE CHANGE EVENTS ***/
349 dataChanged = (item: AttributeFEModel | OutputFEModel) => {
351 if (this.isAttributesTabSelected && item instanceof AttributeFEModel) {
352 itemHasChanged = item.hasValueObjChanged();
353 } else if (this.isOutputsTabSelected && item instanceof OutputFEModel) {
354 itemHasChanged = item.hasChanged();
357 const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item);
358 if (itemHasChanged) {
359 if (dataChangedIdx === -1) {
360 this.changedData.push(item);
363 if (dataChangedIdx !== -1) {
364 this.changedData.splice(dataChangedIdx, 1);
368 if (this.isAttributesTabSelected) {
369 this.isValidChangedData = this.changedData.every((changedItem) => (<AttributeFEModel>changedItem).valueObjIsValid);
370 } else if (this.isOutputsTabSelected) {
371 this.isValidChangedData = this.changedData.every((changedItem) => (<OutputFEModel>changedItem).defaultValueObjIsValid);
373 this.updateHasChangedData();
377 /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
380 * Handle select node in navigation area, and select the row in table
382 onAttributeSelectedUpdate = ($event) => {
383 this.selectedFlatAttribute = $event;
384 let parentAttribute: AttributeFEModel = this.attributesService.getParentAttributeFEModelFromPath(this.instanceFeAttributesMap[this.selectedFlatAttribute.instanceName], this.selectedFlatAttribute.path);
385 parentAttribute.expandedChildAttributeId = this.selectedFlatAttribute.path;
389 * When user select row in table, this will prepare the hierarchy object for the tree.
391 selectAttributeRow = (attributeRowSelectedEvent: AttributeRowSelectedEvent) => {
392 let attribute = attributeRowSelectedEvent.attributeModel;
393 let instanceName = attributeRowSelectedEvent.instanceName;
394 this.attributeStructureHeader = null;
396 // Build hierarchy tree for the navigation and update attributesNavigationData with it.
397 if (!(this.selectedInstanceData instanceof ComponentInstance) || this.selectedInstanceData.originType !== ResourceType.VF) {
398 let simpleFlatAttributes: Array<SimpleFlatAttribute>;
399 if (attribute instanceof AttributeFEModel) {
400 simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(attribute, instanceName);
401 } else if (attribute instanceof DerivedFEAttribute) {
402 // Need to find parent AttributeFEModel
403 let parentAttributeFEModel: AttributeFEModel = _.find(this.instanceFeAttributesMap[instanceName], (tmpFeAttribute): boolean => {
404 return attribute.attributesName.indexOf(tmpFeAttribute.name) === 0;
406 simpleFlatAttributes = this.hierarchyNavService.getSimpleAttributesTree(parentAttributeFEModel, instanceName);
408 this.attributesNavigationData = simpleFlatAttributes;
411 // Update the header in the navigation tree with attribute name.
412 this.attributeStructureHeader = (attribute.attributesName.split('#'))[0];
414 // Set selected attribute in table
415 this.selectedFlatAttribute = this.hierarchyNavService.createSimpleFlatAttribute(attribute, instanceName);
416 this.hierarchyNavTabs.triggerTabChange('Attribute Structure');
419 tabChanged = (event) => {
420 // stop if has changed attributes
421 if (this.hasChangedData) {
422 this.attributeOutputTabs.triggerTabChange(this.currentMainTab.title);
423 this.showUnsavedChangesAlert().then(() => {
424 this.attributeOutputTabs.selectTab(this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title));
429 this.currentMainTab = this.attributeOutputTabs.tabs.find((tab) => tab.title === event.title);
430 this.isAttributesTabSelected = this.currentMainTab.title === "Attributes";
431 this.isOutputsTabSelected = this.currentMainTab.title === "Outputs";
432 this.attributeStructureHeader = null;
433 this.searchQuery = '';
436 declareOutput = (): void => {
437 if (this.checkedAttributesCount == 1) {
438 this.openAddOutputNameModal();
439 } else if (this.checkedAttributesCount > 1) {
440 this.declareAttributes();
444 private openAddOutputNameModal = (): void => {
445 const modalTitle = 'Enter name of the ouput to be created';
446 const modalButtons = [];
447 const modal = this.ModalService.createCustomModal(new ModalModel(
454 modalButtons.push(new ButtonModel('Save', 'blue',
456 const outputName: string = modal.instance.dynamicContent.instance.inputNameForm.value;
458 this.declareAttributes(outputName);
460 this.Notification.warning({
461 message: 'Failed to set input name',
465 this.ModalService.closeCurrentModal();
468 modalButtons.push(new ButtonModel('Cancel', 'outline grey', () => {
469 this.ModalService.closeCurrentModal();
471 this.ModalService.addDynamicContentToModal(modal, DeclareInputComponent, {defaultInputName: this.generateDefaultOutputName()});
472 modal.instance.open();
475 generateDefaultOutputName = (): string => {
476 let defaultInputName: string;
477 let instancesIds = this.keysPipe.transform(this.instanceFeAttributesMap, []);
478 angular.forEach(instancesIds, (instanceId: string) => {
479 const selectedOutput : AttributeBEModel = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId])[0];
480 let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
481 defaultInputName = selectedOutput.name;
482 if (selectedInstanceData.invariantName) {
483 defaultInputName = selectedInstanceData.invariantName+'_'+selectedOutput.name;
486 return defaultInputName;
489 /*** DECLARE ATTRIBUTES/OUTPUTS ***/
490 declareAttributes = (outputName?: string): void => {
491 this.loadingAttributes = true;
492 let selectedComponentInstancesAttributes: InstanceBeAttributesMap = new InstanceBeAttributesMap();
493 let selectedComponentInstancesOutputs: InstanceBeAttributesMap = new InstanceBeAttributesMap();
494 let instancesIds = this.keysPipe.transform(this.instanceFeAttributesMap, []);
496 angular.forEach(instancesIds, (instanceId: string): void => {
497 let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
498 if (selectedInstanceData instanceof ComponentInstance) {
499 if (!this.isOutput(selectedInstanceData.originType)) {
500 // convert Attribute FE model -> Attribute BE model, extract only checked
501 selectedComponentInstancesAttributes[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
503 selectedComponentInstancesAttributes[instanceId][0].outputName = outputName;
506 selectedComponentInstancesOutputs[instanceId] = this.attributesService.getCheckedAttributes(this.instanceFeAttributesMap[instanceId]);
511 let outputsToCreate: InstanceAttributesAPIMap = new InstanceAttributesAPIMap(selectedComponentInstancesOutputs, selectedComponentInstancesAttributes);
512 this.topologyTemplateService
513 .createOutput(this.component, outputsToCreate, this.isSelf())
514 .subscribe((response) => {
515 this.setOutputTabIndication(response.length);
516 this.checkedAttributesCount = 0;
517 response.forEach((output: OutputBEModel) => {
518 const newOutput: OutputFEModel = new OutputFEModel(output);
519 this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
520 this.outputs.push(newOutput);
521 this.updateAttributeValueAfterDeclare(newOutput);
523 this.loadingAttributes = false;
524 }, error => {this.loadingAttributes = false;}); //ignore error
527 saveChangedData = (): Promise<(AttributeBEModel | OutputBEModel)[]> => {
528 return new Promise((resolve, reject) => {
529 if (!this.isValidChangedData) {
530 reject('Changed data is invalid - cannot save!');
533 if (!this.changedData.length) {
538 // make request and its handlers
540 let handleSuccess, handleError;
541 if (this.isAttributesTabSelected) {
542 const changedAttribs = this.changedData.map((changedAttrib) => {
543 changedAttrib = <AttributeFEModel>changedAttrib;
544 const attribBE = new AttributeBEModel(changedAttrib);
545 attribBE.toscaPresentation = new ToscaPresentationData();
546 attribBE.toscaPresentation.ownerId = changedAttrib.parentUniqueId;
547 attribBE.value = changedAttrib.getJSONValue();
548 attribBE.name = changedAttrib.origName || changedAttrib.name;
549 delete attribBE.origName;
553 if (this.selectedInstanceData instanceof ComponentInstance) {
555 console.log("changedAttribs", changedAttribs);
556 request = this.topologyTemplateService.updateServiceAttributes(this.component.uniqueId, _.map(changedAttribs, cp => {
557 delete cp.constraints;
561 request = this.componentInstanceServiceNg2
562 .updateInstanceAttributes(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedAttribs);
564 handleSuccess = (response) => {
565 // reset each changed attribute with new value and remove it from changed attributes list
566 response.forEach((resAttrib) => {
567 const changedAttrib = <AttributeFEModel>this.changedData.shift();
568 this.attributesUtils.resetAttributeValue(changedAttrib, resAttrib.value);
571 console.log("updated instance attributes: ", response);
574 } else if (this.isOutputsTabSelected) {
575 const changedOutputs: OutputBEModel[] = this.changedData.map((changedOutput) => {
576 changedOutput = <OutputFEModel>changedOutput;
577 const outputBE = new OutputBEModel(changedOutput);
578 outputBE.defaultValue = changedOutput.getJSONDefaultValue();
581 request = this.componentServiceNg2.updateComponentOutputs(this.component, changedOutputs);
582 handleSuccess = (response) => {
583 // reset each changed attribute with new value and remove it from changed attributes list
584 response.forEach((resOutput) => {
585 const changedOutput = <OutputFEModel>this.changedData.shift();
586 this.outputsUtils.resetOutputDefaultValue(changedOutput, resOutput.defaultValue);
587 changedOutput.required = resOutput.required;
592 this.savingChangedData = true;
595 this.savingChangedData = false;
596 handleSuccess && handleSuccess(response);
597 this.updateHasChangedData();
601 this.savingChangedData = false;
602 handleError && handleError(error);
603 this.updateHasChangedData();
612 reverseChangedData = (): void => {
613 // make reverse item handler
614 let handleReverseItem;
615 if (this.isAttributesTabSelected) {
616 handleReverseItem = (changedItem) => {
617 changedItem = <AttributeFEModel>changedItem;
618 this.attributesUtils.resetAttributeValue(changedItem, changedItem.value);
619 this.checkedAttributesCount = 0;
621 } else if (this.isOutputsTabSelected) {
622 handleReverseItem = (changedItem) => {
623 changedItem = <OutputFEModel>changedItem;
624 this.outputsUtils.resetOutputDefaultValue(changedItem, changedItem.defaultValue);
625 changedItem.required = changedItem.requiredOrig;
629 this.changedData.forEach(handleReverseItem);
630 this.changedData = [];
631 this.updateHasChangedData();
634 updateHasChangedData = (): boolean => {
635 const curHasChangedData: boolean = (this.changedData.length > 0);
636 if (curHasChangedData !== this.hasChangedData) {
637 this.hasChangedData = curHasChangedData;
638 if (this.hasChangedData) {
639 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, this.hasChangedData, this.showUnsavedChangesAlert);
641 this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
644 return this.hasChangedData;
647 doSaveChangedData = (onSuccessFunction?: Function, onError?: Function): void => {
648 this.saveChangedData().then(
650 this.Notification.success({
651 message: 'Successfully saved changes',
654 if (onSuccessFunction) onSuccessFunction();
655 if (this.isAttributesTabSelected) {
656 this.checkedAttributesCount = 0;
658 this.hasChangedData = false;
659 this.isValidChangedData = false;
662 this.Notification.error({
663 message: 'Failed to save changes!',
666 if (onError) onError();
671 showUnsavedChangesAlert = (): Promise<any> => {
672 let modalTitle: string;
673 if (this.isAttributesTabSelected) {
674 modalTitle = `Unsaved attributes for ${this.selectedInstanceData.name}`;
675 } else if (this.isOutputsTabSelected) {
676 modalTitle = `Unsaved outputs for ${this.component.name}`;
679 return new Promise<any>((resolve, reject) => {
680 this.ModalServiceSdcUI.openCustomModal(
684 type: SdcUiCommon.ModalType.custom,
685 testId: "navigate-modal",
691 type: SdcUiCommon.ButtonType.secondary,
694 callback: () => reject()
699 type: SdcUiCommon.ButtonType.secondary,
703 this.reverseChangedData();
710 type: SdcUiCommon.ButtonType.primary,
713 disabled: !this.isValidChangedData,
714 callback: () => this.doSaveChangedData(resolve, reject)
716 ] as SdcUiCommon.IModalButtonComponent[]
717 } as SdcUiCommon.IModalConfig, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData});
722 updateAttributeValueAfterDeclare = (output: OutputFEModel) => {
723 const attributeList = this.instanceFeAttributesMap[output.instanceUniqueId];
725 const instanceName = output.instanceUniqueId.slice(output.instanceUniqueId.lastIndexOf('.') + 1);
726 const attributeForUpdatingVal = attributeList.find((feAttribute: AttributeFEModel) => {
727 return feAttribute.name == output.relatedAttributeName &&
728 (feAttribute.name == output.relatedAttributeName || output.name === instanceName.concat('_').concat(feAttribute.name.replace(/[.]/g, '_')));
730 const outputPath = (output.outputPath && output.outputPath != attributeForUpdatingVal.name) ? output.outputPath : undefined;
731 attributeForUpdatingVal.setAsDeclared(outputPath); //set attribute as declared before assigning value
732 // this.attributesService.disableRelatedAttributes(attributeForUpdatingVal, outputPath);
733 this.attributesUtils.resetAttributeValue(attributeForUpdatingVal, output.relatedAttributeValue, outputPath);
737 //used for declare button, to keep count of newly checked attributes (and ignore declared attributes)
738 updateCheckedAttributeCount = (increment: boolean): void => {
739 this.checkedAttributesCount += (increment) ? 1 : -1;
742 setOutputTabIndication = (numOutputs: number): void => {
743 this.attributeOutputTabs.setTabIndication('Outputs', numOutputs);
746 deleteOutput = (output: OutputFEModel) => {
747 let outputToDelete = new OutputBEModel(output);
749 this.componentServiceNg2
750 .deleteOutput(this.component, outputToDelete)
751 .subscribe(response => {
752 this.outputs = this.outputs.filter(output => output.uniqueId !== response.uniqueId);
754 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning attributes within the response, use commented code below instead!
755 this.changeSelectedInstance(this.selectedInstanceData);
757 this.Notification.error({
758 message: 'Failed to delete Output:' + error,
764 deleteAttribute = (attribute: AttributeFEModel) => {
765 const attributeToDelete = new AttributeFEModel(attribute);
766 this.loadingAttributes = true;
767 const feMap = this.instanceFeAttributesMap;
768 this.topologyTemplateService
769 .deleteServiceAttribute(this.component.uniqueId, attributeToDelete)
770 .subscribe((response) => {
771 const attribs = feMap[this.component.uniqueId];
772 attribs.splice(attribs.findIndex(p => p.uniqueId === response), 1);
774 this.Notification.error({
775 message: 'Failed to delete Attribute:' + error,
779 this.loadingAttributes = false;
783 addAttribute = () => {
784 let modalTitle = 'Add Attribute';
785 let modal = this.ModalService.createCustomModal(new ModalModel(
790 new ButtonModel('Save', 'blue', () => {
791 modal.instance.dynamicContent.instance.isLoading = true;
792 const newAttribute: AttributeBEModel = modal.instance.dynamicContent.instance.attributeModel;
793 this.topologyTemplateService.createServiceAttribute(this.component.uniqueId, newAttribute)
794 .subscribe((response) => {
795 modal.instance.dynamicContent.instance.isLoading = false;
796 const newAttrib: AttributeFEModel = this.attributesUtils.convertAddAttributeBEToAttributeFE(response);
797 this.instanceFeAttributesMap[this.component.uniqueId].push(newAttrib);
798 modal.instance.close();
800 modal.instance.dynamicContent.instance.isLoading = false;
801 this.Notification.error({
802 message: 'Failed to add Attribute:' + error,
806 }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()),
807 new ButtonModel('Cancel', 'outline grey', () => {
808 modal.instance.close();
813 this.ModalService.addDynamicContentToModal(modal, AttributeCreatorComponent, {});
814 modal.instance.open();
817 private isOutput = (instanceType: string): boolean => {
818 return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;