4218123215509bc34b5936180fec5ad8cb8f99ea
[sdc.git] /
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 'use strict';
22 import * as _ from "lodash";
23 import { PROPERTY_TYPES, ValidationUtils, PROPERTY_VALUE_CONSTRAINTS, FormState, PROPERTY_DATA } from "app/utils";
24 import { DataTypesService } from "app/services";
25 import { PropertyModel, DataTypesMap, Component, GroupInstance, PolicyInstance, PropertyBEModel, ComponentMetadata } from "app/models";
26 import { ComponentInstance } from "../../../../models/componentsInstances/componentInstance";
27 import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service";
28 import { SdcUiCommon, SdcUiServices, SdcUiComponents } from "onap-ui-angular";
29 import { CompositionService } from "app/ng2/pages/composition/composition.service";
30 import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service";
31 import { Observable } from "rxjs";
32 import { TopologyTemplateService } from "app/ng2/services/component-services/topology-template.service";
33
34 export interface IEditPropertyModel {
35     property:PropertyModel;
36     types:Array<string>;
37     simpleTypes:Array<string>;
38 }
39
40 interface IPropertyFormViewModelScope extends ng.IScope {
41     forms:any;
42     editForm:ng.IFormController;
43     footerButtons:Array<any>;
44     isNew:boolean;
45     isLoading:boolean;
46     isService:boolean;
47     validationPattern:RegExp;
48     propertyNameValidationPattern:RegExp;
49     commentValidationPattern:RegExp;
50     editPropertyModel:IEditPropertyModel;
51     modalInstanceProperty:ng.ui.bootstrap.IModalServiceInstance;
52     currentPropertyIndex:number;
53     isLastProperty:boolean;
54     myValue:any;
55     nonPrimitiveTypes:Array<string>;
56     dataTypes:DataTypesMap;
57     isTypeDataType:boolean;
58     maxLength:number;
59     isPropertyValueOwner:boolean;
60     isVnfConfiguration:boolean;
61     constraints:string[];
62     modelNameFilter:string;
63
64     validateJson(json:string):boolean;
65     save(doNotCloseModal?:boolean):void;
66     getValidationPattern(type:string):RegExp;
67     validateIntRange(value:string):boolean;
68     close():void;
69     onValueChange():void;
70     onSchemaTypeChange():void;
71     onTypeChange(resetSchema:boolean):void;
72     showSchema():boolean;
73     delete(property:PropertyModel):void;
74     getPrev():void;
75     getNext():void;
76     isSimpleType(typeName:string):boolean;
77     getDefaultValue():any;
78 }
79
80 export class PropertyFormViewModel {
81
82     static '$inject' = [
83         '$scope',
84         'Sdc.Services.DataTypesService',
85         '$uibModalInstance',
86         'property',
87         'ValidationPattern',
88         'PropertyNameValidationPattern',
89         'CommentValidationPattern',
90         'ValidationUtils',
91         // 'component',
92         '$filter',
93         'ModalServiceSdcUI',
94         'filteredProperties',
95         '$timeout',
96         'isPropertyValueOwner',
97         'propertyOwnerType',
98         'propertyOwnerId',
99         'ComponentInstanceServiceNg2',
100         'TopologyTemplateService',
101         'CompositionService',
102         'workspaceService'
103     ];
104
105     private formState:FormState;
106
107     constructor(private $scope:IPropertyFormViewModelScope,
108                 private DataTypesService:DataTypesService,
109                 private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance,
110                 private property:PropertyModel,
111                 private ValidationPattern:RegExp,
112                 private PropertyNameValidationPattern:RegExp,
113                 private CommentValidationPattern:RegExp,
114                 private ValidationUtils:ValidationUtils,
115                 // private component:Component,
116                 private $filter:ng.IFilterService,
117                 private modalService:SdcUiServices.ModalService,
118                 private filteredProperties:Array<PropertyModel>,
119                 private $timeout:ng.ITimeoutService,
120                 private isPropertyValueOwner:boolean,
121                 private propertyOwnerType:string,
122                 private propertyOwnerId:string,
123                 private ComponentInstanceServiceNg2: ComponentInstanceServiceNg2,
124                 private topologyTemplateService: TopologyTemplateService,
125                 private compositionService: CompositionService,
126                 private workspaceService: WorkspaceService) {
127
128         this.formState = angular.isDefined(property.name) ? FormState.UPDATE : FormState.CREATE;
129         this.initScope();
130     }
131
132     private initResource = ():void => {
133         this.$scope.editPropertyModel.property = new PropertyModel(this.property);
134         this.$scope.editPropertyModel.property.type = this.property.type ? this.property.type : null;
135         this.$scope.editPropertyModel.property.value = this.$scope.editPropertyModel.property.value || this.$scope.editPropertyModel.property.defaultValue;
136         this.$scope.constraints = this.property.constraints && this.property.constraints[0] ? this.property.constraints[0]["validValues"]  : null;
137         this.setMaxLength();
138     };
139
140     //init property add-ons labels that show up at the left side of the input.
141     private initAddOnLabels = () => {
142         if (this.$scope.editPropertyModel.property.name == 'network_role' && this.$scope.isService) {
143             //the server sends back the normalized name. Remove it (to prevent interference with validation) and set the addon label to the component name directly.
144             //Note: this cant be done in properties.ts because we dont have access to the component
145             if (this.$scope.editPropertyModel.property.value) {
146                 let splitProp = this.$scope.editPropertyModel.property.value.split(new RegExp(this.workspaceService.metadata.normalizedName + '.', "gi"));
147                 this.$scope.editPropertyModel.property.value = splitProp.pop();
148             }
149             this.$scope.editPropertyModel.property.addOn = this.workspaceService.metadata.name;
150         }
151     }
152
153     private initForNotSimpleType = ():void => {
154         let property = this.$scope.editPropertyModel.property;
155         this.$scope.isTypeDataType = this.DataTypesService.isDataTypeForPropertyType(this.$scope.editPropertyModel.property);
156         if (property.type && this.$scope.editPropertyModel.simpleTypes.indexOf(property.type) == -1) {
157             if (!(property.value || property.defaultValue)) {
158                 switch (property.type) {
159                     case PROPERTY_TYPES.MAP:
160                         this.$scope.myValue = {'': null};
161                         break;
162                     case PROPERTY_TYPES.LIST:
163                         this.$scope.myValue = [];
164                         break;
165                     default:
166                         this.$scope.myValue = {};
167                 }
168             } else {
169                 this.$scope.myValue = JSON.parse(property.value || property.defaultValue);
170             }
171         }
172     };
173
174     private setMaxLength = ():void => {
175         switch (this.$scope.editPropertyModel.property.type) {
176             case PROPERTY_TYPES.MAP:
177             case PROPERTY_TYPES.LIST:
178                 this.$scope.maxLength = this.$scope.editPropertyModel.property.schema.property.type == PROPERTY_TYPES.JSON ?
179                     PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH :
180                     PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH;
181                 break;
182             case PROPERTY_TYPES.JSON:
183                 this.$scope.maxLength = PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH;
184                 break;
185             default:
186                 this.$scope.maxLength =PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH;
187         }
188     };
189
190
191     private initScope = ():void => {
192
193         //scope properties
194         this.$scope.isLoading = true;
195         this.$scope.forms = {};
196         this.$scope.validationPattern = this.ValidationPattern;
197         this.$scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
198         this.$scope.commentValidationPattern = this.CommentValidationPattern;
199         this.$scope.isNew = (this.formState === FormState.CREATE);
200         this.$scope.isService = this.workspaceService.metadata.isService();
201         this.$scope.modalInstanceProperty = this.$uibModalInstance;
202         this.$scope.currentPropertyIndex = _.findIndex(this.filteredProperties, i=> i.name == this.property.name);
203         this.$scope.isLastProperty = this.$scope.currentPropertyIndex == (this.filteredProperties.length - 1);
204         this.$scope.editPropertyModel = {
205             property : new PropertyModel(this.property),
206             types : PROPERTY_DATA.TYPES,
207             simpleTypes : PROPERTY_DATA.SIMPLE_TYPES}; //All simple types
208         this.$scope.isPropertyValueOwner = this.isPropertyValueOwner;
209         this.$scope.propertyOwnerType = this.propertyOwnerType;
210         this.$scope.modelNameFilter = this.workspaceService.metadata.model;
211         //check if property of VnfConfiguration
212         this.$scope.isVnfConfiguration = false;
213         if(this.propertyOwnerType == "component" && angular.isArray(this.compositionService.componentInstances)) {
214             var componentPropertyOwner:ComponentInstance = this.compositionService.componentInstances.find((ci:ComponentInstance) => {
215                 return ci.uniqueId === this.property.resourceInstanceUniqueId;
216             });
217             if (componentPropertyOwner && componentPropertyOwner.componentName === 'vnfConfiguration') {
218                 this.$scope.isVnfConfiguration = true;
219             }
220         }
221         this.initResource();
222         this.initForNotSimpleType();
223
224         this.$scope.validateJson = (json:string):boolean => {
225             if (!json) {
226                 return true;
227             }
228             return this.ValidationUtils.validateJson(json);
229         };
230
231         this.DataTypesService.fetchDataTypesByModel(this.workspaceService.metadata.model).then(response => {
232             this.$scope.dataTypes = response.data as DataTypesMap;
233             this.$scope.nonPrimitiveTypes = _.filter(Object.keys(this.$scope.dataTypes), (type:string)=> {
234                 return this.$scope.editPropertyModel.types.indexOf(type) == -1;
235             });
236
237             this.$scope.isLoading = false;
238         });
239
240         //scope methods
241         this.$scope.save = (doNotCloseModal?:boolean):void => {
242             let property:PropertyModel = this.$scope.editPropertyModel.property;
243             this.$scope.editPropertyModel.property.description = this.ValidationUtils.stripAndSanitize(this.$scope.editPropertyModel.property.description);
244             //if read only - or no changes made - just closes the modal
245             //need to check for property.value changes manually to detect if map properties deleted
246             if ((this.$scope.editPropertyModel.property.readonly && !this.$scope.isPropertyValueOwner)
247                 || (!this.$scope.forms.editForm.$dirty && angular.equals(JSON.stringify(this.$scope.myValue), this.$scope.editPropertyModel.property.value))) {
248                 this.$uibModalInstance.close();
249                 return;
250             }
251
252             this.$scope.isLoading = true;
253
254             let onPropertyFaild = (response):void => {
255                 console.info('onFaild', response);
256                 this.$scope.isLoading = false;
257             };
258
259             let onPropertySuccess = (propertyFromBE:PropertyModel):void => {
260                 this.$scope.isLoading = false;
261                 this.filteredProperties[this.$scope.currentPropertyIndex] = propertyFromBE;
262                 if (!doNotCloseModal) {
263                     this.$uibModalInstance.close(propertyFromBE);
264                 } else {
265                     this.$scope.forms.editForm.$setPristine();
266                     this.$scope.editPropertyModel.property = new PropertyModel();
267                 }
268             };
269
270             //Not clean, but doing this as a temporary fix until we update the property right panel modals
271             if (this.propertyOwnerType === "group"){
272                 this.ComponentInstanceServiceNg2.updateComponentGroupInstanceProperties(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.propertyOwnerId, [property])
273                     .subscribe((propertiesFromBE) => { onPropertySuccess(<PropertyModel>propertiesFromBE[0])}, error => onPropertyFaild(error));
274             } else if (this.propertyOwnerType === "policy"){
275                 if (!this.$scope.editPropertyModel.property.simpleType &&
276                     !this.$scope.isSimpleType(this.$scope.editPropertyModel.property.type) &&
277                     !_.isNil(this.$scope.myValue)) {
278                     property.value = JSON.stringify(this.$scope.myValue);
279                 }
280                 this.ComponentInstanceServiceNg2.updateComponentPolicyInstanceProperties(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.propertyOwnerId, [property])
281                     .subscribe((propertiesFromBE) => { onPropertySuccess(<PropertyModel>propertiesFromBE[0])}, error => onPropertyFaild(error));
282             } else {
283                 //in case we have uniqueId we call update method
284                 if (this.$scope.isPropertyValueOwner) {
285                     if (!this.$scope.editPropertyModel.property.simpleType && !this.$scope.isSimpleType(property.type)) {
286                         let myValueString:string = JSON.stringify(this.$scope.myValue);
287                         property.value = myValueString;
288                     }
289                     this.updateInstanceProperties(property.resourceInstanceUniqueId, [property]).subscribe((propertiesFromBE) => onPropertySuccess(propertiesFromBE[0]),
290                         error => onPropertyFaild(error));
291                 } else {
292                     if (!this.$scope.editPropertyModel.property.simpleType && !this.$scope.isSimpleType(property.type)) {
293                         let myValueString:string = JSON.stringify(this.$scope.myValue);
294                         property.defaultValue = myValueString;
295                     } else {
296                         this.$scope.editPropertyModel.property.defaultValue = this.$scope.editPropertyModel.property.value;
297                     }
298                     this.addOrUpdateProperty(property).subscribe(onPropertySuccess, error => onPropertyFaild(error));
299                 }
300             }
301         };
302
303         this.$scope.getPrev = ():void=> {
304             this.property = this.filteredProperties[--this.$scope.currentPropertyIndex];
305             this.initResource();
306             this.initForNotSimpleType();
307             this.$scope.isLastProperty = false;
308         };
309
310         this.$scope.getNext = ():void=> {
311             this.property = this.filteredProperties[++this.$scope.currentPropertyIndex];
312             this.initResource();
313             this.initForNotSimpleType();
314             this.$scope.isLastProperty = this.$scope.currentPropertyIndex == (this.filteredProperties.length - 1);
315         };
316
317         this.$scope.isSimpleType = (typeName:string):boolean=> {
318             return typeName && this.$scope.editPropertyModel.simpleTypes.indexOf(typeName) != -1;
319         };
320
321         this.$scope.showSchema = ():boolean => {
322             return [PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP].indexOf(this.$scope.editPropertyModel.property.type) > -1;
323         };
324
325         this.$scope.getValidationPattern = (type:string):RegExp => {
326             return this.ValidationUtils.getValidationPattern(type);
327         };
328
329         this.$scope.validateIntRange = (value:string):boolean => {
330             return !value || this.ValidationUtils.validateIntRange(value);
331         };
332
333         this.$scope.close = ():void => {
334             this.$uibModalInstance.close();
335         };
336
337         // put default value when instance value is empty
338         this.$scope.onValueChange = ():void => {
339             if (!this.$scope.editPropertyModel.property.value) {
340                 if (this.$scope.isPropertyValueOwner) {
341                     this.$scope.editPropertyModel.property.value = this.$scope.editPropertyModel.property.defaultValue;
342                 }
343             }
344         };
345
346         // Add the done button at the footer.
347         this.$scope.footerButtons = [
348             {'name': 'Save', 'css': 'blue', 'callback': this.$scope.save},
349             {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close}
350         ];
351
352         this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => {
353             this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
354         });
355
356         this.$scope.getDefaultValue = ():any => {
357             return this.$scope.isPropertyValueOwner ? this.$scope.editPropertyModel.property.defaultValue : null;
358         };
359
360         this.$scope.onTypeChange = ():void => {
361             this.$scope.editPropertyModel.property.value = '';
362             this.$scope.editPropertyModel.property.defaultValue = '';
363             this.setMaxLength();
364             this.initForNotSimpleType();
365         };
366
367         this.$scope.onSchemaTypeChange = ():void => {
368             if (this.$scope.editPropertyModel.property.type == PROPERTY_TYPES.MAP) {
369                 this.$scope.myValue = {'': null};
370             } else if (this.$scope.editPropertyModel.property.type == PROPERTY_TYPES.LIST) {
371                 this.$scope.myValue = [];
372             }
373             this.setMaxLength();
374         };
375
376         this.$scope.delete = (property:PropertyModel):void => {
377             let onOk: Function = ():void => {
378                 this.deleteProperty(property.uniqueId).subscribe(
379                     this.$scope.close
380                 );
381             };
382             let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE");
383             let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}");
384             const okButton = {testId: "OK", text: "OK", type: SdcUiCommon.ButtonType.info, callback: onOk, closeModal: true} as SdcUiComponents.ModalButtonComponent;
385             this.modalService.openInfoModal(title, message, 'delete-modal', [okButton]);
386         };
387     };
388
389     private updateInstanceProperties = (componentInstanceId:string, properties:PropertyModel[]):Observable<PropertyModel[]> => {
390
391         return this.ComponentInstanceServiceNg2.updateInstanceProperties(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, componentInstanceId, properties)
392             .map(newProperties => {
393                 newProperties.forEach((newProperty) => {
394                     if (!_.isNil(newProperty.path)) {
395                         if (newProperty.path[0] === newProperty.resourceInstanceUniqueId) newProperty.path.shift();
396                         // find exist instance property in parent component for update the new value ( find bu uniqueId & path)
397                         let existProperty: PropertyModel = <PropertyModel>_.find(this.compositionService.componentInstancesProperties[newProperty.resourceInstanceUniqueId], {
398                             uniqueId: newProperty.uniqueId,
399                             path: newProperty.path
400                         });
401                         let index = this.compositionService.componentInstancesProperties[newProperty.resourceInstanceUniqueId].indexOf(existProperty);
402                         this.compositionService.componentInstancesProperties[newProperty.resourceInstanceUniqueId][index] = newProperty;
403                     }
404                 });
405                 return newProperties;
406             });
407     };
408
409     private addOrUpdateProperty = (property: PropertyModel): Observable<PropertyModel> => {
410         if (!property.uniqueId) {
411             let onSuccess = (newProperty: PropertyModel): PropertyModel => {
412                 this.filteredProperties.push(newProperty);
413                 return newProperty;
414             };
415             return this.topologyTemplateService.addProperty(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, property)
416                 .map(onSuccess);
417         } else {
418             let onSuccess = (newProperty: PropertyModel): PropertyModel => {
419                 // find exist instance property in parent component for update the new value ( find bu uniqueId )
420                 let existProperty: PropertyModel = <PropertyModel>_.find(this.filteredProperties, {uniqueId: newProperty.uniqueId});
421                 let propertyIndex = this.filteredProperties.indexOf(existProperty);
422                 this.filteredProperties[propertyIndex] = newProperty;
423                 return newProperty;
424             };
425             return this.topologyTemplateService.updateProperty(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, property).map(onSuccess);
426         }
427     };
428
429     public deleteProperty = (propertyId:string):Observable<void> => {
430         let onSuccess = ():void => {
431             console.log("Property deleted");
432             delete _.remove(this.filteredProperties, {uniqueId: propertyId})[0];
433         };
434         let onFailed = ():void => {
435             console.log("Failed to delete property");
436         };
437         return this.topologyTemplateService.deleteProperty(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, propertyId).map(onSuccess, onFailed);
438     };
439 }