This commit is contained in:
mpetrov 2025-01-31 15:33:30 +02:00
parent d3216f3ee7
commit bc835eb9d4
8 changed files with 111 additions and 77 deletions

View File

@ -77,12 +77,12 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.defaultSortOrder = {property: 'name', direction: Direction.DESC};
this.columns.push(
new EntityTableColumn<CalculatedField>('name', 'common.name', '33%'));
this.columns.push(
new EntityTableColumn<CalculatedField>('type', 'common.type', '50px'));
this.columns.push(
new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '50%', entity => entity.configuration.expression));
const expressionColumn = new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '33%', entity => entity.configuration?.expression);
expressionColumn.sortable = false;
this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', '33%'));
this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', '50px'));
this.columns.push(expressionColumn);
this.cellActionDescriptors.push(
{

View File

@ -133,7 +133,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
};
this.keysPopupClosed = false;
const argumentsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, CalculatedFieldArgumentPanelComponent, 'leftBottom', false, null,
this.viewContainerRef, CalculatedFieldArgumentPanelComponent, 'left', false, null,
ctx,
{},
{}, {}, true);

View File

@ -84,10 +84,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
this.applyDialogData();
this.outputFormGroup.get('type').valueChanges
.pipe(takeUntilDestroyed())
.subscribe(type => this.toggleScopeByOutputType(type));
this.toggleScopeByOutputType(this.outputFormGroup.get('type').value);
this.observeTypeChanges();
}
get configFormGroup(): FormGroup {
@ -113,10 +110,26 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
private applyDialogData(): void {
const { configuration = {}, type = CalculatedFieldType.SIMPLE, ...value } = this.data.value;
const { expression, ...restConfig } = configuration as CalculatedFieldConfiguration;
this.fieldFormGroup.patchValue({ configuration: { ...restConfig, ['expression'+type]: expression }, ...value });
this.fieldFormGroup.patchValue({ configuration: { ...restConfig, ['expression'+type]: expression }, type, ...value });
}
private observeTypeChanges(): void {
this.toggleKeyByCalculatedFieldType(this.fieldFormGroup.get('type').value);
this.toggleScopeByOutputType(this.outputFormGroup.get('type').value);
this.outputFormGroup.get('type').valueChanges
.pipe(takeUntilDestroyed())
.subscribe(type => this.toggleScopeByOutputType(type));
this.fieldFormGroup.get('type').valueChanges
.pipe(takeUntilDestroyed())
.subscribe(type => this.toggleKeyByCalculatedFieldType(type));
}
private toggleScopeByOutputType(type: OutputType): void {
this.outputFormGroup.get('scope')[type === OutputType.Attribute? 'enable' : 'disable']({emitEvent: false});
}
private toggleKeyByCalculatedFieldType(type: CalculatedFieldType): void {
this.outputFormGroup.get('name')[type === CalculatedFieldType.SIMPLE? 'enable' : 'disable']({emitEvent: false});
}
}

View File

@ -24,33 +24,35 @@
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="argumentName" maxlength="255" placeholder="{{ 'action.set' | translate }}"/>
@if (argumentFormGroup.get('argumentName').touched) {
@if (argumentFormGroup.get('argumentName').hasError('required')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-required' | translate"
class="tb-error">
warning
</mat-icon>
} @else if (argumentFormGroup.get('argumentName').hasError('pattern')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-pattern' | translate"
class="tb-error">
warning
</mat-icon>
} @else if (argumentFormGroup.get('argumentName').hasError('maxlength')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-max-length' | translate"
class="tb-error">
warning
</mat-icon>
<div class="h-5 pr-2">
@if (argumentFormGroup.get('argumentName').touched) {
@if (argumentFormGroup.get('argumentName').hasError('required')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-required' | translate"
class="tb-error">
warning
</mat-icon>
} @else if (argumentFormGroup.get('argumentName').hasError('pattern')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-pattern' | translate"
class="tb-error">
warning
</mat-icon>
} @else if (argumentFormGroup.get('argumentName').hasError('maxlength')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'calculated-fields.hint.argument-name-max-length' | translate"
class="tb-error">
warning
</mat-icon>
}
}
}
</div>
</mat-form-field>
</div>
</div>
@ -117,40 +119,42 @@
}
</mat-form-field>
</div>
@if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Attribute) {
<div class="tb-form-row">
<div class="fixed-title-width tb-required">{{ 'calculated-fields.timeseries-key' | translate }}</div>
<tb-entity-key-autocomplete class="w-full" 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">
<mat-select formControlName="scope" class="w-full">
<mat-option [value]="AttributeScope.SERVER_SCOPE">
{{ 'calculated-fields.server-attributes' | translate }}
</mat-option>
@if ((keyEntityType$ | async) === EntityType.DEVICE) {
<mat-option [value]="AttributeScope.CLIENT_SCOPE">
{{ 'calculated-fields.client-attributes' | translate }}
@if (entityFilter.singleEntity.id || entityType === ArgumentEntityType.Current || entityType === ArgumentEntityType.Tenant) {
@if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Attribute) {
<div class="tb-form-row">
<div class="fixed-title-width tb-required">{{ 'calculated-fields.timeseries-key' | translate }}</div>
<tb-entity-key-autocomplete class="w-full" 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">
<mat-select formControlName="scope" class="w-full">
<mat-option [value]="AttributeScope.SERVER_SCOPE">
{{ 'calculated-fields.server-attributes' | translate }}
</mat-option>
<mat-option [value]="AttributeScope.SHARED_SCOPE">
{{ 'calculated-fields.shared-attributes' | translate }}
</mat-option>
}
</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
formControlName="key"
class="w-full"
[dataKeyType]="DataKeyType.attribute"
[entityFilter]="entityFilter"
[keyScopeType]="argumentFormGroup.get('refEntityKey').get('scope').value"
/>
</div>
@if ((keyEntityType$ | async) === EntityType.DEVICE) {
<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>
<div class="tb-form-row">
<div class="fixed-title-width tb-required">{{ 'calculated-fields.attribute-key' | translate }}</div>
<tb-entity-key-autocomplete
formControlName="key"
class="w-full"
[dataKeyType]="DataKeyType.attribute"
[entityFilter]="entityFilter"
[keyScopeType]="argumentFormGroup.get('refEntityKey').get('scope').value"
/>
</div>
}
}
</ng-container>
@if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Rolling) {

View File

@ -25,5 +25,9 @@
width: 100%;
}
}
.mat-mdc-form-field-infix {
display: flex;
}
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, ElementRef, Input, OnInit, output, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, output, ViewChild } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -27,7 +27,7 @@ import {
CalculatedFieldArgumentValue,
CalculatedFieldType
} from '@shared/models/calculated-field.models';
import { debounceTime, delay, distinctUntilChanged, filter, map, startWith } from 'rxjs/operators';
import { delay, distinctUntilChanged, filter, map, startWith, throttleTime } from 'rxjs/operators';
import { EntityType } from '@shared/models/entity-type.models';
import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DatasourceType } from '@shared/models/widget.models';
@ -92,6 +92,7 @@ export class CalculatedFieldArgumentPanelComponent extends PageComponent impleme
constructor(
private fb: FormBuilder,
private cd: ChangeDetectorRef
) {
super();
@ -154,22 +155,24 @@ export class CalculatedFieldArgumentPanelComponent extends PageComponent impleme
default:
entityId = this.argumentFormGroup.get('refEntityId').value as any;
}
if (onInit) {
if (!onInit) {
this.argumentFormGroup.get('refEntityKey').get('key').setValue('');
}
this.entityFilter = {
type: AliasFilterType.singleEntity,
singleEntity: entityId,
};
this.cd.markForCheck();
}
private observeEntityFilterChanges(): void {
merge(
this.refEntityIdFormGroup.get('entityType').valueChanges,
this.refEntityKeyFormGroup.get('type').valueChanges,
this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)),
this.refEntityKeyFormGroup.get('scope').valueChanges,
)
.pipe(debounceTime(300), delay(50), takeUntilDestroyed())
.pipe(throttleTime(100), delay(50), takeUntilDestroyed())
.subscribe(() => this.updateEntityFilter(this.entityType));
}

View File

@ -29,7 +29,7 @@
<a *ngIf="selectEntityFormGroup.get('entity').value && disabled" aria-label="Open device profile" [routerLink]=entityURL>
{{ displayEntityFn(selectEntityFormGroup.get('entity').value) }}
</a>
<mat-icon *ngIf="selectEntityFormGroup.get('entity').hasError('required') && iconError"
<mat-icon *ngIf="iconError && selectEntityFormGroup.get('entity').hasError('required') && selectEntityFormGroup.get('entity').touched"
matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"

View File

@ -16,7 +16,7 @@
-->
<mat-form-field class="tb-flex no-gap !w-full" appearance="outline" subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ 'entity.key-name' | translate }}"
<input matInput type="text" placeholder="{{ 'action.set' | translate }}"
#keyInput
[formControl]="keyControl"
required
@ -28,12 +28,22 @@
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
} @else if (keyControl.hasError('required') && keyControl.touched) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'common.hint.key-required' | translate"
class="tb-error">
warning
</mat-icon>
}
<mat-autocomplete
class="tb-autocomplete"
#keysAutocomplete="matAutocomplete">
@for (key of filteredKeys$ | async; track key) {
<mat-option [value]="key"><span [innerHTML]="key | highlight: searchText"></span></mat-option>
} @empty {
<mat-option [value]="''">{{ 'entity.no-keys-found' | translate }}</mat-option>
}
</mat-autocomplete>
</mat-form-field>