Calculated field fixes and improvements
This commit is contained in:
parent
0260c966bc
commit
96e292fac5
@ -88,9 +88,8 @@
|
||||
[matTooltip]="'action.edit' | translate"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon
|
||||
[matBadgeHidden]="!(argumentsFormArray.dirty
|
||||
&& group.get('refEntityKey').get('type').value === ArgumentType.Rolling
|
||||
&& calculatedFieldType() === CalculatedFieldType.SIMPLE)"
|
||||
[matBadgeHidden]="!(group.get('refEntityKey').get('type').value === ArgumentType.Rolling
|
||||
&& calculatedFieldType === CalculatedFieldType.SIMPLE)"
|
||||
matBadgeColor="warn"
|
||||
matBadgeSize="small"
|
||||
matBadge="*"
|
||||
@ -111,7 +110,7 @@
|
||||
<span class="tb-prompt flex items-center justify-center">{{ 'calculated-fields.no-arguments' | translate }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (errorText && this.argumentsFormArray.dirty) {
|
||||
@if (errorText) {
|
||||
<tb-error noMargin [error]="errorText | translate" class="pl-3"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -17,9 +17,7 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
effect,
|
||||
forwardRef,
|
||||
input,
|
||||
Input,
|
||||
OnChanges,
|
||||
Renderer2,
|
||||
@ -77,8 +75,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
|
||||
@Input() entityId: EntityId;
|
||||
@Input() tenantId: string;
|
||||
@Input() entityName: string;
|
||||
|
||||
calculatedFieldType = input<CalculatedFieldType>()
|
||||
@Input() calculatedFieldType: CalculatedFieldType;
|
||||
|
||||
errorText = '';
|
||||
argumentsFormArray = this.fb.array<AbstractControl>([]);
|
||||
@ -103,17 +100,12 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
|
||||
this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
|
||||
this.propagateChange(this.getArgumentsObject());
|
||||
});
|
||||
effect(() => {
|
||||
if (this.calculatedFieldType() && this.argumentsFormArray.dirty) {
|
||||
this.argumentsFormArray.updateValueAndValidity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (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)) {
|
||||
this.popoverService.hidePopover(trigger);
|
||||
} else {
|
||||
const argumentObj = this.argumentsFormArray.at(index)?.getRawValue() ?? {};
|
||||
const ctx = {
|
||||
index,
|
||||
argument: this.argumentsFormArray.at(index)?.getRawValue() ?? {},
|
||||
argument: argumentObj,
|
||||
entityId: this.entityId,
|
||||
calculatedFieldType: this.calculatedFieldType(),
|
||||
calculatedFieldType: this.calculatedFieldType,
|
||||
buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add',
|
||||
tenantId: this.tenantId,
|
||||
entityName: this.entityName,
|
||||
argumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argumentObj.argumentName),
|
||||
};
|
||||
this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer,
|
||||
this.viewContainerRef, CalculatedFieldArgumentPanelComponent, 'left', false, null,
|
||||
@ -171,7 +165,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
|
||||
}
|
||||
|
||||
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.errorText = 'calculated-fields.hint.arguments-simple-with-rolling';
|
||||
} else if (!this.argumentsFormArray.controls.length) {
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
</div>
|
||||
<ng-container [formGroup]="configFormGroup">
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title">{{ 'calculated-fields.arguments' | translate }}</div>
|
||||
<div class="tb-form-panel-title">{{ 'calculated-fields.arguments' | translate }}*</div>
|
||||
<tb-calculated-field-arguments-table
|
||||
formControlName="arguments"
|
||||
[entityId]="data.entityId"
|
||||
|
||||
@ -31,7 +31,15 @@
|
||||
class="tb-error">
|
||||
warning
|
||||
</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
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
@ -103,25 +111,24 @@
|
||||
<tb-entity-key-autocomplete class="flex-1" formControlName="key" [dataKeyType]="DataKeyType.timeseries" [entityFilter]="entityFilter"/>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-scope' | translate }}</div>
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex-1">
|
||||
<mat-select formControlName="scope">
|
||||
<mat-option [value]="AttributeScope.SERVER_SCOPE">
|
||||
{{ 'calculated-fields.server-attributes' | translate }}
|
||||
</mat-option>
|
||||
@if (entityType === ArgumentEntityType.Device
|
||||
|| entityType === ArgumentEntityType.Current && entityId.entityType === EntityType.DEVICE) {
|
||||
@if (isDeviceEntity) {
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-scope' | translate }}</div>
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex-1">
|
||||
<mat-select formControlName="scope">
|
||||
<mat-option [value]="AttributeScope.SERVER_SCOPE">
|
||||
{{ 'calculated-fields.server-attributes' | translate }}
|
||||
</mat-option>
|
||||
<mat-option [value]="AttributeScope.CLIENT_SCOPE">
|
||||
{{ 'calculated-fields.client-attributes' | translate }}
|
||||
</mat-option>
|
||||
<mat-option [value]="AttributeScope.SHARED_SCOPE">
|
||||
{{ 'calculated-fields.shared-attributes' | translate }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-key' | translate }}</div>
|
||||
<tb-entity-key-autocomplete
|
||||
@ -148,7 +155,7 @@
|
||||
<tb-timeinterval
|
||||
subscriptSizing="dynamic"
|
||||
appearance="outline"
|
||||
class="flex-1"
|
||||
class="time-window-field flex-1"
|
||||
formControlName="timeWindow"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
:host ::ng-deep {
|
||||
.time-window-field {
|
||||
.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field .mdc-notched-outline__notch {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup, UntypedFormControl, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { charsWithNumRegex, noLeadTrailSpacesRegex } from '@shared/models/regex.constants';
|
||||
import {
|
||||
ArgumentEntityType,
|
||||
@ -42,6 +42,7 @@ import { MINUTE } from '@shared/models/time/time.models';
|
||||
@Component({
|
||||
selector: 'tb-calculated-field-argument-panel',
|
||||
templateUrl: './calculated-field-argument-panel.component.html',
|
||||
styleUrls: ['./calculated-field-argument-panel.component.scss']
|
||||
})
|
||||
export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
||||
|
||||
@ -52,11 +53,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
||||
@Input() tenantId: string;
|
||||
@Input() entityName: string;
|
||||
@Input() calculatedFieldType: CalculatedFieldType;
|
||||
@Input() argumentNames: string[];
|
||||
|
||||
argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>();
|
||||
|
||||
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({
|
||||
entityType: [ArgumentEntityType.Current],
|
||||
id: ['']
|
||||
@ -109,6 +111,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
||||
return this.argumentFormGroup.get('refEntityKey') as FormGroup;
|
||||
}
|
||||
|
||||
get isDeviceEntity(): boolean {
|
||||
return this.entityType === ArgumentEntityType.Device
|
||||
|| (this.entityType === ArgumentEntityType.Current
|
||||
&& (this.entityId.entityType === EntityType.DEVICE || this.entityId.entityType === EntityType.DEVICE_PROFILE))
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.argumentFormGroup.patchValue(this.argument, {emitEvent: false});
|
||||
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId);
|
||||
@ -188,9 +196,21 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
||||
this.argumentFormGroup.get('refEntityId').get('id').setValue('');
|
||||
this.argumentFormGroup.get('refEntityId')
|
||||
.get('id')[type === ArgumentEntityType.Tenant || type === ArgumentEntityType.Current ? 'disable' : 'enable']();
|
||||
if (!this.isDeviceEntity) {
|
||||
this.refEntityKeyFormGroup.get('scope').setValue(AttributeScope.SERVER_SCOPE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private uniqNameRequired(): ValidatorFn {
|
||||
return (control: UntypedFormControl) => {
|
||||
const newName = control.value.trim().toLowerCase();
|
||||
const isDuplicate = this.argumentNames?.some(name => name.toLowerCase() === newName);
|
||||
|
||||
return isDuplicate ? { duplicateName: true } : null;
|
||||
};
|
||||
}
|
||||
|
||||
private observeEntityKeyChanges(): void {
|
||||
this.argumentFormGroup.get('refEntityKey').get('type').valueChanges
|
||||
.pipe(takeUntilDestroyed())
|
||||
|
||||
@ -997,7 +997,6 @@ export class ImportExportService {
|
||||
&& !!Object.keys(configuration.arguments).length
|
||||
&& isDefined(configuration.expression)
|
||||
&& isDefined(configuration.output)
|
||||
&& isNotEmptyStr(configuration.output.name);
|
||||
}
|
||||
|
||||
private validateImportedImage(image: ImageExportData): boolean {
|
||||
|
||||
@ -1064,6 +1064,7 @@
|
||||
"expression-max-length": "Expression length should be less than 255 characters.",
|
||||
"argument-name-required": "Argument name is required.",
|
||||
"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-type-required": "Argument type is required."
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user