Browse Source

UI: Calculated field refactor arguments name validators

pull/14253/head
Vladyslav_Prykhodko 11 months ago
parent
commit
0a45184f4d
  1. 34
      ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts
  2. 10
      ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/propagate-arguments-table.component.ts
  3. 30
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts
  4. 27
      ui-ngx/src/app/shared/models/calculated-field.models.ts

34
ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts

@ -25,7 +25,7 @@ import {
ViewChild
} from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { charsWithNumRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
import {
ArgumentEntityType,
@ -34,7 +34,10 @@ import {
ArgumentType,
ArgumentTypeTranslations,
CalculatedFieldArgumentValue,
getCalculatedFieldCurrentEntityFilter
FORBIDDEN_NAMES,
forbiddenNamesValidator,
getCalculatedFieldCurrentEntityFilter,
uniqueNameValidator
} from '@shared/models/calculated-field.models';
import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
import { EntityType } from '@shared/models/entity-type.models';
@ -74,6 +77,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
@Input() defaultValueRequired = false;
@Input() hint: string;
@Input() predefinedEntityFilter: EntityFilter;
@Input() forbiddenNames = FORBIDDEN_NAMES;
@Input() argumentEntityTypes = Object.values(ArgumentEntityType).filter(value => value !== ArgumentEntityType.RelationQuery) as ArgumentEntityType[];
@ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent;
@ -86,7 +90,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
readonly defaultLimit = Math.floor(this.maxDataPointsPerRollingArg / 10);
argumentFormGroup = this.fb.group({
argumentName: ['', [Validators.required, this.uniqNameRequired(), this.forbiddenArgumentNameValidator(), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
argumentName: ['', [Validators.required, Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
refEntityId: [null],
refEntityKey: this.fb.group({
type: [ArgumentType.LatestTelemetry, [Validators.required]],
@ -141,6 +145,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
}
ngOnInit(): void {
this.updatedFormValidators();
this.updatedArgumentType();
this.argumentFormGroup.patchValue(this.argument, {emitEvent: false});
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId);
@ -185,6 +190,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
this.popover.hide();
}
private updatedFormValidators(): void {
this.argumentFormGroup.get('argumentName').addValidators(
[uniqueNameValidator(this.usedArgumentNames), forbiddenNamesValidator(this.forbiddenNames)]);
this.argumentFormGroup.get('argumentName').updateValueAndValidity({emitEvent: false});
}
private updatedArgumentType(): void {
let argumentType = ArgumentEntityType.Current;
if (this.argument.refEntityId?.entityType) {
@ -255,15 +266,6 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
});
}
private uniqNameRequired(): ValidatorFn {
return (control: FormControl) => {
const newName = control.value.trim().toLowerCase();
const isDuplicate = this.usedArgumentNames?.some(name => name.toLowerCase() === newName);
return isDuplicate ? { duplicateName: true } : null;
};
}
private observeEntityKeyChanges(): void {
this.argumentFormGroup.get('refEntityKey').get('type').valueChanges
.pipe(takeUntilDestroyed())
@ -297,14 +299,6 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
}
}
private forbiddenArgumentNameValidator(): ValidatorFn {
return (control: FormControl) => {
const trimmedValue = control.value.trim().toLowerCase();
const forbiddenArgumentNames = ['ctx', 'e', 'pi', 'propagationCtx'];
return forbiddenArgumentNames.includes(trimmedValue) ? { forbiddenName: true } : null;
};
}
private updatedRefEntityIdState(type: ArgumentEntityType): void {
const isEntityWithId = !!type && type !== ArgumentEntityType.Tenant && type !== ArgumentEntityType.Current;
this.argumentFormGroup.get('refEntityId')[isEntityWithId ? 'enable' : 'disable']();

10
ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/propagate-arguments-table.component.ts

@ -31,7 +31,12 @@ import { AppState } from '@core/core.state';
import {
CalculatedFieldArgumentsTableComponent
} from '@home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component';
import { ArgumentEntityType, ArgumentType, CalculatedFieldArgumentValue } from '@shared/models/calculated-field.models';
import {
ArgumentEntityType,
ArgumentType,
CalculatedFieldArgumentValue,
FORBIDDEN_NAMES
} from '@shared/models/calculated-field.models';
import { isDefined } from '@core/utils';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@ -88,7 +93,8 @@ export class PropagateArgumentsTableComponent extends CalculatedFieldArgumentsTa
this.displayColumns = ['name', 'type', 'key', 'actions'];
this.panelAdditionalCtx = {
argumentEntityTypes: [ArgumentEntityType.Current],
isOutputKey: true
isOutputKey: true,
forbiddenNames: [...FORBIDDEN_NAMES, 'propagationCtx'],
};
}
}

30
ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts

@ -32,11 +32,14 @@ import {
ArgumentEntityTypeTranslations,
CalculatedFieldGeofencing,
CalculatedFieldGeofencingValue,
FORBIDDEN_NAMES,
forbiddenNamesValidator,
GeofencingDirectionLevelTranslations,
GeofencingDirectionTranslations,
GeofencingReportStrategy,
GeofencingReportStrategyTranslations,
getCalculatedFieldCurrentEntityFilter
getCalculatedFieldCurrentEntityFilter,
uniqueNameValidator
} from '@shared/models/calculated-field.models';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
import { EntityType } from '@shared/models/entity-type.models';
@ -75,7 +78,7 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
readonly maxRelationLevelPerCfArgument = getCurrentAuthState(this.store).maxRelationLevelPerCfArgument;
geofencingFormGroup = this.fb.group({
name: ['', [Validators.required, this.uniqNameRequired(), this.forbiddenNameValidator(), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
name: ['', [Validators.required, forbiddenNamesValidator(FORBIDDEN_NAMES), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
refEntityId: this.fb.group({
entityType: [ArgumentEntityType.Current],
id: ['']
@ -132,6 +135,7 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
}
ngOnInit(): void {
this.updatedFormValidators();
this.geofencingFormGroup.patchValue(this.zone, {emitEvent: false});
if (this.zone.refDynamicSourceConfiguration?.type) {
this.refEntityIdFormGroup.get('entityType').setValue(this.zone.refDynamicSourceConfiguration.type, {emitEvent: false});
@ -158,6 +162,11 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
return of(['Contains', 'Manages']).pipe(map(name => name?.filter(option => option.toLowerCase().includes(search))));
}
private updatedFormValidators(): void {
this.geofencingFormGroup.get('name').addValidators(uniqueNameValidator(this.usedNames));
this.geofencingFormGroup.get('name').updateValueAndValidity({emitEvent: false});
}
private observeCreateRelationZonesChanges(): void {
this.geofencingFormGroup.get('createRelationsWithMatchedZones').valueChanges
.pipe(takeUntilDestroyed())
@ -264,29 +273,12 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
});
}
private uniqNameRequired(): ValidatorFn {
return (control: FormControl) => {
const newName = control.value.trim().toLowerCase();
const isDuplicate = this.usedNames?.some(name => name.toLowerCase() === newName);
return isDuplicate ? { duplicateName: true } : null;
};
}
private levelsRequired(): ValidatorFn {
return (control: FormControl) => {
return control.value.length ? null : { levelsRequired: true };
};
}
private forbiddenNameValidator(): ValidatorFn {
return (control: FormControl) => {
const trimmedValue = control.value.trim().toLowerCase();
const forbiddenNames = ['ctx', 'e', 'pi'];
return forbiddenNames.includes(trimmedValue) ? { forbiddenName: true } : null;
};
}
levelsFormArray(): UntypedFormArray {
return this.refDynamicSourceFormGroup.get('levels') as UntypedFormArray;
}

27
ui-ngx/src/app/shared/models/calculated-field.models.ts

@ -30,7 +30,9 @@ import {
endGroupHighlightRule
} from '@shared/models/ace/ace.models';
import { EntitySearchDirection } from '@shared/models/relation.models';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import { AbstractControl, FormControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export const FORBIDDEN_NAMES = ['ctx', 'e', 'pi'];
interface BaseCalculatedField extends Omit<BaseData<CalculatedFieldId>, 'label'>, HasVersion, HasEntityDebugSettings, HasTenantId, ExportableEntity<CalculatedFieldId> {
entityId: EntityId;
@ -904,3 +906,26 @@ export function notEmptyObjectValidator(): ValidatorFn {
return null;
};
}
export function forbiddenNamesValidator(forbiddenNames: string[]): ValidatorFn {
const forbiddenNameSet = new Set(forbiddenNames);
return (control: FormControl) => {
const trimmedValue = (control.value || '').trim();
return forbiddenNameSet.has(trimmedValue) ? { forbiddenName: true } : null;
};
}
export function uniqueNameValidator(existingNames: string[]): ValidatorFn {
const namesSet = new Set((existingNames || []).map(name => name.toLowerCase()));
return (control: FormControl) => {
const newName = (control.value || '').trim().toLowerCase();
if (!newName) {
return null;
}
return namesSet.has(newName) ? { duplicateName: true } : null;
};
}

Loading…
Cancel
Save