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, OnInit, Output} from '@angular/core';
21 import {AttributeModel, ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel} 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 {AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn} from "@angular/forms";
37 selector: 'tosca-function',
38 templateUrl: './tosca-function.component.html',
39 styleUrls: ['./tosca-function.component.less'],
41 export class ToscaFunctionComponent implements OnInit {
43 @Input() property: PropertyBEModel;
44 @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
45 @Input() allowClear: boolean = true;
46 @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>();
47 @Output() onValidityChange: EventEmitter<boolean> = new EventEmitter<boolean>();
49 toscaGetFunctionValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
50 const toscaGetFunction: ToscaGetFunction = control.value;
51 const hasAnyValue = Object.keys(toscaGetFunction).find(key => toscaGetFunction[key]);
55 const errors: ValidationErrors = {};
56 if (!toscaGetFunction.sourceName) {
57 errors.sourceName = { required: true };
59 if (!toscaGetFunction.functionType) {
60 errors.functionType = { required: true };
62 if (!toscaGetFunction.sourceUniqueId) {
63 errors.sourceUniqueId = { required: true };
65 if (!toscaGetFunction.sourceName) {
66 errors.sourceName = { required: true };
68 if (!toscaGetFunction.propertyPathFromSource) {
69 errors.propertyPathFromSource = { required: true };
71 if (!toscaGetFunction.propertyName) {
72 errors.propertyName = { required: true };
74 if (!toscaGetFunction.propertySource) {
75 errors.propertySource = { required: true };
77 return errors ? errors : null;
80 toscaGetFunctionForm: FormControl = new FormControl(new ToscaGetFunction(undefined), [this.toscaGetFunctionValidator]);
81 formGroup: FormGroup = new FormGroup({
82 'toscaGetFunction': this.toscaGetFunctionForm
85 selectedProperty: PropertyDropdownValue;
86 isLoading: boolean = false;
87 propertyDropdownList: Array<PropertyDropdownValue> = [];
88 toscaFunctions: Array<string> = [];
89 propertySourceList: Array<string> = [];
90 instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
91 dropdownValuesLabel: string;
92 dropDownErrorMsg: string;
93 propertySource: string
94 toscaGetFunction: ToscaGetFunction = new ToscaGetFunction(undefined);
96 private componentMetadata: ComponentMetadata;
98 constructor(private topologyTemplateService: TopologyTemplateService,
99 private workspaceService: WorkspaceService,
100 private propertiesService: PropertiesService,
101 private dataTypeService: DataTypeService,
102 private translateService: TranslateService) {
106 this.componentMetadata = this.workspaceService.metadata;
107 this.loadToscaFunctions();
108 this.loadPropertySourceDropdown();
109 this.initToscaGetFunction();
112 private initToscaGetFunction(): void {
113 this.toscaGetFunctionForm.valueChanges.subscribe(toscaGetFunction => {
114 this.onValidityChange.emit(this.toscaGetFunctionForm.valid);
115 if (this.toscaGetFunctionForm.valid) {
116 this.onValidFunction.emit(toscaGetFunction);
119 if (!this.property.isToscaGetFunction()) {
122 this.toscaGetFunction = new ToscaGetFunction(this.property.toscaGetFunction);
123 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
124 if (this.isGetPropertySelected() || this.isGetAttributeSelected()) {
125 if (this.toscaGetFunction.propertySource === PropertySource.SELF) {
126 this.propertySource = PropertySource.SELF;
128 this.propertySource = this.toscaGetFunction.sourceName;
131 if (this.toscaGetFunction.propertyName) {
132 this.loadPropertyDropdown(() => {
133 this.selectedProperty = this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName)
138 private loadToscaFunctions(): void {
139 this.toscaFunctions.push(ToscaGetFunctionType.GET_ATTRIBUTE);
140 this.toscaFunctions.push(ToscaGetFunctionType.GET_INPUT);
141 this.toscaFunctions.push(ToscaGetFunctionType.GET_PROPERTY);
144 private loadPropertySourceDropdown(): void {
145 this.propertySourceList.push(PropertySource.SELF);
146 this.componentInstanceMap.forEach((value, key) => {
147 const instanceName = value.name;
148 this.instanceNameAndIdMap.set(instanceName, key);
149 if (instanceName !== PropertySource.SELF) {
150 this.addToPropertySource(instanceName);
155 private addToPropertySource(source: string): void {
156 this.propertySourceList.push(source);
157 this.propertySourceList.sort((a, b) => {
158 if (a === PropertySource.SELF) {
160 } else if (b === PropertySource.SELF) {
164 return a.localeCompare(b);
168 onToscaFunctionChange(): void {
169 this.resetPropertySource();
170 this.resetPropertyDropdown();
171 if (this.isGetInputSelected()) {
172 this.setSelfPropertySource();
173 this.loadPropertyDropdown();
177 private loadPropertyDropdown(onComplete?: () => any): void {
178 this.loadPropertyDropdownLabel();
179 this.loadPropertyDropdownValues(onComplete);
182 private resetForm(): void {
183 this.toscaGetFunction = new ToscaGetFunction();
184 this.toscaGetFunctionForm.setValue(new ToscaGetFunction());
185 this.propertySource = undefined;
186 this.selectedProperty = undefined;
189 private resetPropertySource(): void {
190 this.toscaGetFunction.propertyUniqueId = undefined;
191 this.toscaGetFunction.propertyName = undefined;
192 this.toscaGetFunction.propertySource = undefined;
193 this.toscaGetFunction.sourceUniqueId = undefined;
194 this.toscaGetFunction.sourceName = undefined;
195 this.toscaGetFunction.propertyPathFromSource = undefined;
196 this.propertySource = undefined;
197 this.selectedProperty = undefined;
199 const toscaGetFunction1 = new ToscaGetFunction(undefined);
200 toscaGetFunction1.functionType = this.toscaGetFunction.functionType;
201 this.toscaGetFunctionForm.setValue(toscaGetFunction1);
204 private loadPropertyDropdownLabel(): void {
205 if (!this.toscaGetFunction.functionType) {
208 if (this.isGetInputSelected()) {
209 this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
210 } else if (this.isGetPropertySelected()) {
211 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
212 } else if (this.isGetAttributeSelected()) {
213 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL');
217 private loadPropertyDropdownValues(onComplete?: () => any): void {
218 if (!this.toscaGetFunction.functionType) {
221 this.resetPropertyDropdown();
222 this.fillPropertyDropdownValues(onComplete);
225 private resetPropertyDropdown(): void {
226 this.dropDownErrorMsg = undefined;
227 this.selectedProperty = undefined;
228 this.propertyDropdownList = [];
231 private fillPropertyDropdownValues(onComplete?: () => any): void {
233 const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
234 propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
235 const properties: Array<PropertyBEModel | AttributeModel> = this.extractProperties(response);
236 if (!properties || properties.length === 0) {
237 const msgCode = this.getNotFoundMsgCode();
238 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
241 this.addPropertiesToDropdown(properties);
242 if (this.propertyDropdownList.length == 0) {
243 const msgCode = this.getNotFoundMsgCode();
244 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
247 console.error('An error occurred while loading properties.', error);
257 private getNotFoundMsgCode(): string {
258 if (this.isGetInputSelected()) {
259 return 'TOSCA_FUNCTION_NO_INPUT_FOUND';
261 if (this.isGetAttributeSelected()) {
262 return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND';
264 if (this.isGetPropertySelected()) {
265 return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
271 private propertyTypeToString() {
272 if (this.property.schemaType) {
273 return `${this.property.type} of ${this.property.schemaType}`;
275 return this.property.type;
278 private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeModel> {
279 if (this.isGetInputSelected()) {
280 return componentGenericResponse.inputs;
282 if (this.isGetPropertySelected()) {
283 if (this.propertySource === PropertySource.SELF) {
284 return componentGenericResponse.properties;
286 const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(this.propertySource)];
287 return this.removeSelectedProperty(componentInstanceProperties);
289 if (this.propertySource === PropertySource.SELF) {
290 return componentGenericResponse.attributes;
292 return componentGenericResponse.componentInstancesAttributes[this.instanceNameAndIdMap.get(this.propertySource)];
295 private getPropertyObservable(): Observable<ComponentGenericResponse> {
296 if (this.isGetInputSelected()) {
297 return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
299 if (this.isGetPropertySelected()) {
300 if (this.propertySource === PropertySource.SELF) {
301 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
303 return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
305 if (this.isGetAttributeSelected()) {
306 if (this.propertySource === PropertySource.SELF) {
307 return this.topologyTemplateService.findAllComponentAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
309 return this.topologyTemplateService.findAllComponentInstanceAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
313 private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
314 if (!componentInstanceProperties) {
317 return componentInstanceProperties.filter(property =>
318 (property.uniqueId !== this.property.uniqueId) ||
319 (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
323 private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
324 this.propertyDropdownList.push(propertyDropdownValue);
325 this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
328 private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeModel>): void {
329 for (const property of properties) {
330 if (this.hasSameType(property)) {
331 this.addPropertyToDropdown({
332 propertyName: property.name,
333 propertyId: property.uniqueId,
334 propertyLabel: property.name,
335 propertyPath: [property.name]
337 } else if (this.isComplexType(property.type)) {
338 this.fillPropertyDropdownWithMatchingChildProperties(property);
343 private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeModel,
344 parentPropertyList: Array<PropertyBEModel | AttributeModel> = []): void {
345 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
346 if (!dataTypeFound || !dataTypeFound.properties) {
349 parentPropertyList.push(inputProperty);
350 dataTypeFound.properties.forEach(dataTypeProperty => {
351 if (this.hasSameType(dataTypeProperty)) {
352 this.addPropertyToDropdown({
353 propertyName: dataTypeProperty.name,
354 propertyId: parentPropertyList[0].uniqueId,
355 propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
356 propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name]
358 } else if (this.isComplexType(dataTypeProperty.type)) {
359 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
364 private hasSameType(property: PropertyBEModel | AttributeModel) {
365 if (this.typeHasSchema(this.property.type)) {
366 if (!property.schema || !property.schema.property) {
369 return property.type === this.property.type && this.property.schema.property.type === property.schema.property.type;
372 return property.type === this.property.type;
375 private isGetPropertySelected(): boolean {
376 return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY;
379 private isGetAttributeSelected(): boolean {
380 return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_ATTRIBUTE;
383 private isGetInputSelected(): boolean {
384 return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_INPUT;
387 private isComplexType(propertyType: string): boolean {
388 return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
391 private typeHasSchema(propertyType: string): boolean {
392 return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType;
395 private stopLoading(): void {
396 this.isLoading = false;
399 private startLoading(): void {
400 this.isLoading = true;
403 showPropertyDropdown(): boolean {
404 if (this.isGetPropertySelected() || this.isGetAttributeSelected()) {
405 return this.toscaGetFunction.propertySource && !this.isLoading && !this.dropDownErrorMsg;
408 return this.toscaGetFunction.functionType && !this.isLoading && !this.dropDownErrorMsg;
411 onPropertySourceChange(): void {
412 if (!this.toscaGetFunction.functionType || !this.propertySource) {
415 this.toscaGetFunction.propertyUniqueId = undefined;
416 this.toscaGetFunction.propertyName = undefined;
417 this.toscaGetFunction.propertyPathFromSource = undefined;
418 if (this.propertySource === PropertySource.SELF) {
419 this.setSelfPropertySource();
421 this.toscaGetFunction.propertySource = PropertySource.INSTANCE;
422 this.toscaGetFunction.sourceName = this.propertySource;
423 this.toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(this.propertySource);
425 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
426 this.loadPropertyDropdown();
429 private setSelfPropertySource(): void {
430 this.toscaGetFunction.propertySource = PropertySource.SELF;
431 this.toscaGetFunction.sourceName = this.componentMetadata.name;
432 this.toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
433 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
436 onPropertyChange(): void {
437 this.toscaGetFunction.propertyUniqueId = this.selectedProperty.propertyId;
438 this.toscaGetFunction.propertyName = this.selectedProperty.propertyName;
439 this.toscaGetFunction.propertyPathFromSource = this.selectedProperty.propertyPath;
440 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
447 showClearButton(): boolean {
448 return this.allowClear && this.toscaGetFunction.functionType !== undefined;
451 showPropertySourceDropdown(): boolean {
452 return this.isGetPropertySelected() || this.isGetAttributeSelected();
456 export interface PropertyDropdownValue {
457 propertyName: string;
459 propertyLabel: string;
460 propertyPath: Array<string>;