Support additional operands for node filters
[sdc.git] / catalog-ui / src / app / ng2 / pages / service-dependencies-editor / service-dependencies-editor.component.ts
1 /*!
2  * Copyright © 2016-2018 European Support Limited
3  * Modification Copyright (C) 2022 Nordix Foundation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14  * or implied. See the License for the specific language governing
15  * permissions and limitations under the License.
16  */
17 import {Component, Input, OnInit} from '@angular/core';
18 import {InputBEModel, PropertyBEModel, PropertyFEModel, PropertyModel} from 'app/models';
19 import {SourceType} from 'app/ng2/components/logic/service-dependencies/service-dependencies.component';
20 import {DropdownValue} from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component';
21 import {ServiceServiceNg2} from 'app/ng2/services/component-services/service.service';
22 import {PROPERTY_DATA, PROPERTY_TYPES} from 'app/utils';
23 import {PropertiesUtils} from '../properties-assignment/services/properties.utils';
24 import {ToscaFunctionValidationEvent} from "../properties-assignment/tosca-function/tosca-function.component";
25 import {InstanceFeDetails} from "../../../models/instance-fe-details";
26 import {CompositionService} from "../composition/composition.service";
27 import {ToscaGetFunction} from "../../../models/tosca-get-function";
28 import {PropertyFilterConstraintUi} from "../../../models/ui-models/property-filter-constraint-ui";
29 import {ConstraintOperatorType, FilterConstraintHelper} from "../../../utils/filter-constraint-helper";
30 import {ToscaFunctionHelper} from "../../../utils/tosca-function-helper";
31 import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
32 import {CustomToscaFunction} from "../../../models/default-custom-functions";
33 import {ToscaFunction} from "../../../models/tosca-function";
34
35 @Component({
36   selector: 'service-dependencies-editor',
37   templateUrl: './service-dependencies-editor.component.html',
38   styleUrls: ['./service-dependencies-editor.component.less'],
39   providers: [ServiceServiceNg2]
40 })
41 export class ServiceDependenciesEditorComponent implements OnInit {
42
43   @Input() serviceRuleIndex: number;
44   @Input() serviceRules: PropertyFilterConstraintUi[];
45   @Input() compositeServiceName: string;
46   @Input() currentServiceName: string;
47   @Input() parentServiceInputs: InputBEModel[];
48   @Input() parentServiceProperties: PropertyBEModel[];
49   @Input() selectedInstanceProperties: PropertyBEModel[];
50   @Input() allowedOperators: ConstraintOperatorType[] = [
51       ConstraintOperatorType.GREATER_THAN,
52       ConstraintOperatorType.LESS_THAN,
53       ConstraintOperatorType.EQUAL,
54       ConstraintOperatorType.GREATER_OR_EQUAL,
55       ConstraintOperatorType.LESS_OR_EQUAL,
56       ConstraintOperatorType.IN_RANGE,
57       ConstraintOperatorType.VALID_VALUES,
58       ConstraintOperatorType.LENGTH,
59       ConstraintOperatorType.MIN_LENGTH,
60       ConstraintOperatorType.MAX_LENGTH,
61       ConstraintOperatorType.PATTERN
62   ];
63     @Input() comparableAllowedOperators: ConstraintOperatorType[] = [
64         ConstraintOperatorType.GREATER_THAN,
65         ConstraintOperatorType.LESS_THAN,
66         ConstraintOperatorType.EQUAL,
67         ConstraintOperatorType.GREATER_OR_EQUAL,
68         ConstraintOperatorType.LESS_OR_EQUAL,
69         ConstraintOperatorType.IN_RANGE,
70         ConstraintOperatorType.VALID_VALUES,
71     ];
72   @Input() capabilityNameAndPropertiesMap: Map<string, PropertyModel[]>;
73   @Input() filterType: FilterType;
74   @Input() filterConstraint: PropertyFilterConstraintUi;
75   //output
76   currentRule: PropertyFilterConstraintUi;
77
78   FILTER_TYPE_CAPABILITY: FilterType = FilterType.CAPABILITY
79
80   listAllowedOperators: ConstraintOperatorType[] = [
81         ConstraintOperatorType.EQUAL,
82         ConstraintOperatorType.LENGTH,
83         ConstraintOperatorType.MIN_LENGTH,
84         ConstraintOperatorType.MAX_LENGTH
85     ];
86
87   operatorTypes: DropdownValue[] = [
88     {label: FilterConstraintHelper.convertToSymbol(ConstraintOperatorType.GREATER_THAN), value: ConstraintOperatorType.GREATER_THAN},
89     {label: FilterConstraintHelper.convertToSymbol(ConstraintOperatorType.LESS_THAN), value: ConstraintOperatorType.LESS_THAN},
90     {label: FilterConstraintHelper.convertToSymbol(ConstraintOperatorType.EQUAL), value: ConstraintOperatorType.EQUAL},
91     {label: FilterConstraintHelper.convertToSymbol(ConstraintOperatorType.GREATER_OR_EQUAL), value: ConstraintOperatorType.GREATER_OR_EQUAL},
92     {label: FilterConstraintHelper.convertToSymbol(ConstraintOperatorType.LESS_OR_EQUAL), value: ConstraintOperatorType.LESS_OR_EQUAL}
93   ];
94   lengthArray: string[] = [ConstraintOperatorType.LENGTH,
95       ConstraintOperatorType.MIN_LENGTH,
96       ConstraintOperatorType.MAX_LENGTH];
97
98   servicePropertyDropdownList: DropdownValue[];
99   isLoading: false;
100   selectedProperty: PropertyFEModel;
101   selectedSourceType: string;
102   componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
103   customToscaFunctions: Array<CustomToscaFunction>;
104   capabilityDropdownList: DropdownValue[] = [];
105   validValuesToscaFunctionList: ToscaFunction[];
106   rangeToscaFunctionList: ToscaFunction[];
107   overridingType = PROPERTY_TYPES.INTEGER;
108
109   SOURCE_TYPES = {
110     STATIC: {label: 'Static', value: SourceType.STATIC},
111     TOSCA_FUNCTION: {label: 'Tosca Function', value: SourceType.TOSCA_FUNCTION},
112     TOSCA_FUNCTION_LIST: {label: 'Tosca Function List', value: SourceType.TOSCA_FUNCTION_LIST}
113   };
114
115   constructor(private propertiesUtils: PropertiesUtils, private compositionService: CompositionService, private topologyTemplateService: TopologyTemplateService) {}
116
117   ngOnInit(): void {
118     if (this.compositionService.componentInstances) {
119       this.compositionService.componentInstances.forEach(value => {
120         this.componentInstanceMap.set(value.uniqueId, <InstanceFeDetails>{
121           name: value.name
122         });
123       });
124     }
125     this.initCustomToscaFunctions();
126     this.initCapabilityDropdown();
127     this.initCurrentRule();
128     this.initConstraintOperatorOptions();
129     this.initSelectedSourceType();
130     this.initPropertyDropdown();
131     this.syncRuleData();
132     this.generateRangeToscaFunctionList();
133   }
134
135   private initCustomToscaFunctions() {
136     this.customToscaFunctions = [];
137     this.topologyTemplateService.getDefaultCustomFunction().toPromise().then((data) => {
138         for (let customFunction of data) {
139             this.customToscaFunctions.push(new CustomToscaFunction(customFunction));
140         }
141     });
142 }
143
144   private initCapabilityDropdown(): void {
145     if (this.filterType == FilterType.CAPABILITY) {
146       this.capabilityDropdownList = [
147         new DropdownValue(undefined, 'Select'),
148         ...Array.from(this.capabilityNameAndPropertiesMap.keys()).map(capabilityName => new DropdownValue(capabilityName, capabilityName))
149       ];
150     }
151   }
152
153   private initPropertyDropdown(): void {
154     let propertyList: PropertyBEModel[] = [];
155     if (this.filterType == FilterType.CAPABILITY) {
156       if (this.currentRule.capabilityName) {
157         propertyList = this.capabilityNameAndPropertiesMap.get(this.currentRule.capabilityName);
158       }
159     } else {
160       propertyList = this.selectedInstanceProperties;
161     }
162     let selectLabel;
163     if (this.filterType == FilterType.CAPABILITY) {
164       selectLabel = this.currentRule.capabilityName ? 'Select' : 'Select a Capability';
165     } else {
166       selectLabel = 'Select';
167     }
168     this.servicePropertyDropdownList = [new DropdownValue(undefined, selectLabel), ...propertyList.map(prop => new DropdownValue(prop.name, prop.name)).sort((prop1, prop2) => prop1.value.localeCompare(prop2.value))];
169   }
170
171     private initConstraintOperatorOptions(): void {
172         if (!this.selectedProperty) {
173             this.operatorTypes = [this.setOperatorDropdownValue(undefined)];
174             return;
175         }
176         const operatorList: DropdownValue[] = [];
177         switch (true) {
178             case this.selectedProperty.type === PROPERTY_TYPES.RANGE:
179                 if (this.currentRule.constraintOperator !== ConstraintOperatorType.IN_RANGE) {
180                     this.currentRule.constraintOperator = ConstraintOperatorType.IN_RANGE;
181                 }
182                 this.operatorTypes = [this.setOperatorDropdownValue(ConstraintOperatorType.IN_RANGE)];
183                 break;
184             case this.selectedProperty.type === PROPERTY_TYPES.STRING:
185                 this.allowedOperators.forEach(constraintOperatorType =>
186                     operatorList.push(this.setOperatorDropdownValue(constraintOperatorType))
187                 );
188                 this.operatorTypes = operatorList;
189                 break;
190             case  this.selectedProperty.type != PROPERTY_TYPES.STRING &&
191             ((PROPERTY_DATA.SIMPLE_TYPES_COMPARABLE.indexOf(this.selectedProperty.type) > -1) ||
192             (PROPERTY_DATA.COMPARABLE_TYPES.indexOf(this.selectedProperty.type) > -1)):
193                 this.comparableAllowedOperators.forEach(constraintOperatorType =>
194                     operatorList.push(this.setOperatorDropdownValue(constraintOperatorType))
195                 );
196                 this.operatorTypes = operatorList;
197                 break;
198             case this.selectedProperty.type === PROPERTY_TYPES.LIST:
199                 this.listAllowedOperators.forEach(constraintOperatorType =>
200                     operatorList.push(this.setOperatorDropdownValue(constraintOperatorType))
201                 );
202                 this.operatorTypes = operatorList;
203                 break;
204             default:
205                 if (this.currentRule.constraintOperator !== ConstraintOperatorType.EQUAL) {
206                     this.currentRule.constraintOperator = ConstraintOperatorType.EQUAL;
207                 }
208                 this.operatorTypes = [this.setOperatorDropdownValue(ConstraintOperatorType.EQUAL)];
209                 break;
210         }
211     }
212
213     private setOperatorDropdownValue(constraintOperatorType: ConstraintOperatorType) {
214         if (constraintOperatorType === undefined) {
215             return new DropdownValue(undefined, 'Select a Property');
216         }
217         return new DropdownValue(constraintOperatorType, FilterConstraintHelper.convertToSymbol(constraintOperatorType));
218     }
219
220     private initSelectedSourceType(): void {
221     if (!this.currentRule.sourceType || this.currentRule.sourceType === SourceType.STATIC) {
222       this.selectedSourceType = SourceType.STATIC;
223     } else {
224         if (!this.isValidValuesOperator() && !this.isRangeType() && !this.isInRangeOperator()){
225           this.selectedSourceType = SourceType.TOSCA_FUNCTION;
226         }
227         else {
228           this.selectedSourceType = SourceType.TOSCA_FUNCTION_LIST;
229         }
230     }
231   }
232
233   private initCurrentRule(): void {
234       let propertyList: PropertyBEModel[] = [];
235       if (this.filterType == FilterType.CAPABILITY) {
236           if (this.currentRule.capabilityName) {
237               propertyList = this.capabilityNameAndPropertiesMap.get(this.currentRule.capabilityName);
238           }
239       } else {
240           propertyList = this.selectedInstanceProperties;
241       }
242     if (this.filterConstraint) {
243         this.filterConstraint.originalType = propertyList.find(prop=>prop.name==this.filterConstraint.servicePropertyName).type;
244       this.currentRule = new PropertyFilterConstraintUi(this.filterConstraint);
245     } else {
246       this.currentRule = new PropertyFilterConstraintUi({
247         sourceName: SourceType.STATIC,
248         sourceType: SourceType.STATIC,
249         constraintOperator: ConstraintOperatorType.EQUAL,
250         value: undefined,
251         originalType: undefined
252       });
253     }
254   }
255
256   onCapabilityChange(): void {
257     this.initPropertyDropdown();
258     this.resetSelectedProperty();
259   }
260
261   onPropertyChange(): void {
262     this.currentRule.value = undefined;
263     this.onValueChange(false);
264     this.updateSelectedProperty();
265     this.initConstraintOperatorOptions();
266   }
267
268   syncRuleData(): void {
269     if (!this.currentRule.sourceName || this.currentRule.sourceType === SourceType.STATIC) {
270       this.currentRule.sourceName = SourceType.STATIC;
271       this.currentRule.sourceType = SourceType.STATIC;
272     }
273     this.initSelectedProperty();
274     this.initConstraintOperatorOptions();
275   }
276
277   onValueChange(isValidValue:any): void {
278     this.currentRule.updateValidity(isValidValue);
279   }
280
281   checkFormValidForSubmit(): boolean {
282     return this.currentRule.isValidRule();
283   }
284
285   initSelectedProperty(): void {
286     if (!this.currentRule.servicePropertyName) {
287       this.selectedProperty = undefined;
288       return;
289     }
290     let newProperty;
291     if (this.filterType === FilterType.CAPABILITY) {
292       const currentProperty = this.capabilityNameAndPropertiesMap.get(this.currentRule.capabilityName)
293         .find(property => property.name === this.currentRule.servicePropertyName);
294       newProperty = new PropertyFEModel(currentProperty);
295     } else {
296       newProperty = new PropertyFEModel(this.selectedInstanceProperties.find(property => property.name === this.currentRule.servicePropertyName));
297     }
298     newProperty.value = undefined;
299     newProperty.toscaFunction = undefined;
300
301     if (typeof this.currentRule.value === 'string') {
302       newProperty.value = this.currentRule.value;
303       this.propertiesUtils.initValueObjectRef(newProperty);
304     } else if (ToscaFunctionHelper.isValueToscaFunction(this.currentRule.value)) {
305       newProperty.toscaFunction = ToscaFunctionHelper.convertObjectToToscaFunction(this.currentRule.value);
306       newProperty.value = newProperty.toscaFunction.buildValueString();
307     } else if (Array.isArray(this.currentRule.value) &&
308         typeof this.currentRule.value[0] === "object" &&
309         this.currentRule.value[0]['propertySource'] != undefined) {
310             this.validValuesToscaFunctionList = this.currentRule.value;
311             this.rangeToscaFunctionList = this.currentRule.value;
312             newProperty.toscaFunction = this.currentRule.value;
313     } else {
314       newProperty.value = JSON.stringify(this.currentRule.value);
315       this.propertiesUtils.initValueObjectRef(newProperty);
316     }
317
318     this.selectedProperty = newProperty;
319       this.currentRule.originalType = this.selectedProperty.type;
320   }
321
322   updateSelectedProperty(): void {
323     this.selectedProperty = undefined;
324     if (!this.currentRule.servicePropertyName) {
325       return;
326     }
327
328     let newProperty;
329     if (this.filterType === FilterType.CAPABILITY) {
330       const currentProperty = this.capabilityNameAndPropertiesMap.get(this.currentRule.capabilityName)
331         .find(property => property.name === this.currentRule.servicePropertyName);
332       newProperty = new PropertyFEModel(currentProperty);
333     } else {
334       newProperty = new PropertyFEModel(this.selectedInstanceProperties.find(property => property.name === this.currentRule.servicePropertyName));
335     }
336     newProperty.value = undefined;
337     newProperty.toscaFunction = undefined;
338
339     this.propertiesUtils.initValueObjectRef(newProperty);
340     this.selectedProperty = newProperty;
341     this.currentRule.originalType = this.selectedProperty.type;
342   }
343
344   isStaticSource(): boolean {
345     return this.selectedSourceType === SourceType.STATIC
346   }
347
348   isToscaFunctionSource(): boolean {
349     return this.selectedSourceType === SourceType.TOSCA_FUNCTION
350   }
351
352   isToscaFunctionListSource(): boolean {
353     return this.selectedSourceType === SourceType.TOSCA_FUNCTION_LIST
354   }
355
356   isComplexListMapType(): boolean {
357     return this.selectedProperty && this.selectedProperty.derivedDataType > 0;
358   }
359
360   isRangeType(): boolean {
361     return this.selectedProperty && this.selectedProperty.derivedDataType == 4;
362   }
363
364   isLengthOperator(): boolean {
365       return this.lengthArray.indexOf(this.currentRule.constraintOperator) > -1;
366   }
367
368   isInRangeOperator(): boolean {
369     return this.currentRule.constraintOperator && this.currentRule.constraintOperator === ConstraintOperatorType.IN_RANGE;
370   }
371
372   isValidValuesOperator(): boolean {
373     return this.currentRule.constraintOperator && this.currentRule.constraintOperator === ConstraintOperatorType.VALID_VALUES;
374   }
375
376   updateComplexListMapTypeRuleValue(): void {
377     this.currentRule.value = PropertyFEModel.cleanValueObj(this.selectedProperty.valueObj);
378     this.onValueChange(this.selectedProperty.valueObjIsValid);
379   }
380
381   onToscaFunctionValidityChange(validationEvent: ToscaFunctionValidationEvent): void {
382     if (validationEvent.isValid && validationEvent.toscaFunction) {
383         if (this.isValidValuesOperator()) {
384             this.currentRule.value = this.validValuesToscaFunctionList;
385             this.currentRule.sourceType = SourceType.TOSCA_FUNCTION_LIST;
386             if (validationEvent.toscaFunction instanceof ToscaGetFunction) {
387                 this.currentRule.sourceName = SourceType.TOSCA_FUNCTION_LIST;
388             }
389         }
390         else {
391             if (this.isLengthOperator()) {
392                 this.overridingType = PROPERTY_TYPES.INTEGER;
393             }
394             this.currentRule.value = validationEvent.toscaFunction;
395             this.currentRule.sourceType = validationEvent.toscaFunction.type
396             if (validationEvent.toscaFunction instanceof ToscaGetFunction) {
397                 this.currentRule.sourceName = validationEvent.toscaFunction.sourceName;
398             }
399         }
400     } else {
401       this.currentRule.updateValidity(false);
402       this.currentRule.value = undefined;
403       this.currentRule.sourceType = undefined;
404       this.currentRule.sourceName = undefined;
405     }
406   }
407
408     onToscaFunctionListValidityChange(validationEvent: ToscaFunctionValidationEvent, valueIndex: number): void {
409         if (validationEvent.isValid && validationEvent.toscaFunction) {
410             this.validValuesToscaFunctionList.splice(this.validValuesToscaFunctionList.length -1, 1, validationEvent.toscaFunction);
411             this.currentRule.value = this.validValuesToscaFunctionList;
412             this.currentRule.sourceType = 'SEVERAL';
413             if (validationEvent.toscaFunction instanceof ToscaGetFunction) {
414                 this.currentRule.sourceName = validationEvent.toscaFunction.sourceName;
415             }
416         } else {
417             this.currentRule.updateValidity(false);
418             this.currentRule.value = undefined;
419             this.currentRule.sourceType = undefined;
420             this.currentRule.sourceName = undefined;
421         }
422     }
423
424     onToscaRangeFunctionListValidityChange(validationEvent: ToscaFunctionValidationEvent, valueIndex: number): void {
425         if (validationEvent.isValid && validationEvent.toscaFunction) {
426             this.rangeToscaFunctionList.splice(valueIndex, 1, validationEvent.toscaFunction);
427             this.currentRule.value = this.rangeToscaFunctionList;
428             this.currentRule.sourceType = 'SEVERAL';
429             if (validationEvent.toscaFunction instanceof ToscaGetFunction) {
430                 this.currentRule.sourceName = validationEvent.toscaFunction.sourceName;
431             }
432         } else {
433             this.currentRule.updateValidity(false);
434             this.currentRule.value = undefined;
435             this.currentRule.sourceType = undefined;
436             this.currentRule.sourceName = undefined;
437         }
438     }
439
440   onSourceTypeChange(): void {
441     this.currentRule.value = undefined;
442     if (!this.isStaticSource() && (this.isValidValuesOperator() || this.isRangeType() || this.isInRangeOperator())) {
443         this.selectedSourceType = SourceType.TOSCA_FUNCTION_LIST;
444     }
445     this.currentRule.sourceType = this.selectedSourceType;
446     if (this.isStaticSource()) {
447       this.currentRule.sourceName = SourceType.STATIC;
448     }
449     if (this.isToscaFunctionListSource()) {
450       this.currentRule.sourceName = SourceType.TOSCA_FUNCTION_LIST;
451     }
452     this.updateSelectedProperty();
453   }
454
455   private resetSelectedProperty(): void {
456     this.currentRule.servicePropertyName = undefined;
457     this.selectedProperty = undefined;
458     this.onPropertyChange();
459   }
460
461   addToList(){
462       if (!this.validValuesToscaFunctionList) {
463           this.validValuesToscaFunctionList = new Array();
464       }
465       this.validValuesToscaFunctionList.push(ToscaFunctionHelper.convertObjectToToscaFunction(undefined));
466   }
467
468   generateRangeToscaFunctionList() {
469       if (!this.rangeToscaFunctionList) {
470           this.rangeToscaFunctionList = new Array();
471           this.rangeToscaFunctionList.push(ToscaFunctionHelper.convertObjectToToscaFunction(undefined));
472           this.rangeToscaFunctionList.push(ToscaFunctionHelper.convertObjectToToscaFunction(undefined));
473       }
474   }
475
476   trackByFn(index) {
477     return index;
478   }
479
480   removeFromList(valueIndex: number){
481     this.validValuesToscaFunctionList.splice(valueIndex, 1);
482       this.currentRule.updateValidity(!this.doesArrayContainsEmptyValues(this.validValuesToscaFunctionList) && !(this.validValuesToscaFunctionList.length === 0));
483       if (this.doesArrayContainsEmptyValues(this.validValuesToscaFunctionList) || (this.validValuesToscaFunctionList.length === 0)) {
484           this.currentRule.value = undefined;
485           this.currentRule.sourceType = undefined;
486           this.currentRule.sourceName = undefined;
487       }
488   }
489
490   private doesArrayContainsEmptyValues(arr) {
491     for(const element of arr) {
492       if(element === undefined) return true;
493     }
494       return false;
495   }
496 }
497
498 export enum FilterType {
499   CAPABILITY,
500   PROPERTY
501 }