Enable UI component to display property constraints
[sdc.git] / catalog-ui / src / app / ng2 / pages / type-workspace / type-workspace-properties / add-property / add-property.component.ts
1 /*
2  * -
3  *  ============LICENSE_START=======================================================
4  *  Copyright (C) 2022 Nordix Foundation.
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *       http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
23 import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model";
24 import {AbstractControl, FormControl, FormGroup, ValidationErrors, Validators} from "@angular/forms";
25 import {PROPERTY_DATA} from "../../../../../utils/constants";
26 import {DataTypeService} from "../../../../services/data-type.service";
27 import {DataTypeModel} from "../../../../../models/data-types";
28 import {Subscription} from "rxjs";
29 import {ToscaTypeHelper} from "../../../../../utils/tosca-type-helper";
30 import {SchemaProperty, SchemaPropertyGroupModel} from "../../../../../models/schema-property";
31
32 @Component({
33     selector: 'app-add-property',
34     templateUrl: './add-property.component.html',
35     styleUrls: ['./add-property.component.less']
36 })
37 export class AddPropertyComponent implements OnInit, OnDestroy {
38
39     @Input() property: PropertyBEModel;
40     @Input() readOnly: boolean = true;
41     @Input() model: string;
42
43     @Output() onValidityChange: EventEmitter<PropertyValidationEvent> = new EventEmitter<PropertyValidationEvent>();
44
45     private valueChangesSub: Subscription;
46     private descriptionForm: FormControl = new FormControl(undefined);
47     private requiredForm: FormControl = new FormControl(false, Validators.required);
48     nameForm: FormControl = new FormControl(undefined, [Validators.required]);
49     typeForm: FormControl = new FormControl(undefined, Validators.required);
50     schemaForm: FormControl = new FormControl(undefined, (control: AbstractControl): ValidationErrors | null => {
51         if (this.typeNeedsSchema() && !control.value) {
52             return {required: true};
53         }
54         return null;
55     });
56     hasDefaultValueForm: FormControl = new FormControl(false, Validators.required);
57     defaultValueForm: FormControl = new FormControl(undefined);
58     formGroup: FormGroup = new FormGroup({
59         'name': this.nameForm,
60         'description': this.descriptionForm,
61         'type': this.typeForm,
62         'required': this.requiredForm,
63         'schema': this.schemaForm,
64         'defaultValue': this.defaultValueForm,
65         'hasDefaultValue': this.hasDefaultValueForm,
66     });
67
68     isLoading: boolean = false;
69     showSchema: boolean = false;
70     typeList: string[];
71     dataTypeMap: Map<string, DataTypeModel>;
72     dataType: DataTypeModel;
73     schemaTypeList: string[];
74
75     constructor(private dataTypeService: DataTypeService) {
76     }
77
78     ngOnInit(): void {
79         this.isLoading = true;
80         this.initTypeAndSchemaDropdown().then(() => this.updateDataType());
81         this.initForm();
82         this.valueChangesSub = this.formGroup.valueChanges.subscribe(() => {
83             this.emitValidityChange();
84         });
85     }
86
87     ngOnDestroy(): void {
88         if (this.valueChangesSub) {
89             this.valueChangesSub.unsubscribe();
90         }
91     }
92
93     onSchemaChange(): void {
94         this.resetDefaultValue();
95     }
96
97     onTypeChange(): void {
98         this.schemaForm.setValue(null);
99         this.showSchema = this.typeNeedsSchema();
100         this.updateDataType();
101         this.resetDefaultValue();
102     }
103
104     private updateDataType(): void {
105         this.dataType = this.dataTypeMap.get(this.typeForm.value);
106     }
107
108     private initForm(): void {
109         if (!this.property) {
110             return;
111         }
112
113         this.nameForm.setValue(this.property.name);
114         this.descriptionForm.setValue(this.property.description);
115         this.typeForm.setValue(this.property.type);
116         this.showSchema = this.typeNeedsSchema();
117         this.requiredForm.setValue(this.property.required);
118         this.schemaForm.setValue(this.property.schemaType);
119         this.initDefaultValueForm();
120     }
121
122     private initDefaultValueForm() {
123         if (this.property.defaultValue == undefined) {
124             return;
125         }
126         let defaultValue;
127         if (!this.isTypeSimple() && typeof this.property.defaultValue === 'string') {
128             defaultValue = JSON.parse(this.property.defaultValue);
129         } else {
130             defaultValue = this.property.defaultValue;
131         }
132         this.defaultValueForm.setValue(defaultValue);
133         this.hasDefaultValueForm.setValue(true);
134     }
135
136     private typeNeedsSchema() {
137         return PROPERTY_DATA.SCHEMA_TYPES.indexOf(this.typeForm.value) > -1;
138     }
139
140     private initTypeAndSchemaDropdown(): Promise<Map<string, DataTypeModel>> {
141         const primitiveTypes: string[] = Array.from(PROPERTY_DATA.TYPES).sort((a, b) => a.localeCompare(b));
142         const promise = this.dataTypeService.findAllDataTypesByModel(this.model);
143         promise.then((dataTypeMap: Map<string, DataTypeModel>) => {
144             this.dataTypeMap = dataTypeMap;
145             const nonPrimitiveTypes: string[] = Array.from(dataTypeMap.keys()).filter(type => {
146                 return primitiveTypes.indexOf(type) === -1;
147             });
148             nonPrimitiveTypes.sort((a, b) => a.localeCompare(b));
149             this.typeList = [...primitiveTypes, ...nonPrimitiveTypes];
150             this.schemaTypeList = Array.from(this.typeList);
151             this.isLoading = false;
152         });
153         return promise;
154     }
155
156     private emitValidityChange(): void {
157         const isValid: boolean = this.formGroup.valid;
158         this.onValidityChange.emit({
159             isValid: isValid,
160             property: isValid ? this.buildPropertyFromForm() : undefined
161         });
162     }
163
164     private buildPropertyFromForm(): PropertyBEModel {
165         const property = new PropertyBEModel();
166         property.name = this.nameForm.value;
167         property.type = this.typeForm.value;
168         if (this.schemaForm.value) {
169             property.schemaType = this.schemaForm.value;
170         }
171         property.description = this.descriptionForm.value;
172         if (this.hasDefaultValueForm.value === true) {
173             property.defaultValue = this.defaultValueForm.value;
174         }
175         return property;
176     }
177
178     public isTypeSimple(): boolean {
179         return ToscaTypeHelper.isTypeSimple(this.typeForm.value);
180     }
181
182     public isTypeList(): boolean {
183         return ToscaTypeHelper.isTypeList(this.typeForm.value);
184     }
185
186     public isTypeMap(): boolean {
187         return ToscaTypeHelper.isTypeMap(this.typeForm.value);
188     }
189
190     public isTypeComplex(): boolean {
191         return ToscaTypeHelper.isTypeComplex(this.typeForm.value);
192     }
193
194     private isTypeRange() {
195         return ToscaTypeHelper.isTypeRange(this.typeForm.value);
196     }
197
198     onPropertyValueChange($event: any): void {
199         this.defaultValueForm.setValue($event.value);
200     }
201
202     showDefaultValue(): boolean {
203         if (this.readOnly) {
204             return this.defaultValueForm.value != undefined && this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid;
205         }
206         return this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid;
207     }
208
209     getDataType(type: string): DataTypeModel {
210         return this.dataTypeMap.get(type);
211     }
212
213     private resetDefaultValue(): void {
214         this.defaultValueForm.reset();
215         if (this.isTypeComplex() || this.isTypeMap()) {
216             this.defaultValueForm.setValue({});
217         } else if (this.isTypeList() || this.isTypeRange()) {
218             this.defaultValueForm.setValue([]);
219         }
220     }
221
222     buildSchemaGroupProperty(): SchemaPropertyGroupModel {
223         const schemaProperty = new SchemaProperty();
224         schemaProperty.type = this.schemaForm.value
225         return new SchemaPropertyGroupModel(schemaProperty);
226     }
227
228 }
229
230 export class PropertyValidationEvent {
231     isValid: boolean;
232     property: PropertyBEModel;
233 }