Constraints in data type view
[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 validConstraints: boolean = true;
46     private valueChangesSub: Subscription;
47     private descriptionForm: FormControl = new FormControl(undefined);
48     private requiredForm: FormControl = new FormControl(false, Validators.required);
49     nameForm: FormControl = new FormControl(undefined, [Validators.required]);
50     typeForm: FormControl = new FormControl(undefined, Validators.required);
51     schemaForm: FormControl = new FormControl(undefined, (control: AbstractControl): ValidationErrors | null => {
52         if (this.typeNeedsSchema() && !control.value) {
53             return {required: true};
54         }
55         return null;
56     });
57
58     hasDefaultValueForm: FormControl = new FormControl(false, Validators.required);
59     defaultValueForm: FormControl = new FormControl(undefined);
60     constraintsForm: FormControl = new FormControl(undefined);
61     formGroup: FormGroup = new FormGroup({
62         'name': this.nameForm,
63         'description': this.descriptionForm,
64         'type': this.typeForm,
65         'required': this.requiredForm,
66         'schema': this.schemaForm,
67         'defaultValue': this.defaultValueForm,
68         'hasDefaultValue': this.hasDefaultValueForm,
69         'constraints': this.constraintsForm,
70     });
71
72     isLoading: boolean = false;
73     showSchema: boolean = false;
74     typeList: string[];
75     dataTypeMap: Map<string, DataTypeModel>;
76     dataType: DataTypeModel;
77     schemaTypeList: string[];
78
79     constructor(private dataTypeService: DataTypeService) {
80     }
81
82     ngOnInit(): void {
83         this.isLoading = true;
84         this.initTypeAndSchemaDropdown().then(() => this.updateDataType());
85         this.initForm();
86         this.valueChangesSub = this.formGroup.valueChanges.subscribe(() => {
87             this.emitValidityChange();
88         });
89     }
90
91     ngOnDestroy(): void {
92         if (this.valueChangesSub) {
93             this.valueChangesSub.unsubscribe();
94         }
95     }
96
97     onSchemaChange(): void {
98         this.resetDefaultValue();
99     }
100
101     onTypeChange(): void {
102         this.schemaForm.setValue(null);
103         this.showSchema = this.typeNeedsSchema();
104         this.updateDataType();
105         this.resetDefaultValue();
106         if (this.property) {
107             this.property.constraints = [];
108         }
109     }
110
111     private updateDataType(): void {
112         this.dataType = this.dataTypeMap.get(this.typeForm.value);
113     }
114
115     private initForm(): void {
116         if (!this.property) {
117             return;
118         }
119
120         this.nameForm.setValue(this.property.name);
121         this.descriptionForm.setValue(this.property.description);
122         this.typeForm.setValue(this.property.type);
123         this.showSchema = this.typeNeedsSchema();
124         this.requiredForm.setValue(this.property.required);
125         this.schemaForm.setValue(this.property.schemaType);
126         this.constraintsForm.setValue(this.property.constraints);
127         this.initDefaultValueForm();
128     }
129
130     private initDefaultValueForm() {
131         if (this.property.defaultValue == undefined) {
132             return;
133         }
134         let defaultValue;
135         if (!this.isTypeSimple() && typeof this.property.defaultValue === 'string') {
136             defaultValue = JSON.parse(this.property.defaultValue);
137         } else {
138             defaultValue = this.property.defaultValue;
139         }
140         this.defaultValueForm.setValue(defaultValue);
141         this.hasDefaultValueForm.setValue(true);
142     }
143
144     private typeNeedsSchema() {
145         return PROPERTY_DATA.SCHEMA_TYPES.indexOf(this.typeForm.value) > -1;
146     }
147
148     private initTypeAndSchemaDropdown(): Promise<Map<string, DataTypeModel>> {
149         const primitiveTypes: string[] = Array.from(PROPERTY_DATA.TYPES).sort((a, b) => a.localeCompare(b));
150         const promise = this.dataTypeService.findAllDataTypesByModel(this.model);
151         promise.then((dataTypeMap: Map<string, DataTypeModel>) => {
152             this.dataTypeMap = dataTypeMap;
153             const nonPrimitiveTypes: string[] = Array.from(dataTypeMap.keys()).filter(type => {
154                 return primitiveTypes.indexOf(type) === -1;
155             });
156             nonPrimitiveTypes.sort((a, b) => a.localeCompare(b));
157             this.typeList = [...primitiveTypes, ...nonPrimitiveTypes];
158             this.schemaTypeList = Array.from(this.typeList);
159             this.isLoading = false;
160         });
161         return promise;
162     }
163
164     private emitValidityChange(): void {
165         const isValid: boolean = this.formGroup.valid;
166         this.onValidityChange.emit({
167             isValid: isValid && this.validConstraints,
168             property: isValid ? this.buildPropertyFromForm() : undefined
169         });
170     }
171
172     private buildPropertyFromForm(): PropertyBEModel {
173         const property = new PropertyBEModel();
174         property.name = this.nameForm.value;
175         property.type = this.typeForm.value;
176         property.constraints = this.constraintsForm.value;
177         if (this.schemaForm.value) {
178             property.schemaType = this.schemaForm.value;
179         }
180         property.description = this.descriptionForm.value;
181         if (this.hasDefaultValueForm.value === true) {
182             property.defaultValue = this.defaultValueForm.value;
183         }
184         return property;
185     }
186
187     public isTypeSimple(): boolean {
188         return ToscaTypeHelper.isTypeSimple(this.typeForm.value);
189     }
190
191     public isTypeList(): boolean {
192         return ToscaTypeHelper.isTypeList(this.typeForm.value);
193     }
194
195     public isTypeMap(): boolean {
196         return ToscaTypeHelper.isTypeMap(this.typeForm.value);
197     }
198
199     public isTypeComplex(): boolean {
200         return ToscaTypeHelper.isTypeComplex(this.typeForm.value);
201     }
202
203     private isTypeRange() {
204         return ToscaTypeHelper.isTypeRange(this.typeForm.value);
205     }
206
207     onPropertyValueChange($event: any): void {
208         this.defaultValueForm.setValue($event.value);
209     }
210
211     showDefaultValue(): boolean {
212         if (this.readOnly) {
213             return this.defaultValueForm.value != undefined && this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid;
214         }
215         return this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid;
216     }
217
218     getDataType(type: string): DataTypeModel {
219         return this.dataTypeMap.get(type);
220     }
221
222     private resetDefaultValue(): void {
223         this.defaultValueForm.reset();
224         if (this.isTypeComplex() || this.isTypeMap()) {
225             this.defaultValueForm.setValue({});
226         } else if (this.isTypeList() || this.isTypeRange()) {
227             this.defaultValueForm.setValue([]);
228         }
229     }
230
231     buildSchemaGroupProperty(): SchemaPropertyGroupModel {
232         const schemaProperty = new SchemaProperty();
233         schemaProperty.type = this.schemaForm.value
234         return new SchemaPropertyGroupModel(schemaProperty);
235     }
236
237     onConstraintChange = (constraints: any): void => {
238         if (this.property) {
239             if (!this.property.constraints) {
240                 this.property.constraints = [];
241             }
242             this.property.constraints = constraints.constraints;
243         }
244         else {
245             this.constraintsForm.setValue(constraints.constraints);
246         }
247         this.validConstraints = constraints.valid;
248         this.onValidityChange.emit({
249             isValid: constraints.valid,
250             property: constraints.valid ? this.buildPropertyFromForm() : undefined
251         });
252     }
253 }
254
255 export class PropertyValidationEvent {
256     isValid: boolean;
257     property: PropertyBEModel;
258 }