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.defaultSortOrder = {property: 'name', direction: Direction.DESC};
this.columns.push( const expressionColumn = new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '33%', entity => entity.configuration?.expression);
new EntityTableColumn<CalculatedField>('name', 'common.name', '33%')); expressionColumn.sortable = false;
this.columns.push(
new EntityTableColumn<CalculatedField>('type', 'common.type', '50px')); this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', '33%'));
this.columns.push( this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', '50px'));
new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '50%', entity => entity.configuration.expression)); this.columns.push(expressionColumn);
this.cellActionDescriptors.push( this.cellActionDescriptors.push(
{ {

View File

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

View File

@ -84,10 +84,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
public fb: UntypedFormBuilder) { public fb: UntypedFormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.applyDialogData(); this.applyDialogData();
this.outputFormGroup.get('type').valueChanges this.observeTypeChanges();
.pipe(takeUntilDestroyed())
.subscribe(type => this.toggleScopeByOutputType(type));
this.toggleScopeByOutputType(this.outputFormGroup.get('type').value);
} }
get configFormGroup(): FormGroup { get configFormGroup(): FormGroup {
@ -113,10 +110,26 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
private applyDialogData(): void { private applyDialogData(): void {
const { configuration = {}, type = CalculatedFieldType.SIMPLE, ...value } = this.data.value; const { configuration = {}, type = CalculatedFieldType.SIMPLE, ...value } = this.data.value;
const { expression, ...restConfig } = configuration as CalculatedFieldConfiguration; 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 { private toggleScopeByOutputType(type: OutputType): void {
this.outputFormGroup.get('scope')[type === OutputType.Attribute? 'enable' : 'disable']({emitEvent: false}); 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,6 +24,7 @@
<div class="tb-flex no-gap"> <div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="argumentName" maxlength="255" placeholder="{{ 'action.set' | translate }}"/> <input matInput name="value" formControlName="argumentName" maxlength="255" placeholder="{{ 'action.set' | translate }}"/>
<div class="h-5 pr-2">
@if (argumentFormGroup.get('argumentName').touched) { @if (argumentFormGroup.get('argumentName').touched) {
@if (argumentFormGroup.get('argumentName').hasError('required')) { @if (argumentFormGroup.get('argumentName').hasError('required')) {
<mat-icon matSuffix <mat-icon matSuffix
@ -51,6 +52,7 @@
</mat-icon> </mat-icon>
} }
} }
</div>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
@ -117,6 +119,7 @@
} }
</mat-form-field> </mat-form-field>
</div> </div>
@if (entityFilter.singleEntity.id || entityType === ArgumentEntityType.Current || entityType === ArgumentEntityType.Tenant) {
@if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Attribute) { @if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Attribute) {
<div class="tb-form-row"> <div class="tb-form-row">
<div class="fixed-title-width tb-required">{{ 'calculated-fields.timeseries-key' | translate }}</div> <div class="fixed-title-width tb-required">{{ 'calculated-fields.timeseries-key' | translate }}</div>
@ -152,6 +155,7 @@
/> />
</div> </div>
} }
}
</ng-container> </ng-container>
@if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Rolling) { @if (refEntityKeyFormGroup.get('type').value !== ArgumentType.Rolling) {
<div class="tb-form-row"> <div class="tb-form-row">

View File

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

View File

@ -14,7 +14,7 @@
/// limitations under the License. /// 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 { TbPopoverComponent } from '@shared/components/popover.component';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -27,7 +27,7 @@ import {
CalculatedFieldArgumentValue, CalculatedFieldArgumentValue,
CalculatedFieldType CalculatedFieldType
} from '@shared/models/calculated-field.models'; } 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 { EntityType } from '@shared/models/entity-type.models';
import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DatasourceType } from '@shared/models/widget.models'; import { DatasourceType } from '@shared/models/widget.models';
@ -92,6 +92,7 @@ export class CalculatedFieldArgumentPanelComponent extends PageComponent impleme
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private cd: ChangeDetectorRef
) { ) {
super(); super();
@ -154,22 +155,24 @@ export class CalculatedFieldArgumentPanelComponent extends PageComponent impleme
default: default:
entityId = this.argumentFormGroup.get('refEntityId').value as any; entityId = this.argumentFormGroup.get('refEntityId').value as any;
} }
if (onInit) { if (!onInit) {
this.argumentFormGroup.get('refEntityKey').get('key').setValue(''); this.argumentFormGroup.get('refEntityKey').get('key').setValue('');
} }
this.entityFilter = { this.entityFilter = {
type: AliasFilterType.singleEntity, type: AliasFilterType.singleEntity,
singleEntity: entityId, singleEntity: entityId,
}; };
this.cd.markForCheck();
} }
private observeEntityFilterChanges(): void { private observeEntityFilterChanges(): void {
merge( merge(
this.refEntityIdFormGroup.get('entityType').valueChanges, this.refEntityIdFormGroup.get('entityType').valueChanges,
this.refEntityKeyFormGroup.get('type').valueChanges,
this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)), this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)),
this.refEntityKeyFormGroup.get('scope').valueChanges, this.refEntityKeyFormGroup.get('scope').valueChanges,
) )
.pipe(debounceTime(300), delay(50), takeUntilDestroyed()) .pipe(throttleTime(100), delay(50), takeUntilDestroyed())
.subscribe(() => this.updateEntityFilter(this.entityType)); .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> <a *ngIf="selectEntityFormGroup.get('entity').value && disabled" aria-label="Open device profile" [routerLink]=entityURL>
{{ displayEntityFn(selectEntityFormGroup.get('entity').value) }} {{ displayEntityFn(selectEntityFormGroup.get('entity').value) }}
</a> </a>
<mat-icon *ngIf="selectEntityFormGroup.get('entity').hasError('required') && iconError" <mat-icon *ngIf="iconError && selectEntityFormGroup.get('entity').hasError('required') && selectEntityFormGroup.get('entity').touched"
matSuffix matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"

View File

@ -16,7 +16,7 @@
--> -->
<mat-form-field class="tb-flex no-gap !w-full" appearance="outline" subscriptSizing="dynamic"> <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 #keyInput
[formControl]="keyControl" [formControl]="keyControl"
required required
@ -28,12 +28,22 @@
(click)="clear()"> (click)="clear()">
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </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 <mat-autocomplete
class="tb-autocomplete" class="tb-autocomplete"
#keysAutocomplete="matAutocomplete"> #keysAutocomplete="matAutocomplete">
@for (key of filteredKeys$ | async; track key) { @for (key of filteredKeys$ | async; track key) {
<mat-option [value]="key"><span [innerHTML]="key | highlight: searchText"></span></mat-option> <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-autocomplete>
</mat-form-field> </mat-form-field>