Browse Source

Merge pull request #12625 from maxunbearable/feature/calculated-fields-improvements-07-02

Calculated field fixes and improvements
pull/12629/head
Vladyslav Prykhodko 2 years ago
committed by GitHub
parent
commit
b078f93de5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html
  2. 20
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts
  3. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  4. 35
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html
  5. 23
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts
  6. 1
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  7. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  8. 7
      ui-ngx/src/form.scss

7
ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html

@ -88,9 +88,8 @@
[matTooltip]="'action.edit' | translate" [matTooltip]="'action.edit' | translate"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon <mat-icon
[matBadgeHidden]="!(argumentsFormArray.dirty [matBadgeHidden]="!(group.get('refEntityKey').get('type').value === ArgumentType.Rolling
&& group.get('refEntityKey').get('type').value === ArgumentType.Rolling && calculatedFieldType === CalculatedFieldType.SIMPLE)"
&& calculatedFieldType() === CalculatedFieldType.SIMPLE)"
matBadgeColor="warn" matBadgeColor="warn"
matBadgeSize="small" matBadgeSize="small"
matBadge="*" matBadge="*"
@ -111,7 +110,7 @@
<span class="tb-prompt flex items-center justify-center">{{ 'calculated-fields.no-arguments' | translate }}</span> <span class="tb-prompt flex items-center justify-center">{{ 'calculated-fields.no-arguments' | translate }}</span>
} }
</div> </div>
@if (errorText && this.argumentsFormArray.dirty) { @if (errorText) {
<tb-error noMargin [error]="errorText | translate" class="pl-3"/> <tb-error noMargin [error]="errorText | translate" class="pl-3"/>
} }
</div> </div>

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

@ -17,9 +17,7 @@
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
effect,
forwardRef, forwardRef,
input,
Input, Input,
OnChanges, OnChanges,
Renderer2, Renderer2,
@ -77,8 +75,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
@Input() entityId: EntityId; @Input() entityId: EntityId;
@Input() tenantId: string; @Input() tenantId: string;
@Input() entityName: string; @Input() entityName: string;
@Input() calculatedFieldType: CalculatedFieldType;
calculatedFieldType = input<CalculatedFieldType>()
errorText = ''; errorText = '';
argumentsFormArray = this.fb.array<AbstractControl>([]); argumentsFormArray = this.fb.array<AbstractControl>([]);
@ -103,17 +100,12 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => { this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
this.propagateChange(this.getArgumentsObject()); this.propagateChange(this.getArgumentsObject());
}); });
effect(() => {
if (this.calculatedFieldType() && this.argumentsFormArray.dirty) {
this.argumentsFormArray.updateValueAndValidity();
}
});
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.calculatedFieldType?.previousValue if (changes.calculatedFieldType?.previousValue
&& changes.calculatedFieldType.currentValue !== changes.calculatedFieldType.previousValue) { && changes.calculatedFieldType.currentValue !== changes.calculatedFieldType.previousValue) {
this.argumentsFormArray.markAsDirty(); this.argumentsFormArray.updateValueAndValidity();
} }
} }
@ -142,14 +134,16 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.popoverService.hasPopover(trigger)) { if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger); this.popoverService.hidePopover(trigger);
} else { } else {
const argumentObj = this.argumentsFormArray.at(index)?.getRawValue() ?? {};
const ctx = { const ctx = {
index, index,
argument: this.argumentsFormArray.at(index)?.getRawValue() ?? {}, argument: argumentObj,
entityId: this.entityId, entityId: this.entityId,
calculatedFieldType: this.calculatedFieldType(), calculatedFieldType: this.calculatedFieldType,
buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add', buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add',
tenantId: this.tenantId, tenantId: this.tenantId,
entityName: this.entityName, entityName: this.entityName,
usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argumentObj.argumentName),
}; };
this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer, this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, CalculatedFieldArgumentPanelComponent, 'left', false, null, this.viewContainerRef, CalculatedFieldArgumentPanelComponent, 'left', false, null,
@ -171,7 +165,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
} }
private updateErrorText(): void { private updateErrorText(): void {
if (this.calculatedFieldType() === CalculatedFieldType.SIMPLE if (this.calculatedFieldType === CalculatedFieldType.SIMPLE
&& this.argumentsFormArray.controls.some(control => control.get('refEntityKey').get('type').value === ArgumentType.Rolling)) { && this.argumentsFormArray.controls.some(control => control.get('refEntityKey').get('type').value === ArgumentType.Rolling)) {
this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling'; this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling';
} else if (!this.argumentsFormArray.controls.length) { } else if (!this.argumentsFormArray.controls.length) {

4
ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html

@ -65,7 +65,7 @@
</div> </div>
<ng-container [formGroup]="configFormGroup"> <ng-container [formGroup]="configFormGroup">
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'calculated-fields.arguments' | translate }}</div> <div class="tb-form-panel-title tb-required">{{ 'calculated-fields.arguments' | translate }}</div>
<tb-calculated-field-arguments-table <tb-calculated-field-arguments-table
formControlName="arguments" formControlName="arguments"
[entityId]="data.entityId" [entityId]="data.entityId"
@ -75,7 +75,7 @@
/> />
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'calculated-fields.expression' | translate }}*</div> <div class="tb-form-panel-title tb-required">{{ 'calculated-fields.expression' | translate }}</div>
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) { @if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) {
<mat-form-field class="mat-block" appearance="outline"> <mat-form-field class="mat-block" appearance="outline">
<input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'action.set' | translate" required> <input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'action.set' | translate" required>

35
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html

@ -31,7 +31,15 @@
class="tb-error"> class="tb-error">
warning warning
</mat-icon> </mat-icon>
} @else if (argumentFormGroup.get('argumentName').touched && argumentFormGroup.get('argumentName').hasError('pattern')) { } @else if (argumentFormGroup.get('argumentName').touched && argumentFormGroup.get('argumentName').hasError('duplicateName')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-duplicate' | translate"
class="tb-error">
warning
</mat-icon>
} @else if (argumentFormGroup.get('argumentName').touched && argumentFormGroup.get('argumentName').hasError('pattern')) {
<mat-icon matSuffix <mat-icon matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"
@ -103,25 +111,24 @@
<tb-entity-key-autocomplete class="flex-1" formControlName="key" [dataKeyType]="DataKeyType.timeseries" [entityFilter]="entityFilter"/> <tb-entity-key-autocomplete class="flex-1" formControlName="key" [dataKeyType]="DataKeyType.timeseries" [entityFilter]="entityFilter"/>
</div> </div>
} @else { } @else {
<div class="tb-form-row"> @if (enableAttributeScopeSelection) {
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-scope' | translate }}</div> <div class="tb-form-row">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex-1"> <div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-scope' | translate }}</div>
<mat-select formControlName="scope"> <mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex-1">
<mat-option [value]="AttributeScope.SERVER_SCOPE"> <mat-select formControlName="scope">
{{ 'calculated-fields.server-attributes' | translate }} <mat-option [value]="AttributeScope.SERVER_SCOPE">
</mat-option> {{ 'calculated-fields.server-attributes' | translate }}
@if (entityType === ArgumentEntityType.Device </mat-option>
|| entityType === ArgumentEntityType.Current && entityId.entityType === EntityType.DEVICE) {
<mat-option [value]="AttributeScope.CLIENT_SCOPE"> <mat-option [value]="AttributeScope.CLIENT_SCOPE">
{{ 'calculated-fields.client-attributes' | translate }} {{ 'calculated-fields.client-attributes' | translate }}
</mat-option> </mat-option>
<mat-option [value]="AttributeScope.SHARED_SCOPE"> <mat-option [value]="AttributeScope.SHARED_SCOPE">
{{ 'calculated-fields.shared-attributes' | translate }} {{ 'calculated-fields.shared-attributes' | translate }}
</mat-option> </mat-option>
} </mat-select>
</mat-select> </mat-form-field>
</mat-form-field> </div>
</div> }
<div class="tb-form-row"> <div class="tb-form-row">
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-key' | translate }}</div> <div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-key' | translate }}</div>
<tb-entity-key-autocomplete <tb-entity-key-autocomplete

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

@ -16,7 +16,7 @@
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { charsWithNumRegex, noLeadTrailSpacesRegex } from '@shared/models/regex.constants'; import { charsWithNumRegex, noLeadTrailSpacesRegex } from '@shared/models/regex.constants';
import { import {
ArgumentEntityType, ArgumentEntityType,
@ -52,11 +52,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
@Input() tenantId: string; @Input() tenantId: string;
@Input() entityName: string; @Input() entityName: string;
@Input() calculatedFieldType: CalculatedFieldType; @Input() calculatedFieldType: CalculatedFieldType;
@Input() usedArgumentNames: string[];
argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>(); argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>();
argumentFormGroup = this.fb.group({ argumentFormGroup = this.fb.group({
argumentName: ['', [Validators.required, Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]], argumentName: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
refEntityId: this.fb.group({ refEntityId: this.fb.group({
entityType: [ArgumentEntityType.Current], entityType: [ArgumentEntityType.Current],
id: [''] id: ['']
@ -109,6 +110,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
return this.argumentFormGroup.get('refEntityKey') as FormGroup; return this.argumentFormGroup.get('refEntityKey') as FormGroup;
} }
get enableAttributeScopeSelection(): boolean {
return this.entityType === ArgumentEntityType.Device
|| (this.entityType === ArgumentEntityType.Current
&& (this.entityId.entityType === EntityType.DEVICE || this.entityId.entityType === EntityType.DEVICE_PROFILE))
}
ngOnInit(): void { ngOnInit(): void {
this.argumentFormGroup.patchValue(this.argument, {emitEvent: false}); this.argumentFormGroup.patchValue(this.argument, {emitEvent: false});
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId); this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId);
@ -188,9 +195,21 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
this.argumentFormGroup.get('refEntityId').get('id').setValue(''); this.argumentFormGroup.get('refEntityId').get('id').setValue('');
this.argumentFormGroup.get('refEntityId') this.argumentFormGroup.get('refEntityId')
.get('id')[type === ArgumentEntityType.Tenant || type === ArgumentEntityType.Current ? 'disable' : 'enable'](); .get('id')[type === ArgumentEntityType.Tenant || type === ArgumentEntityType.Current ? 'disable' : 'enable']();
if (!this.enableAttributeScopeSelection) {
this.refEntityKeyFormGroup.get('scope').setValue(AttributeScope.SERVER_SCOPE);
}
}); });
} }
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 { private observeEntityKeyChanges(): void {
this.argumentFormGroup.get('refEntityKey').get('type').valueChanges this.argumentFormGroup.get('refEntityKey').get('type').valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())

1
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -997,7 +997,6 @@ export class ImportExportService {
&& !!Object.keys(configuration.arguments).length && !!Object.keys(configuration.arguments).length
&& isDefined(configuration.expression) && isDefined(configuration.expression)
&& isDefined(configuration.output) && isDefined(configuration.output)
&& isNotEmptyStr(configuration.output.name);
} }
private validateImportedImage(image: ImageExportData): boolean { private validateImportedImage(image: ImageExportData): boolean {

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1064,6 +1064,7 @@
"expression-max-length": "Expression length should be less than 255 characters.", "expression-max-length": "Expression length should be less than 255 characters.",
"argument-name-required": "Argument name is required.", "argument-name-required": "Argument name is required.",
"argument-name-pattern": "Argument name is invalid.", "argument-name-pattern": "Argument name is invalid.",
"argument-name-duplicate": "Argument with such name already exists.",
"argument-name-max-length": "Argument name should be less than 256 characters.", "argument-name-max-length": "Argument name should be less than 256 characters.",
"argument-type-required": "Argument type is required." "argument-type-required": "Argument type is required."
} }

7
ui-ngx/src/form.scss

@ -163,6 +163,13 @@
.tb-form-panel-title { .tb-form-panel-title {
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
&.tb-required::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
} }
.tb-form-panel-hint { .tb-form-panel-hint {
font-size: 12px; font-size: 12px;

Loading…
Cancel
Save