2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 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
9 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
20 import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
21 import {AttributeBEModel, ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel, PropertyDeclareAPIModel, DerivedFEProperty} from 'app/models';
22 import {TopologyTemplateService} from "../../../../services/component-services/topology-template.service";
23 import {WorkspaceService} from "../../../workspace/workspace.service";
24 import {PropertiesService} from "../../../../services/properties.service";
25 import {PROPERTY_DATA, PROPERTY_TYPES} from "../../../../../utils/constants";
26 import {DataTypeService} from "../../../../services/data-type.service";
27 import {ToscaGetFunctionType} from "../../../../../models/tosca-get-function-type";
28 import {TranslateService} from "../../../../shared/translator/translate.service";
29 import {ComponentGenericResponse} from '../../../../services/responses/component-generic-response';
30 import {Observable} from 'rxjs/Observable';
31 import {PropertySource} from "../../../../../models/property-source";
32 import {InstanceFeDetails} from "../../../../../models/instance-fe-details";
33 import {ToscaGetFunction} from "../../../../../models/tosca-get-function";
34 import {FormControl, FormGroup, Validators} from "@angular/forms";
35 import {ToscaGetFunctionTypeConverter} from "../../../../../models/tosca-get-function-type-converter";
38 selector: 'app-tosca-get-function',
39 templateUrl: './tosca-get-function.component.html',
40 styleUrls: ['./tosca-get-function.component.less']
42 export class ToscaGetFunctionComponent implements OnInit, OnChanges {
44 @Input() property: PropertyBEModel;
45 @Input() overridingType: PROPERTY_TYPES;
46 @Input() toscaGetFunction: ToscaGetFunction;
47 @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
48 @Input() functionType: ToscaGetFunctionType;
49 @Input() compositionMap: boolean;
50 @Input() compositionMapKey: string;
51 @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>();
52 @Output() onValidityChange: EventEmitter<ToscaGetFunctionValidationEvent> = new EventEmitter<ToscaGetFunctionValidationEvent>();
54 formGroup: FormGroup = new FormGroup({
55 'selectedProperty': new FormControl(undefined, Validators.required),
56 'propertySource': new FormControl(undefined, Validators.required),
57 'toscaIndex' : new FormControl(undefined)
60 isLoading: boolean = false;
61 propertyDropdownList: Array<PropertyDropdownValue> = [];
62 propertySourceList: Array<string> = [];
63 instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
64 dropdownValuesLabel: string;
65 dropDownErrorMsg: string;
66 toscaIndexFlag: boolean = false;
68 private isInitialized: boolean = false;
69 private componentMetadata: ComponentMetadata;
71 constructor(private topologyTemplateService: TopologyTemplateService,
72 private workspaceService: WorkspaceService,
73 private propertiesService: PropertiesService,
74 private dataTypeService: DataTypeService,
75 private translateService: TranslateService) {
79 this.componentMetadata = this.workspaceService.metadata;
80 this.formGroup.valueChanges.subscribe(() => {
81 if (!this.isInitialized) {
84 let formGroupStatus : boolean = this.formGroup.valid;
85 const selectedProperty: PropertyDropdownValue = this.formGroup.value.selectedProperty;
86 if (selectedProperty != null && selectedProperty.isList && formGroupStatus
87 && (this.toscaIndex.value == null || this.toscaIndex.value == '')) {
88 formGroupStatus = false;
90 this.onValidityChange.emit({
91 isValid: formGroupStatus,
92 toscaGetFunction: this.formGroup.valid ? this.buildGetFunctionFromForm() : undefined
94 if (this.formGroup.valid) {
95 this.onValidFunction.emit(this.buildGetFunctionFromForm());
98 this.loadPropertySourceDropdown();
99 this.loadPropertyDropdownLabel();
100 this.initToscaGetFunction().subscribe(() => {
101 this.isInitialized = true;
106 ngOnChanges(_changes: SimpleChanges): void {
107 if (!this.isInitialized) {
110 this.isInitialized = false;
112 this.loadPropertySourceDropdown();
113 this.loadPropertyDropdownLabel();
114 this.initToscaGetFunction().subscribe(() => {
115 this.isInitialized = true;
119 private initToscaGetFunction(): Observable<void> {
120 return new Observable(subscriber => {
121 if (!this.toscaGetFunction) {
122 if (this.isGetInput()) {
123 this.setSelfPropertySource();
124 this.loadPropertyDropdown();
129 if (this.toscaGetFunction.propertySource == PropertySource.SELF) {
130 this.propertySource.setValue(PropertySource.SELF);
131 } else if (this.toscaGetFunction.propertySource == PropertySource.INSTANCE) {
133 .setValue(this.propertySourceList.find(source => this.toscaGetFunction.sourceName === source));
135 if (this.propertySource.valid) {
136 this.loadPropertyDropdown(() => {
137 this.selectedProperty
138 .setValue(this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName));
144 if (this.toscaGetFunction.toscaIndex != null) {
145 this.toscaIndexFlag = true;
146 this.toscaIndex.setValue(this.toscaGetFunction.toscaIndex);
151 private buildGetFunctionFromForm() {
152 const toscaGetFunction = new ToscaGetFunction();
153 toscaGetFunction.type = ToscaGetFunctionTypeConverter.convertToToscaFunctionType(this.functionType);
154 toscaGetFunction.functionType = this.functionType;
155 const propertySource = this.propertySource.value;
156 if (this.isPropertySourceSelf()) {
157 toscaGetFunction.propertySource = propertySource
158 toscaGetFunction.sourceName = this.componentMetadata.name;
159 toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
161 toscaGetFunction.propertySource = PropertySource.INSTANCE;
162 toscaGetFunction.sourceName = propertySource;
163 toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(propertySource);
166 const selectedProperty: PropertyDropdownValue = this.selectedProperty.value;
167 toscaGetFunction.propertyUniqueId = selectedProperty.propertyId;
168 toscaGetFunction.propertyName = selectedProperty.propertyName;
169 toscaGetFunction.propertyPathFromSource = selectedProperty.propertyPath;
170 toscaGetFunction.toscaIndex = this.toscaIndex.value;
172 return toscaGetFunction;
175 private loadPropertySourceDropdown(): void {
176 if (this.isGetInput()) {
179 this.propertySourceList = [];
180 this.propertySourceList.push(PropertySource.SELF);
181 this.componentInstanceMap.forEach((value, key) => {
182 const instanceName = value.name;
183 this.instanceNameAndIdMap.set(instanceName, key);
184 if (instanceName !== PropertySource.SELF) {
185 this.addToPropertySource(instanceName);
190 private addToPropertySource(source: string): void {
191 this.propertySourceList.push(source);
192 this.propertySourceList.sort((a, b) => {
193 if (a === PropertySource.SELF) {
195 } else if (b === PropertySource.SELF) {
199 return a.localeCompare(b);
203 private loadPropertyDropdown(onComplete?: () => any): void {
204 this.loadPropertyDropdownLabel();
205 this.loadPropertyDropdownValues(onComplete);
208 private resetForm(): void {
209 this.formGroup.reset();
212 private loadPropertyDropdownLabel(): void {
213 if (!this.functionType) {
216 if (this.isGetInput()) {
217 this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
218 } else if (this.isGetProperty()) {
219 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
220 } else if (this.isGetAttribute()) {
221 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL');
225 private loadPropertyDropdownValues(onComplete?: () => any): void {
226 if (!this.functionType) {
229 this.resetPropertyDropdown();
230 this.fillPropertyDropdownValues(onComplete);
233 private resetPropertyDropdown(): void {
234 this.dropDownErrorMsg = undefined;
235 this.selectedProperty.reset();
236 this.toscaIndex.reset();
237 this.propertyDropdownList = [];
240 private fillPropertyDropdownValues(onComplete?: () => any): void {
242 const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
243 propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
244 const properties: Array<PropertyBEModel | AttributeBEModel> = this.extractProperties(response);
245 if (!properties || properties.length === 0) {
246 const msgCode = this.getNotFoundMsgCode();
247 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.overridingType != undefined ? this.overridingType : this.propertyTypeToString()});
250 this.addPropertiesToDropdown(properties);
251 if (this.propertyDropdownList.length == 0) {
252 const msgCode = this.getNotFoundMsgCode();
253 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.overridingType != undefined ? this.overridingType : this.propertyTypeToString()});
256 console.error('An error occurred while loading properties.', error);
266 private getNotFoundMsgCode(): string {
267 if (this.isGetInput()) {
268 return 'TOSCA_FUNCTION_NO_INPUT_FOUND';
270 if (this.isGetAttribute()) {
271 return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND';
273 if (this.isGetProperty()) {
274 return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
280 private propertyTypeToString() {
281 if (this.isSubProperty()){
282 if ((this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel> this.property).input instanceof DerivedFEProperty)
283 || this.compositionMap) {
284 if(this.isComplexType(this.property.schemaType) && !this.compositionMap){
285 let mapChildProp : DerivedFEProperty = (<DerivedFEProperty> (<PropertyDeclareAPIModel> this.property).input);
286 let propertySchemaType = mapChildProp.type;
287 if (this.property.type == PROPERTY_TYPES.MAP || propertySchemaType == PROPERTY_TYPES.MAP) {
288 if (mapChildProp.mapKey != '' && mapChildProp.mapKey != null && mapChildProp.schema.property.type != null) {
289 propertySchemaType = mapChildProp.schema.property.type;
292 if ((propertySchemaType == PROPERTY_TYPES.MAP || (propertySchemaType == PROPERTY_TYPES.LIST && mapChildProp.schema.property.type == PROPERTY_TYPES.MAP))
293 && mapChildProp.isChildOfListOrMap) {
294 propertySchemaType = PROPERTY_TYPES.STRING;
296 return propertySchemaType;
298 return this.property.schema.property.type;
301 return this.getType((<PropertyDeclareAPIModel>this.property).propertiesName.split("#").slice(1), this.property.type);
303 if (this.property.schemaType) {
304 return `${this.property.type} of ${this.property.schemaType}`;
306 return this.property.type;
309 private isSubProperty(): boolean{
310 return this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>this.property).propertiesName && (<PropertyDeclareAPIModel>this.property).propertiesName.length > 1;
313 private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeBEModel> {
314 if (this.isGetInput()) {
315 return componentGenericResponse.inputs;
317 const instanceId = this.instanceNameAndIdMap.get(this.propertySource.value);
318 if (this.isGetProperty()) {
319 if (this.isPropertySourceSelf()) {
320 return componentGenericResponse.properties;
322 return this.removeSelectedProperty(componentGenericResponse.componentInstancesProperties[instanceId]);
324 if (this.isPropertySourceSelf()) {
325 return [...(componentGenericResponse.attributes || []), ...(componentGenericResponse.properties || [])];
327 return [...(componentGenericResponse.componentInstancesAttributes[instanceId] || []),
328 ...(componentGenericResponse.componentInstancesProperties[instanceId] || [])];
331 private isPropertySourceSelf() {
332 return this.propertySource.value === PropertySource.SELF;
335 private getPropertyObservable(): Observable<ComponentGenericResponse> {
336 if (this.isGetInput()) {
337 return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
339 if (this.isGetProperty()) {
340 if (this.isPropertySourceSelf()) {
341 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
343 return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
345 if (this.isGetAttribute()) {
346 if (this.isPropertySourceSelf()) {
347 return this.topologyTemplateService.findAllComponentAttributesAndProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
349 return this.topologyTemplateService.getComponentInstanceAttributesAndProperties(this.componentMetadata.uniqueId, this.componentMetadata.componentType);
353 private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
354 if (!componentInstanceProperties) {
357 return componentInstanceProperties.filter(property =>
358 (property.uniqueId !== this.property.uniqueId) ||
359 (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
363 private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
364 this.propertyDropdownList.push(propertyDropdownValue);
365 this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
368 private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeBEModel>): void {
369 for (const property of properties) {
370 if (this.hasSameType(property)) {
371 this.addPropertyToDropdown({
372 propertyName: property.name,
373 propertyId: property.uniqueId,
374 propertyLabel: property.name,
375 propertyPath: [property.name],
376 isList: property.type === PROPERTY_TYPES.LIST
378 } else if (this.isComplexType(property.type)) {
379 this.fillPropertyDropdownWithMatchingChildProperties(property);
384 private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeBEModel,
385 parentPropertyList: Array<PropertyBEModel | AttributeBEModel> = []): void {
386 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
387 if (!dataTypeFound || !dataTypeFound.properties) {
390 parentPropertyList.push(inputProperty);
391 dataTypeFound.properties.forEach(dataTypeProperty => {
392 if (this.hasSameType(dataTypeProperty)) {
393 this.addPropertyToDropdown({
394 propertyName: dataTypeProperty.name,
395 propertyId: parentPropertyList[0].uniqueId,
396 propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
397 propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name],
398 isList : dataTypeProperty.type === PROPERTY_TYPES.LIST
400 } else if (this.isComplexType(dataTypeProperty.type)) {
401 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
406 private hasSameType(property: PropertyBEModel | AttributeBEModel): boolean {
407 if (this.overridingType != undefined) {
408 return property.type === this.overridingType;
410 if (this.property.type === PROPERTY_TYPES.ANY) {
413 let validPropertyType = property.type === PROPERTY_TYPES.LIST ? property.schemaType : property.type;
414 if (this.typeHasSchema(this.property.type)) {
415 if ((this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel> this.property).input instanceof DerivedFEProperty) || this.compositionMap) {
416 let childObject : DerivedFEProperty = (<DerivedFEProperty>(<PropertyDeclareAPIModel> this.property).input);
417 let childSchemaType = this.property.schemaType != null ? this.property.schemaType : childObject.type;
418 if(this.isComplexType(childSchemaType) && !this.compositionMap){
419 if (childObject.type == PROPERTY_TYPES.MAP && childObject.isChildOfListOrMap) {
420 return validPropertyType === PROPERTY_TYPES.STRING;
422 return validPropertyType === childObject.type;
424 return validPropertyType === this.property.schema.property.type;
427 if (!property.schema || !property.schema.property) {
430 return validPropertyType === this.property.type && this.property.schema.property.type === property.schema.property.type;
432 if (this.property.schema.property.isDataType && this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>this.property).propertiesName){
433 let typeToMatch = (<PropertyDeclareAPIModel> this.property).input.type;
434 let childObject : DerivedFEProperty = (<DerivedFEProperty>(<PropertyDeclareAPIModel> this.property).input);
435 if (childObject.type == PROPERTY_TYPES.MAP && childObject.isChildOfListOrMap) {
436 typeToMatch = PROPERTY_TYPES.STRING;
438 return validPropertyType === typeToMatch;
441 return validPropertyType === this.property.type;
444 private getType(propertyPath:string[], type: string): string {
445 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, type);
446 let nestedProperty = dataTypeFound.properties.find(property => property.name === propertyPath[0]);
447 if (propertyPath.length === 1){
448 return nestedProperty.type;
450 return this.getType(propertyPath.slice(1), nestedProperty.type);
453 private isGetProperty(): boolean {
454 return this.functionType === ToscaGetFunctionType.GET_PROPERTY;
457 private isGetAttribute(): boolean {
458 return this.functionType === ToscaGetFunctionType.GET_ATTRIBUTE;
461 private isGetInput(): boolean {
462 return this.functionType === ToscaGetFunctionType.GET_INPUT;
465 private isComplexType(propertyType: string): boolean {
466 return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
469 private typeHasSchema(propertyType: string): boolean {
470 return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType;
473 private stopLoading(): void {
474 this.isLoading = false;
477 private startLoading(): void {
478 this.isLoading = true;
481 showPropertyDropdown(): boolean {
482 if (this.isGetProperty() || this.isGetAttribute()) {
483 return this.propertySource.valid && !this.isLoading && !this.dropDownErrorMsg;
486 return this.functionType && !this.isLoading && !this.dropDownErrorMsg;
489 onPropertySourceChange(): void {
490 this.selectedProperty.reset();
491 this.toscaIndex.reset();
492 if (!this.functionType || !this.propertySource.valid) {
495 this.loadPropertyDropdown();
498 onPropertyValueChange(): void {
499 this.toscaIndexFlag = false;
500 this.toscaIndex.reset();
501 const selectedProperty: PropertyDropdownValue = this.selectedProperty.value;
502 if (selectedProperty.isList) {
503 this.toscaIndexFlag = true;
507 indexTokenChange(): void {
508 if ((this.toscaIndex.value).toLowerCase() === 'index') {
511 let indexTokenValue = Number(this.toscaIndex.value);
512 if (isNaN(indexTokenValue)) {
513 this.toscaIndex.reset();
517 showPropertySourceDropdown(): boolean {
518 return this.isGetProperty() || this.isGetAttribute();
521 private setSelfPropertySource(): void {
522 this.propertySource.setValue(PropertySource.SELF);
525 private get propertySource(): FormControl {
526 return this.formGroup.get('propertySource') as FormControl;
529 private get selectedProperty(): FormControl {
530 return this.formGroup.get('selectedProperty') as FormControl;
533 private get toscaIndex(): FormControl {
534 return this.formGroup.get('toscaIndex') as FormControl;
539 export interface PropertyDropdownValue {
540 propertyName: string;
542 propertyLabel: string;
543 propertyPath: Array<string>;
547 export interface ToscaGetFunctionValidationEvent {
549 toscaGetFunction: ToscaGetFunction,