2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2022 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';
22 AbstractControl, FormArray,
25 FormGroup, ValidationErrors,
28 } from '@angular/forms';
29 import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils/constants';
32 selector: 'app-constraints',
33 templateUrl: './constraints.component.html',
34 styleUrls: ['./constraints.component.less']
36 export class ConstraintsComponent implements OnInit {
38 @Input() propertyConstraints: any[];
39 @Input() propertyType: string;
40 @Input() isViewOnly: boolean = false;
41 @Output() onConstraintChange: EventEmitter<any> = new EventEmitter<any>();
43 constraintTypes: string[];
44 ConstraintTypesMapping = ConstraintTypesMapping;
45 valid: boolean = false;
46 constraintForm: FormGroup;
50 constructor(private formBuilder: FormBuilder) {}
52 get constraintsArray() {
53 return this.constraintForm.get('constraint') as FormArray;
56 get constraintValidators(): ValidatorFn {
57 switch (this.propertyType) {
58 case PROPERTY_TYPES.INTEGER:
59 console.warn('Add int validator');
60 return Validators.compose([
64 case PROPERTY_TYPES.FLOAT:
65 console.warn('Add float validator');
66 return Validators.compose([
71 console.warn('Only required validator');
72 return Validators.compose([
78 public constraintValuesArray(index: number): FormArray {
79 return this.constraintsArray.at(index).get('value') as FormArray;
84 this.constraintTypes = Object.keys(ConstraintTypes).map((key) => ConstraintTypes[key]);
86 // This is only used by the spec test
87 if (!this.constraintForm) {
88 this.constraintForm = this.formBuilder.group({
89 constraint: this.formBuilder.array([])
93 this.validationMessages = {
95 { type: 'required', message: 'Constraint value is required'},
96 { type: 'invalidInt', message: 'Constraint value is not a valid integer'},
97 { type: 'invalidFloat', message: 'Constraint value is not a valid floating point value'},
98 { type: 'invalidString', message: 'String contains invalid characters'}
101 { type: 'required', message: 'Constraint type is required'}
108 ngOnChanges(changes): void {
111 // Changes fires before init so form has to be initialised here
113 this.constraintForm = this.formBuilder.group({
114 constraint: this.formBuilder.array([])
117 if (changes.propertyConstraints && changes.propertyConstraints.currentValue) {
118 changes.propertyConstraints.currentValue.forEach((constraint: any) => {
119 const prop = this.getConstraintFromPropertyBEModel(constraint);
120 console.log('constraint from BE model', prop);
121 this.constraintsArray.push(prop);
126 if (changes.propertyType) {
128 // Reset constraints on property type change
129 console.warn('Property type changed. Resetting constraints');
130 this.constraintForm = this.formBuilder.group({
131 constraint: this.formBuilder.array([])
135 if (!this.propertyType || changes.propertyType.currentValue == this.propertyType) {
136 this.propertyType = changes.propertyType.currentValue;
138 this.propertyType = changes.propertyType;
139 this.emitOnConstraintChange();
142 this.constraintsArray.controls.forEach((control: AbstractControl) => {
143 control.get('value').setValidators(this.constraintValidators);
147 console.log('constraints', this.constraintsArray);
150 removeFromList(constraintIndex: number, valueIndex: number) {
151 this.constraintsArray.at(constraintIndex).get('value').value.splice(valueIndex, 1);
152 this.emitOnConstraintChange();
155 addToList(constraintIndex: number) {
156 const newConstraint = new FormControl('', this.constraintValidators);
158 this.constraintValuesArray(constraintIndex).push(newConstraint);
159 console.log('constraintsArray', this.constraintsArray);
160 console.log('constraintValuesArray', this.constraintValuesArray(constraintIndex));
161 this.emitOnConstraintChange();
164 onChangeConstraintType(constraintIndex: number, newType: ConstraintTypes) {
165 if ((newType == ConstraintTypes.valid_values)) {
166 const newConstraint = this.formBuilder.group({
167 type: new FormControl({
169 disabled: this.isViewOnly
170 }, Validators.required),
171 value: this.formBuilder.array([])});
173 this.constraintsArray.removeAt(constraintIndex);
174 this.constraintsArray.push(newConstraint);
175 } else if (newType == ConstraintTypes.in_range) {
176 const newConstraint = this.formBuilder.group({
177 type: new FormControl({
179 disabled: this.isViewOnly
180 }, Validators.required),
181 value: this.formBuilder.array([])});
183 const valRef = newConstraint.get('value') as FormArray;
184 valRef.push(new FormControl('', this.constraintValidators));
185 valRef.push(new FormControl('', this.constraintValidators));
187 this.constraintsArray.removeAt(constraintIndex);
188 this.constraintsArray.push(newConstraint);
190 this.constraintsArray.at(constraintIndex).value.type = newType;
192 this.emitOnConstraintChange();
195 onChangeConstraintValue(constraintIndex: number, newValue: any) {
196 this.constraintsArray.at(constraintIndex).get('value').setValue(newValue);
197 this.emitOnConstraintChange();
200 onChangeConstrainValueIndex(constraintIndex: number, newValue: any, valueIndex: number) {
201 this.constraintValuesArray(constraintIndex).controls[valueIndex].setValue(newValue);
202 this.emitOnConstraintChange();
205 removeConstraint(constraintIndex: number) {
206 this.constraintsArray.removeAt(constraintIndex);
207 this.emitOnConstraintChange();
211 const newConstraint = this.formBuilder.group({
212 type: new FormControl({
213 value: ConstraintTypes.null,
214 disabled: this.isViewOnly
215 }, Validators.required),
216 value: new FormControl({
218 disabled: this.isViewOnly
219 }, this.constraintValidators)
221 this.constraintsArray.push(newConstraint);
223 this.emitOnConstraintChange();
226 getInRangeValue(constraintIndex: number, valueIndex: number): string {
227 const value = this.constraintsArray.at(constraintIndex).get('value').value;
229 if (!value || !value[valueIndex]) {
233 return value[valueIndex];
236 disableConstraint(optionConstraintType: ConstraintTypes): boolean {
237 const invalid = this.notAllowedConstraint(optionConstraintType);
238 return invalid ? invalid : this.getConstraintTypeIfPresent(optionConstraintType) ? true : false;
241 notAllowedConstraint(optionConstraintType: ConstraintTypes): boolean {
242 switch (optionConstraintType) {
243 case ConstraintTypes.less_or_equal:
244 case ConstraintTypes.less_than:
245 case ConstraintTypes.greater_or_equal:
246 case ConstraintTypes.greater_than:
247 case ConstraintTypes.in_range:
248 if (this.isComparable(this.propertyType)) {
252 case ConstraintTypes.length:
253 case ConstraintTypes.max_length:
254 case ConstraintTypes.min_length:
255 if (this.propertyType == PROPERTY_TYPES.STRING || this.propertyType == PROPERTY_TYPES.MAP || this.propertyType == PROPERTY_TYPES.LIST) {
259 case ConstraintTypes.pattern:
260 if (this.propertyType == PROPERTY_TYPES.STRING) {
264 case ConstraintTypes.valid_values:
265 case ConstraintTypes.equal:
271 getConstraintTypeIfPresent(constraintType: ConstraintTypes): AbstractControl {
272 return this.constraintsArray.controls.find((control: AbstractControl) => {
273 const type = control.get('type').value;
274 return type == constraintType;
282 isComparable(propType: string): boolean {
283 if (PROPERTY_DATA.COMPARABLE_TYPES.indexOf(propType) >= 0) {
289 private getConstraintFromPropertyBEModel(constraint: any): AbstractControl {
290 console.log('be model constraints', constraint);
291 let constraintType: ConstraintTypes;
292 let constraintValue: any;
294 constraintType = ConstraintTypes.null;
295 constraintValue = '';
296 } else if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
297 constraintType = ConstraintTypes.valid_values;
298 } else if (constraint.hasOwnProperty(ConstraintTypes.equal)) {
299 constraintType = ConstraintTypes.equal;
300 constraintValue = constraint.equal;
301 } else if (constraint.hasOwnProperty(ConstraintTypes.greater_than)) {
302 constraintType = ConstraintTypes.greater_than;
303 constraintValue = constraint.greaterThan;
304 } else if (constraint.hasOwnProperty(ConstraintTypes.greater_or_equal)) {
305 constraintType = ConstraintTypes.greater_or_equal;
306 constraintValue = constraint.greaterOrEqual;
307 } else if (constraint.hasOwnProperty(ConstraintTypes.less_than)) {
308 constraintType = ConstraintTypes.less_than;
309 constraintValue = constraint.lessThan;
310 } else if (constraint.hasOwnProperty(ConstraintTypes.less_or_equal)) {
311 constraintType = ConstraintTypes.less_or_equal;
312 constraintValue = constraint.lessOrEqual;
313 } else if (constraint.hasOwnProperty(ConstraintTypes.in_range)) {
314 constraintType = ConstraintTypes.in_range;
315 constraintValue = new Array(constraint.inRange[0], constraint.inRange[1]);
316 } else if (constraint.rangeMaxValue || constraint.rangeMinValue) {
317 constraintType = ConstraintTypes.in_range;
318 constraintValue = new Array(constraint.rangeMinValue, constraint.rangeMaxValue);
319 } else if (constraint.hasOwnProperty(ConstraintTypes.length)) {
320 constraintType = ConstraintTypes.length;
321 constraintValue = constraint.length;
322 } else if (constraint.hasOwnProperty(ConstraintTypes.min_length)) {
323 constraintType = ConstraintTypes.min_length;
324 constraintValue = constraint.minLength;
325 } else if (constraint.hasOwnProperty(ConstraintTypes.max_length)) {
326 constraintType = ConstraintTypes.max_length;
327 constraintValue = constraint.maxLength;
328 } else if (constraint.hasOwnProperty(ConstraintTypes.pattern)) {
329 constraintType = ConstraintTypes.pattern;
330 constraintValue = constraint.pattern;
333 if (!constraint.hasOwnProperty(ConstraintTypes.valid_values) && !constraint.hasOwnProperty(ConstraintTypes.in_range)) {
334 return this.formBuilder.group({
335 type: new FormControl({
336 value: constraintType,
337 disabled: this.isViewOnly
338 }, Validators.required),
339 value: new FormControl({
340 value: constraintValue,
341 disabled: this.isViewOnly
342 }, this.constraintValidators)
345 const newForm = this.formBuilder.group({
346 type: new FormControl({
347 value: constraintType,
348 disabled: this.isViewOnly
349 }, Validators.required),
350 value: this.formBuilder.array([])
353 const valRef = newForm.get('value') as FormArray;
354 if (constraint.hasOwnProperty(ConstraintTypes.valid_values)) {
355 constraint.validValues.forEach((val) => {
356 valRef.push(new FormControl({value: val, disabled: this.isViewOnly}, this.constraintValidators));
359 constraint.inRange.forEach((val) => {
360 valRef.push(new FormControl({value: val, disabled: this.isViewOnly}, this.constraintValidators));
364 console.log('new form', newForm);
369 private getConstraintsFormat(): any[] {
370 const constraintArray = new Array();
371 this.constraintsArray.controls.forEach((control: AbstractControl) => {
372 const type = control.get('type').value;
373 let constraint: Constraint;
375 if (type != ConstraintTypes.valid_values && type != ConstraintTypes.in_range) {
378 value: control.get('value').value
383 control.get('value').value.forEach((val) => {
393 console.log('New constraint object', constraint);
394 constraintArray.push(this.getConstraintFormat(constraint));
396 return constraintArray;
399 private getConstraintFormat(constraint: Constraint): any {
400 switch (constraint.type) {
401 case ConstraintTypes.equal:
403 [ConstraintTypes.equal]: constraint.value
405 case ConstraintTypes.less_or_equal:
407 [ConstraintTypes.less_or_equal]: constraint.value
409 case ConstraintTypes.less_than:
411 [ConstraintTypes.less_than]: constraint.value
413 case ConstraintTypes.greater_or_equal:
415 [ConstraintTypes.greater_or_equal]: constraint.value
417 case ConstraintTypes.greater_than:
419 [ConstraintTypes.greater_than]: constraint.value
421 case ConstraintTypes.in_range:
423 [ConstraintTypes.in_range]: constraint.value
425 case ConstraintTypes.length:
427 [ConstraintTypes.length]: constraint.value
429 case ConstraintTypes.max_length:
431 [ConstraintTypes.max_length]: constraint.value
433 case ConstraintTypes.min_length:
435 [ConstraintTypes.min_length]: constraint.value
437 case ConstraintTypes.pattern:
439 [ConstraintTypes.pattern]: constraint.value
441 case ConstraintTypes.valid_values:
443 [ConstraintTypes.valid_values]: constraint.value
450 private validateConstraints(): void {
451 this.valid = this.constraintsArray.controls.every((control: AbstractControl) => {
452 const value = control.get('value').value;
453 const type = control.get('type').value;
454 control.updateValueAndValidity();
456 if (Array.isArray(value)) {
457 return !(value.length == 0 || this.doesArrayContaintEmptyValues(value));
459 if (type == ConstraintTypes.pattern) {
467 this.valid = this.constraintForm.valid;
470 return value && type != ConstraintTypes.null;
474 private doesArrayContaintEmptyValues(arr) {
475 for (const element of arr) {
476 if (element === '') { return true; }
481 private emitOnConstraintChange(): void {
482 console.log('constraints', this.constraintsArray);
484 this.validateConstraints();
485 const newConstraints = this.getConstraintsFormat();
487 this.valid = this.constraintForm.valid;
488 console.log('emitOnConstraintChange.valid', this.valid);
490 this.onConstraintChange.emit({
491 constraints: newConstraints,
498 export enum ConstraintTypes {
501 greater_than = 'greaterThan',
502 greater_or_equal = 'greaterOrEqual',
503 less_than = 'lessThan',
504 less_or_equal = 'lessOrEqual',
505 in_range = 'inRange',
506 valid_values = 'validValues',
508 min_length = 'minLength',
509 max_length = 'maxLength',
513 export const ConstraintTypesMapping = {
514 [ConstraintTypes.equal]: 'equal',
515 [ConstraintTypes.greater_than]: 'greater_than',
516 [ConstraintTypes.greater_or_equal]: 'greater_or_equal',
517 [ConstraintTypes.less_than]: 'less_than',
518 [ConstraintTypes.less_or_equal]: 'less_or_equal',
519 [ConstraintTypes.in_range]: 'in_range',
520 [ConstraintTypes.valid_values]: 'valid_values',
521 [ConstraintTypes.length]: 'length',
522 [ConstraintTypes.min_length]: 'min_length',
523 [ConstraintTypes.max_length]: 'max_length',
524 [ConstraintTypes.pattern]: 'pattern'
527 export interface Constraint {
528 type: ConstraintTypes;
532 export function intValidator(): ValidatorFn {
533 const intRegex = /^[-+]?\d+$/;
534 return (control: AbstractControl): ValidationErrors | null => {
535 if (control.value && !intRegex.test(control.value)) {
536 return {invalidInt: true};
543 export function floatValidator(): ValidatorFn {
544 const floatRegex = /^[-+]?\d+(\.\d+)?$/;
546 return (control: AbstractControl): ValidationErrors | null => {
547 if (control.value && !floatRegex.test(control.value)) {
548 return {invalidFloat: true};