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() toscaGetFunction: ToscaGetFunction;
46 @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
47 @Input() functionType: ToscaGetFunctionType;
48 @Input() compositionMap: boolean;
49 @Input() compositionMapKey: string;
50 @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>();
51 @Output() onValidityChange: EventEmitter<ToscaGetFunctionValidationEvent> = new EventEmitter<ToscaGetFunctionValidationEvent>();
53 formGroup: FormGroup = new FormGroup({
54 'selectedProperty': new FormControl(undefined, Validators.required),
55 'propertySource': new FormControl(undefined, Validators.required)
58 isLoading: boolean = false;
59 propertyDropdownList: Array<PropertyDropdownValue> = [];
60 propertySourceList: Array<string> = [];
61 instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
62 dropdownValuesLabel: string;
63 dropDownErrorMsg: string;
65 private isInitialized: boolean = false;
66 private componentMetadata: ComponentMetadata;
68 constructor(private topologyTemplateService: TopologyTemplateService,
69 private workspaceService: WorkspaceService,
70 private propertiesService: PropertiesService,
71 private dataTypeService: DataTypeService,
72 private translateService: TranslateService) {
76 this.componentMetadata = this.workspaceService.metadata;
77 this.formGroup.valueChanges.subscribe(() => {
78 if (!this.isInitialized) {
81 this.onValidityChange.emit({
82 isValid: this.formGroup.valid,
83 toscaGetFunction: this.formGroup.valid ? this.buildGetFunctionFromForm() : undefined
85 if (this.formGroup.valid) {
86 this.onValidFunction.emit(this.buildGetFunctionFromForm());
89 this.loadPropertySourceDropdown();
90 this.loadPropertyDropdownLabel();
91 this.initToscaGetFunction().subscribe(() => {
92 this.isInitialized = true;
97 ngOnChanges(_changes: SimpleChanges): void {
98 if (!this.isInitialized) {
101 this.isInitialized = false;
103 this.loadPropertySourceDropdown();
104 this.loadPropertyDropdownLabel();
105 this.initToscaGetFunction().subscribe(() => {
106 this.isInitialized = true;
110 private initToscaGetFunction(): Observable<void> {
111 return new Observable(subscriber => {
112 if (!this.toscaGetFunction) {
113 if (this.isGetInput()) {
114 this.setSelfPropertySource();
115 this.loadPropertyDropdown();
120 if (this.toscaGetFunction.propertySource == PropertySource.SELF) {
121 this.propertySource.setValue(PropertySource.SELF);
122 } else if (this.toscaGetFunction.propertySource == PropertySource.INSTANCE) {
124 .setValue(this.propertySourceList.find(source => this.toscaGetFunction.sourceName === source));
126 if (this.propertySource.valid) {
127 this.loadPropertyDropdown(() => {
128 this.selectedProperty
129 .setValue(this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName));
138 private buildGetFunctionFromForm() {
139 const toscaGetFunction = new ToscaGetFunction();
140 toscaGetFunction.type = ToscaGetFunctionTypeConverter.convertToToscaFunctionType(this.functionType);
141 toscaGetFunction.functionType = this.functionType;
142 const propertySource = this.propertySource.value;
143 if (this.isPropertySourceSelf()) {
144 toscaGetFunction.propertySource = propertySource
145 toscaGetFunction.sourceName = this.componentMetadata.name;
146 toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
148 toscaGetFunction.propertySource = PropertySource.INSTANCE;
149 toscaGetFunction.sourceName = propertySource;
150 toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(propertySource);
153 const selectedProperty: PropertyDropdownValue = this.selectedProperty.value;
154 toscaGetFunction.propertyUniqueId = selectedProperty.propertyId;
155 toscaGetFunction.propertyName = selectedProperty.propertyName;
156 toscaGetFunction.propertyPathFromSource = selectedProperty.propertyPath;
158 return toscaGetFunction;
161 private loadPropertySourceDropdown(): void {
162 if (this.isGetInput()) {
165 this.propertySourceList = [];
166 this.propertySourceList.push(PropertySource.SELF);
167 this.componentInstanceMap.forEach((value, key) => {
168 const instanceName = value.name;
169 this.instanceNameAndIdMap.set(instanceName, key);
170 if (instanceName !== PropertySource.SELF) {
171 this.addToPropertySource(instanceName);
176 private addToPropertySource(source: string): void {
177 this.propertySourceList.push(source);
178 this.propertySourceList.sort((a, b) => {
179 if (a === PropertySource.SELF) {
181 } else if (b === PropertySource.SELF) {
185 return a.localeCompare(b);
189 private loadPropertyDropdown(onComplete?: () => any): void {
190 this.loadPropertyDropdownLabel();
191 this.loadPropertyDropdownValues(onComplete);
194 private resetForm(): void {
195 this.formGroup.reset();
198 private loadPropertyDropdownLabel(): void {
199 if (!this.functionType) {
202 if (this.isGetInput()) {
203 this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
204 } else if (this.isGetProperty()) {
205 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
206 } else if (this.isGetAttribute()) {
207 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL');
211 private loadPropertyDropdownValues(onComplete?: () => any): void {
212 if (!this.functionType) {
215 this.resetPropertyDropdown();
216 this.fillPropertyDropdownValues(onComplete);
219 private resetPropertyDropdown(): void {
220 this.dropDownErrorMsg = undefined;
221 this.selectedProperty.reset();
222 this.propertyDropdownList = [];
225 private fillPropertyDropdownValues(onComplete?: () => any): void {
227 const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
228 propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
229 const properties: Array<PropertyBEModel | AttributeBEModel> = this.extractProperties(response);
230 if (!properties || properties.length === 0) {
231 const msgCode = this.getNotFoundMsgCode();
232 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
235 this.addPropertiesToDropdown(properties);
236 if (this.propertyDropdownList.length == 0) {
237 const msgCode = this.getNotFoundMsgCode();
238 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
241 console.error('An error occurred while loading properties.', error);
251 private getNotFoundMsgCode(): string {
252 if (this.isGetInput()) {
253 return 'TOSCA_FUNCTION_NO_INPUT_FOUND';
255 if (this.isGetAttribute()) {
256 return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND';
258 if (this.isGetProperty()) {
259 return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
265 private propertyTypeToString() {
266 if (this.isSubProperty()){
267 if ((this.typeHasSchema(this.property.type) && this.property instanceof PropertyDeclareAPIModel &&
268 (<PropertyDeclareAPIModel> this.property).input instanceof DerivedFEProperty) || this.compositionMap) {
269 if(this.isComplexType(this.property.schemaType) && !this.compositionMap){
270 let propertySchemaType = (<PropertyDeclareAPIModel> this.property).input.type;
271 return propertySchemaType == PROPERTY_TYPES.MAP ? PROPERTY_TYPES.STRING : propertySchemaType;
273 return this.property.schema.property.type;
276 return this.getType((<PropertyDeclareAPIModel>this.property).propertiesName.split("#").slice(1), this.property.type);
278 if (this.property.schemaType) {
279 return `${this.property.type} of ${this.property.schemaType}`;
281 return this.property.type;
284 private isSubProperty(): boolean{
285 return this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>this.property).propertiesName && (<PropertyDeclareAPIModel>this.property).propertiesName.length > 1;
288 private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeBEModel> {
289 if (this.isGetInput()) {
290 return componentGenericResponse.inputs;
292 const instanceId = this.instanceNameAndIdMap.get(this.propertySource.value);
293 if (this.isGetProperty()) {
294 if (this.isPropertySourceSelf()) {
295 return componentGenericResponse.properties;
297 return this.removeSelectedProperty(componentGenericResponse.componentInstancesProperties[instanceId]);
299 if (this.isPropertySourceSelf()) {
300 return [...(componentGenericResponse.attributes || []), ...(componentGenericResponse.properties || [])];
302 return [...(componentGenericResponse.componentInstancesAttributes[instanceId] || []),
303 ...(componentGenericResponse.componentInstancesProperties[instanceId] || [])];
306 private isPropertySourceSelf() {
307 return this.propertySource.value === PropertySource.SELF;
310 private getPropertyObservable(): Observable<ComponentGenericResponse> {
311 if (this.isGetInput()) {
312 return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
314 if (this.isGetProperty()) {
315 if (this.isPropertySourceSelf()) {
316 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
318 return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
320 if (this.isGetAttribute()) {
321 if (this.isPropertySourceSelf()) {
322 return this.topologyTemplateService.findAllComponentAttributesAndProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
324 return this.topologyTemplateService.getComponentInstanceAttributesAndProperties(this.componentMetadata.uniqueId, this.componentMetadata.componentType);
328 private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
329 if (!componentInstanceProperties) {
332 return componentInstanceProperties.filter(property =>
333 (property.uniqueId !== this.property.uniqueId) ||
334 (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
338 private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
339 this.propertyDropdownList.push(propertyDropdownValue);
340 this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
343 private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeBEModel>): void {
344 for (const property of properties) {
345 if (this.hasSameType(property)) {
346 this.addPropertyToDropdown({
347 propertyName: property.name,
348 propertyId: property.uniqueId,
349 propertyLabel: property.name,
350 propertyPath: [property.name]
352 } else if (this.isComplexType(property.type)) {
353 this.fillPropertyDropdownWithMatchingChildProperties(property);
358 private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeBEModel,
359 parentPropertyList: Array<PropertyBEModel | AttributeBEModel> = []): void {
360 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
361 if (!dataTypeFound || !dataTypeFound.properties) {
364 parentPropertyList.push(inputProperty);
365 dataTypeFound.properties.forEach(dataTypeProperty => {
366 if (this.hasSameType(dataTypeProperty)) {
367 this.addPropertyToDropdown({
368 propertyName: dataTypeProperty.name,
369 propertyId: parentPropertyList[0].uniqueId,
370 propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
371 propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name]
373 } else if (this.isComplexType(dataTypeProperty.type)) {
374 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
379 private hasSameType(property: PropertyBEModel | AttributeBEModel): boolean {
380 if (this.typeHasSchema(this.property.type)) {
381 if ((this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel> this.property).input instanceof DerivedFEProperty) || this.compositionMap) {
382 if(this.isComplexType(this.property.schemaType) && !this.compositionMap){
383 let propertySchemaType = (<PropertyDeclareAPIModel> this.property).input.type;
384 propertySchemaType = propertySchemaType == PROPERTY_TYPES.MAP ? PROPERTY_TYPES.STRING : propertySchemaType;
385 return property.type === propertySchemaType;
387 return property.type === this.property.schema.property.type;
390 if (!property.schema || !property.schema.property) {
393 return property.type === this.property.type && this.property.schema.property.type === property.schema.property.type;
395 if (this.property.schema.property.isDataType && this.property instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>this.property).propertiesName){
396 let typeToMatch = this.getType((<PropertyDeclareAPIModel>this.property).propertiesName.split("#").slice(1), this.property.type);
397 return property.type === typeToMatch;
400 return property.type === this.property.type;
403 private getType(propertyPath:string[], type: string): string {
404 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, type);
405 let nestedProperty = dataTypeFound.properties.find(property => property.name === propertyPath[0]);
406 if (propertyPath.length === 1){
407 return nestedProperty.type;
409 return this.getType(propertyPath.slice(1), nestedProperty.type);
412 private isGetProperty(): boolean {
413 return this.functionType === ToscaGetFunctionType.GET_PROPERTY;
416 private isGetAttribute(): boolean {
417 return this.functionType === ToscaGetFunctionType.GET_ATTRIBUTE;
420 private isGetInput(): boolean {
421 return this.functionType === ToscaGetFunctionType.GET_INPUT;
424 private isComplexType(propertyType: string): boolean {
425 return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
428 private typeHasSchema(propertyType: string): boolean {
429 return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType;
432 private stopLoading(): void {
433 this.isLoading = false;
436 private startLoading(): void {
437 this.isLoading = true;
440 showPropertyDropdown(): boolean {
441 if (this.isGetProperty() || this.isGetAttribute()) {
442 return this.propertySource.valid && !this.isLoading && !this.dropDownErrorMsg;
445 return this.functionType && !this.isLoading && !this.dropDownErrorMsg;
448 onPropertySourceChange(): void {
449 this.selectedProperty.reset();
450 if (!this.functionType || !this.propertySource.valid) {
453 this.loadPropertyDropdown();
456 showPropertySourceDropdown(): boolean {
457 return this.isGetProperty() || this.isGetAttribute();
460 private setSelfPropertySource(): void {
461 this.propertySource.setValue(PropertySource.SELF);
464 private get propertySource(): FormControl {
465 return this.formGroup.get('propertySource') as FormControl;
468 private get selectedProperty(): FormControl {
469 return this.formGroup.get('selectedProperty') as FormControl;
474 export interface PropertyDropdownValue {
475 propertyName: string;
477 propertyLabel: string;
478 propertyPath: Array<string>;
481 export interface ToscaGetFunctionValidationEvent {
483 toscaGetFunction: ToscaGetFunction,