Merge pull request #12719 from maxunbearable/feature/calculated-fields-adjustments-19-02

Calculated fields adjustments
This commit is contained in:
Vladyslav Prykhodko 2025-02-24 12:42:07 +02:00 committed by GitHub
commit 4dad2bbc53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 126 additions and 47 deletions

View File

@ -44,6 +44,7 @@ import {
CalculatedFieldTestScriptDialogData, CalculatedFieldTestScriptDialogData,
getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsEditorCompleter,
getCalculatedFieldArgumentsHighlights, getCalculatedFieldArgumentsHighlights,
CalculatedFieldTypeTranslations,
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { import {
CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogComponent,
@ -112,7 +113,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
expressionColumn.sortable = false; expressionColumn.sortable = false;
this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', '33%')); 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>('type', 'common.type', '50px', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type))));
this.columns.push(expressionColumn); this.columns.push(expressionColumn);
this.cellActionDescriptors.push( this.cellActionDescriptors.push(

View File

@ -73,7 +73,7 @@
</mat-select> </mat-select>
} }
</mat-form-field> </mat-form-field>
<mat-chip-listbox formControlName="key" class="tb-inline-field w-1/6 xs:w-1/3"> <mat-chip-listbox formControlName="key" class="tb-inline-field entity-key-field w-1/6 xs:w-1/3">
<mat-chip> <mat-chip>
<div tbTruncateWithTooltip> <div tbTruncateWithTooltip>
{{ group.get('refEntityKey').get('key').value }} {{ group.get('refEntityKey').get('key').value }}

View File

@ -16,6 +16,10 @@
@use '../../../../../../../scss/constants' as constants; @use '../../../../../../../scss/constants' as constants;
:host { :host {
.entity-key-field {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.tb-form-table-row-cell-buttons { .tb-form-table-row-cell-buttons {
--mat-badge-legacy-small-size-container-size: 8px; --mat-badge-legacy-small-size-container-size: 8px;
--mat-badge-small-size-container-overlap-offset: -5px; --mat-badge-small-size-container-overlap-offset: -5px;

View File

@ -74,10 +74,10 @@
[calculatedFieldType]="fieldFormGroup.get('type').value" [calculatedFieldType]="fieldFormGroup.get('type').value"
/> />
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel no-gap">
<div class="tb-form-panel-title tb-required">{{ '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="mt-3" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'action.set' | translate" required> <input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'action.set' | translate" required>
@if (configFormGroup.get('expressionSIMPLE').errors && configFormGroup.get('expressionSIMPLE').touched) { @if (configFormGroup.get('expressionSIMPLE').errors && configFormGroup.get('expressionSIMPLE').touched) {
<mat-error> <mat-error>
@ -104,12 +104,13 @@
[editorCompleter]="argumentsEditorCompleter$ | async" [editorCompleter]="argumentsEditorCompleter$ | async"
helpId="calculated-field/expression_fn" helpId="calculated-field/expression_fn"
> >
<div toolbarPrefixButton class="tb-primary-background script-lang-chip">{{ 'api-usage.tbel' | translate }}</div>
<button toolbarSuffixButton <button toolbarSuffixButton
mat-icon-button mat-icon-button
matTooltip="{{ 'common.test-function' | translate }}" matTooltip="{{ 'common.test-function' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
class="tb-mat-32" class="tb-mat-32"
[disabled]="configFormGroup.get('expressionSCRIPT').invalid || configFormGroup.get('arguments').invalid" [disabled]="configFormGroup.get('arguments').invalid"
(click)="onTestScript()"> (click)="onTestScript()">
<mat-icon class="material-icons" color="primary">bug_report</mat-icon> <mat-icon class="material-icons" color="primary">bug_report</mat-icon>
</button> </button>
@ -118,7 +119,7 @@
<button mat-button mat-raised-button color="primary" <button mat-button mat-raised-button color="primary"
type="button" type="button"
(click)="onTestScript()" (click)="onTestScript()"
[disabled]="configFormGroup.get('expressionSCRIPT').invalid || configFormGroup.get('arguments').invalid"> [disabled]="configFormGroup.get('arguments').invalid">
{{ 'common.test-function' | translate }} {{ 'common.test-function' | translate }}
</button> </button>
</div> </div>

View File

@ -19,6 +19,19 @@
width: 869px; width: 869px;
max-width: 100%; max-width: 100%;
} }
.script-lang-chip {
line-height: 20px;
font-size: 14px;
font-weight: 500;
color: white;
border-radius: 100px;
width: 70px;
display: flex;
justify-content: center;
margin-top: 2px;
margin-right: 4px;
}
} }
:host ::ng-deep { :host ::ng-deep {

View File

@ -162,13 +162,13 @@
<tb-timeinterval <tb-timeinterval
subscriptSizing="dynamic" subscriptSizing="dynamic"
appearance="outline" appearance="outline"
class="flex-1" class="time-interval-field flex-1"
formControlName="timeWindow" formControlName="timeWindow"
/> />
</div> </div>
<div class="tb-form-row"> <div class="tb-form-row limit-field-row">
<div class="fixed-title-width">{{ 'calculated-fields.limit' | translate }}</div> <div class="fixed-title-width">{{ 'calculated-fields.limit' | translate }}</div>
<tb-datapoints-limit class="flex-1" formControlName="limit"/> <tb-datapoints-limit class="w-full flex-1" formControlName="limit"/>
</div> </div>
} }
</div> </div>

View File

@ -15,14 +15,37 @@
*/ */
@use '../../../../../../../scss/constants' as constants; @use '../../../../../../../scss/constants' as constants;
$panel-width: 520px;
:host { :host {
display: flex; display: flex;
width: 520px; width: $panel-width;
max-width: 100%; max-width: 100%;
max-height: 100vh;
.fixed-title-width { .fixed-title-width {
@media #{constants.$mat-lt-sm} { @media #{constants.$mat-xs} {
min-width: 120px; min-width: 120px;
} }
} }
} }
:host ::ng-deep {
.limit-field-row {
@media screen and (max-width: $panel-width) {
display: flex;
flex-direction: column;
.fixed-title-width {
align-self: flex-start;
padding-top: 8px;
}
}
}
.time-interval-field {
.advanced-input {
flex-direction: column;
}
}
}

View File

@ -28,7 +28,7 @@ import {
CalculatedFieldType, CalculatedFieldType,
getCalculatedFieldCurrentEntityFilter getCalculatedFieldCurrentEntityFilter
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; import { debounceTime, delay, distinctUntilChanged, filter } 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';
@ -38,6 +38,7 @@ import { EntityFilter } from '@shared/models/query/query.models';
import { AliasFilterType } from '@shared/models/alias.models'; import { AliasFilterType } from '@shared/models/alias.models';
import { merge } from 'rxjs'; import { merge } from 'rxjs';
import { MINUTE } from '@shared/models/time/time.models'; import { MINUTE } from '@shared/models/time/time.models';
import { TimeService } from '@core/services/time.service';
@Component({ @Component({
selector: 'tb-calculated-field-argument-panel', selector: 'tb-calculated-field-argument-panel',
@ -57,6 +58,8 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>(); argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>();
readonly defaultLimit = Math.max(this.timeService.getMinDatapointsLimit(), Math.floor(this.timeService.getMaxDatapointsLimit() / 10));
argumentFormGroup = this.fb.group({ argumentFormGroup = this.fb.group({
argumentName: ['', [Validators.required, this.uniqNameRequired(), 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({
@ -69,7 +72,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]], scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]],
}), }),
defaultValue: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], defaultValue: ['', [Validators.pattern(noLeadTrailSpacesRegex)]],
limit: [1000], limit: [this.defaultLimit],
timeWindow: [MINUTE * 15], timeWindow: [MINUTE * 15],
}); });
@ -92,11 +95,13 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private popover: TbPopoverComponent<CalculatedFieldArgumentPanelComponent> private popover: TbPopoverComponent<CalculatedFieldArgumentPanelComponent>,
private timeService: TimeService
) { ) {
this.observeEntityFilterChanges(); this.observeEntityFilterChanges();
this.observeEntityTypeChanges() this.observeEntityTypeChanges()
this.observeEntityKeyChanges(); this.observeEntityKeyChanges();
this.observeUpdatePosition();
} }
get entityType(): ArgumentEntityType { get entityType(): ArgumentEntityType {
@ -224,4 +229,15 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
typeControl.markAsTouched(); typeControl.markAsTouched();
} }
} }
private observeUpdatePosition(): void {
merge(
this.refEntityIdFormGroup.get('entityType').valueChanges,
this.refEntityKeyFormGroup.get('type').valueChanges,
this.argumentFormGroup.get('timeWindow').valueChanges,
this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)),
)
.pipe(delay(50), takeUntilDestroyed())
.subscribe(() => this.popover.updatePosition());
}
} }

View File

@ -39,11 +39,11 @@
<div class="flex flex-1 items-center gap-2"> <div class="flex flex-1 items-center gap-2">
@if (argumentsTypeMap.get(group.get('argumentName').value) === ArgumentType.Rolling) { @if (argumentsTypeMap.get(group.get('argumentName').value) === ArgumentType.Rolling) {
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field flex-1"> <mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field flex-1">
<input matInput tb-json-to-string name="values" formControlName="values" placeholder="{{ 'value.json-value' | translate }}*"/> <input matInput tb-json-to-string name="rollingJson" formControlName="rollingJson" placeholder="{{ 'value.json-value' | translate }}"/>
</mat-form-field> </mat-form-field>
} @else { } @else {
<mat-form-field appearance="outline" class="tb-inline-field w-1/3" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="tb-inline-field w-1/3" subscriptSizing="dynamic">
<input matInput formControlName="ts" type="number" placeholder="{{ 'action.set' | translate }}"> <input matInput formControlName="ts" type="number" placeholder="{{ 'common.timestamp' | translate }}">
</mat-form-field> </mat-form-field>
<tb-value-input class="argument-value min-w-60 flex-1" [required]="false" [hideJsonEdit]="true" [shortBooleanField]="true" formControlName="value"/> <tb-value-input class="argument-value min-w-60 flex-1" [required]="false" [hideJsonEdit]="true" [shortBooleanField]="true" formControlName="value"/>
} }

View File

@ -110,14 +110,14 @@ export class CalculatedFieldTestArgumentsComponent extends PageComponent impleme
minWidth: 'min(700px, 100%)', minWidth: 'min(700px, 100%)',
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
jsonValue: group.value, jsonValue: this.argumentsTypeMap.get(group.get('argumentName').value) === ArgumentType.Rolling ? group.value.rollingJson : group.value,
required: true, required: true,
fillHeight: true fillHeight: true
} }
}).afterClosed() }).afterClosed()
.pipe(filter(Boolean)) .pipe(filter(Boolean))
.subscribe(result => this.argumentsTypeMap.get(group.get('argumentName').value) === ArgumentType.Rolling .subscribe(result => this.argumentsTypeMap.get(group.get('argumentName').value) === ArgumentType.Rolling
? group.patchValue({ timeWindow: (result as CalculatedFieldRollingTelemetryArgumentValue).timeWindow, values: (result as CalculatedFieldRollingTelemetryArgumentValue).values }) ? group.get('rollingJson').patchValue({ values: (result as CalculatedFieldRollingTelemetryArgumentValue).values, timeWindow: (result as CalculatedFieldRollingTelemetryArgumentValue).timeWindow })
: group.patchValue({ ts: (result as CalculatedFieldSingleArgumentValue).ts, value: (result as CalculatedFieldSingleArgumentValue).value }) ); : group.patchValue({ ts: (result as CalculatedFieldSingleArgumentValue).ts, value: (result as CalculatedFieldSingleArgumentValue).value }) );
} }
@ -131,16 +131,15 @@ export class CalculatedFieldTestArgumentsComponent extends PageComponent impleme
private getRollingArgumentFormGroup({ argumentName, timeWindow, values }: CalculatedFieldRollingTelemetryArgumentValue): FormGroup { private getRollingArgumentFormGroup({ argumentName, timeWindow, values }: CalculatedFieldRollingTelemetryArgumentValue): FormGroup {
return this.fb.group({ return this.fb.group({
timeWindow: [timeWindow ?? {}],
argumentName: [{ value: argumentName, disabled: true }], argumentName: [{ value: argumentName, disabled: true }],
values: [values] rollingJson: [{ values: values ?? [], timeWindow: timeWindow ?? {} }]
}) as FormGroup; }) as FormGroup;
} }
private getValue(): CalculatedFieldEventArguments { private getValue(): CalculatedFieldEventArguments {
return this.argumentsFormArray.getRawValue().reduce((acc, rowItem) => { return this.argumentsFormArray.getRawValue().reduce((acc, rowItem) => {
const { argumentName, ...value } = rowItem; const { argumentName, rollingJson = {}, ...value } = rowItem;
acc[argumentName] = value; acc[argumentName] = { ...rollingJson, ...value };
return acc; return acc;
}, {}); }, {});
} }

View File

@ -38,6 +38,7 @@
functionName="calculate" functionName="calculate"
class="expression-edit" class="expression-edit"
[functionArgs]="functionArgs" [functionArgs]="functionArgs"
[required]="true"
[disableUndefinedCheck]="true" [disableUndefinedCheck]="true"
[fillHeight]="true" [fillHeight]="true"
[highlightRules]="data.argumentsHighlightRules" [highlightRules]="data.argumentsHighlightRules"

View File

@ -26,7 +26,7 @@ import {
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { FormBuilder } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { NEVER, Observable, of, switchMap } from 'rxjs'; import { NEVER, Observable, of, switchMap } from 'rxjs';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
@ -62,7 +62,7 @@ export class CalculatedFieldScriptTestDialogComponent extends DialogComponent<Ca
@ViewChild('expressionContent', {static: true}) expressionContent: JsonContentComponent; @ViewChild('expressionContent', {static: true}) expressionContent: JsonContentComponent;
calculatedFieldScriptTestFormGroup = this.fb.group({ calculatedFieldScriptTestFormGroup = this.fb.group({
expression: [], expression: ['', Validators.required],
arguments: [], arguments: [],
output: [] output: []
}); });

View File

@ -183,6 +183,7 @@
[class.lt-lg:!hidden]="column.mobileHide" [class.lt-lg:!hidden]="column.mobileHide"
*matCellDef="let entity; let row = index" *matCellDef="let entity; let row = index"
[matTooltip]="cellTooltip(entity, column, row)" [matTooltip]="cellTooltip(entity, column, row)"
#cellMatTooltip="matTooltip"
matTooltipPosition="above" matTooltipPosition="above"
[style]="cellStyle(entity, column, row)"> [style]="cellStyle(entity, column, row)">
<ng-container [ngSwitch]="column.type"> <ng-container [ngSwitch]="column.type">
@ -208,6 +209,8 @@
[copyText]="column.actionCell.onAction(null, entity)" [copyText]="column.actionCell.onAction(null, entity)"
tooltipText="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}" tooltipText="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}"
tooltipPosition="above" tooltipPosition="above"
(mouseover)="cellMatTooltip.hide()"
(mouseleave)="cellMatTooltip.show()"
[icon]="column.actionCell.icon" [icon]="column.actionCell.icon"
[style]="column.actionCell.style"> [style]="column.actionCell.style">
</tb-copy-button> </tb-copy-button>

View File

@ -403,7 +403,9 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
new EntityTableColumn<Event>('messageType', 'event.message-type', '100px', new EntityTableColumn<Event>('messageType', 'event.message-type', '100px',
(entity) => entity.body.msgType ?? '-', (entity) => entity.body.msgType ?? '-',
() => ({padding: '0 12px 0 0'}), () => ({padding: '0 12px 0 0'}),
false false,
() => ({padding: '0 12px 0 0'}),
(entity) => entity.body.msgType,
), ),
new EntityActionTableColumn<Event>('arguments', 'event.arguments', new EntityActionTableColumn<Event>('arguments', 'event.arguments',
{ {
@ -539,8 +541,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
case DebugEventType.DEBUG_CALCULATED_FIELD: case DebugEventType.DEBUG_CALCULATED_FIELD:
this.filterColumns.push( this.filterColumns.push(
{key: 'entityId', title: 'event.entity-id'}, {key: 'entityId', title: 'event.entity-id'},
{key: 'messageId', title: 'event.message-id'}, {key: 'msgId', title: 'event.message-id'},
{key: 'messageType', title: 'event.message-type'}, {key: 'msgType', title: 'event.message-type'},
{key: 'arguments', title: 'event.arguments'}, {key: 'arguments', title: 'event.arguments'},
{key: 'result', title: 'event.result'}, {key: 'result', title: 'event.result'},
{key: 'isError', title: 'event.error'}, {key: 'isError', title: 'event.error'},

View File

@ -31,7 +31,12 @@ import {
} from '@home/components/widget/lib/scada/scada-symbol.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models'; import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { AceHighlightRule, AceHighlightRules } from '@shared/models/ace/ace.models'; import {
AceHighlightRule,
AceHighlightRules,
dotOperatorHighlightRule,
endGroupHighlightRule
} from '@shared/models/ace/ace.models';
import { HelpLinks, ValueType } from '@shared/models/constants'; import { HelpLinks, ValueType } from '@shared/models/constants';
import { formPropertyCompletions } from '@shared/models/dynamic-form.models'; import { formPropertyCompletions } from '@shared/models/dynamic-form.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
@ -921,17 +926,6 @@ export class ScadaSymbolElement {
const identifierRe = /[a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*/; const identifierRe = /[a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*/;
const dotOperatorHighlightRule: AceHighlightRule = {
token: 'punctuation.operator',
regex: /[.](?![.])/,
};
const endGroupHighlightRule: AceHighlightRule = {
regex: '',
token: 'empty',
next: 'no_regex'
};
const scadaSymbolCtxObjectHighlightRule: AceHighlightRule = { const scadaSymbolCtxObjectHighlightRule: AceHighlightRule = {
token: 'tb.scada-symbol-ctx', token: 'tb.scada-symbol-ctx',
regex: /\bctx\b/, regex: /\bctx\b/,

View File

@ -33,7 +33,7 @@
</mat-form-field> </mat-form-field>
<mat-form-field *ngIf="valueType === valueTypeEnum.STRING" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute flex flex-1"> <mat-form-field *ngIf="valueType === valueTypeEnum.STRING" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute flex flex-1">
<input [disabled]="disabled" matInput [required]="required" name="value" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()" <input [disabled]="disabled" matInput [required]="required" name="value" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()"
placeholder="{{ 'value.string-value' | translate }}*"/> placeholder="{{ 'value.string-value' | translate }}{{ required ? '*' : ''}}"/>
<mat-icon matSuffix <mat-icon matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"
@ -45,7 +45,7 @@
</mat-form-field> </mat-form-field>
<mat-form-field *ngIf="valueType === valueTypeEnum.INTEGER" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number flex flex-1"> <mat-form-field *ngIf="valueType === valueTypeEnum.INTEGER" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number flex flex-1">
<input [disabled]="disabled" matInput [required]="required" name="value" type="number" step="1" pattern="^-?[0-9]+$" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()" <input [disabled]="disabled" matInput [required]="required" name="value" type="number" step="1" pattern="^-?[0-9]+$" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()"
placeholder="{{ 'value.integer-value' | translate }}*"/> placeholder="{{ 'value.integer-value' | translate }}{{ required ? '*' : ''}}"/>
<mat-icon matSuffix <mat-icon matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"
@ -58,7 +58,7 @@
</mat-form-field> </mat-form-field>
<mat-form-field *ngIf="valueType === valueTypeEnum.DOUBLE" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number flex flex-1"> <mat-form-field *ngIf="valueType === valueTypeEnum.DOUBLE" appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number flex flex-1">
<input [disabled]="disabled" matInput [required]="required" name="value" type="number" step="any" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()" <input [disabled]="disabled" matInput [required]="required" name="value" type="number" step="any" #value="ngModel" [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()"
placeholder="{{ 'value.double-value' | translate }}*"/> placeholder="{{ 'value.double-value' | translate }}{{ required ? '*' : ''}}"/>
<mat-icon matSuffix <mat-icon matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"
@ -81,7 +81,7 @@
<div *ngIf="valueType === valueTypeEnum.JSON" class="flex flex-1 flex-row items-center justify-start gap-2"> <div *ngIf="valueType === valueTypeEnum.JSON" class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute flex flex-1"> <mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute flex flex-1">
<input [disabled]="disabled" matInput tb-json-to-string [required]="required" name="value" #value="ngModel" <input [disabled]="disabled" matInput tb-json-to-string [required]="required" name="value" #value="ngModel"
[(ngModel)]="modelValue" (ngModelChange)="onValueChanged()" placeholder="{{ 'value.json-value' | translate }}*"/> [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()" placeholder="{{ 'value.json-value' | translate }}{{ required ? '*' : ''}}"/>
<mat-icon matSuffix <mat-icon matSuffix
matTooltipPosition="above" matTooltipPosition="above"
matTooltipClass="tb-error-tooltip" matTooltipClass="tb-error-tooltip"

View File

@ -365,5 +365,16 @@ export interface AceHighlightRule {
next?: string; next?: string;
} }
export const dotOperatorHighlightRule: AceHighlightRule = {
token: 'punctuation.operator',
regex: /[.](?![.])/,
};
export const endGroupHighlightRule: AceHighlightRule = {
regex: '',
token: 'empty',
next: 'no_regex'
};

View File

@ -28,7 +28,11 @@ import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType } from '@shared/models/alias.models'; import { AliasFilterType } from '@shared/models/alias.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TbEditorCompleter } from '@shared/models/ace/completion.models'; import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { AceHighlightRules } from '@shared/models/ace/ace.models'; import {
AceHighlightRules,
dotOperatorHighlightRule,
endGroupHighlightRule
} from '@shared/models/ace/ace.models';
export interface CalculatedField extends Omit<BaseData<CalculatedFieldId>, 'label'>, HasVersion, HasTenantId, ExportableEntity<CalculatedFieldId> { export interface CalculatedField extends Omit<BaseData<CalculatedFieldId>, 'label'>, HasVersion, HasTenantId, ExportableEntity<CalculatedFieldId> {
debugSettings?: EntityDebugSettings; debugSettings?: EntityDebugSettings;
@ -337,6 +341,7 @@ export const getCalculatedFieldArgumentsHighlights = (
const calculatedFieldSingleArgumentValueHighlightRules: AceHighlightRules = { const calculatedFieldSingleArgumentValueHighlightRules: AceHighlightRules = {
calculatedFieldSingleArgumentValue: [ calculatedFieldSingleArgumentValue: [
dotOperatorHighlightRule,
{ {
token: 'tb.calculated-field-value', token: 'tb.calculated-field-value',
regex: /value/, regex: /value/,
@ -346,12 +351,14 @@ const calculatedFieldSingleArgumentValueHighlightRules: AceHighlightRules = {
token: 'tb.calculated-field-ts', token: 'tb.calculated-field-ts',
regex: /ts/, regex: /ts/,
next: 'no_regex' next: 'no_regex'
} },
endGroupHighlightRule
], ],
} }
const calculatedFieldRollingArgumentValueHighlightRules: AceHighlightRules = { const calculatedFieldRollingArgumentValueHighlightRules: AceHighlightRules = {
calculatedFieldRollingArgumentValue: [ calculatedFieldRollingArgumentValue: [
dotOperatorHighlightRule,
{ {
token: 'tb.calculated-field-values', token: 'tb.calculated-field-values',
regex: /values/, regex: /values/,
@ -361,12 +368,14 @@ const calculatedFieldRollingArgumentValueHighlightRules: AceHighlightRules = {
token: 'tb.calculated-field-time-window', token: 'tb.calculated-field-time-window',
regex: /timeWindow/, regex: /timeWindow/,
next: 'calculatedFieldRollingArgumentTimeWindow' next: 'calculatedFieldRollingArgumentTimeWindow'
} },
endGroupHighlightRule
], ],
} }
const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules = { const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules = {
calculatedFieldRollingArgumentTimeWindow: [ calculatedFieldRollingArgumentTimeWindow: [
dotOperatorHighlightRule,
{ {
token: 'tb.calculated-field-start-ts', token: 'tb.calculated-field-start-ts',
regex: /startTs/, regex: /startTs/,
@ -381,6 +390,7 @@ const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules =
token: 'tb.calculated-field-limit', token: 'tb.calculated-field-limit',
regex: /limit/, regex: /limit/,
next: 'no_regex' next: 'no_regex'
} },
endGroupHighlightRule
] ]
} }

View File

@ -1101,6 +1101,7 @@
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"data": "Data", "data": "Data",
"timestamp": "Timestamp",
"enter-username": "Enter username", "enter-username": "Enter username",
"enter-password": "Enter password", "enter-password": "Enter password",
"enter-search": "Enter search", "enter-search": "Enter search",