Browse Source

UI: Calculated field Entity aggregation change model and default value

pull/14253/head
Vladyslav_Prykhodko 11 months ago
parent
commit
fe45f40209
  1. 26
      ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html
  2. 34
      ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts
  3. 8
      ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html
  4. 40
      ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts
  5. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.module.ts
  6. 5
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  7. 14
      ui-ngx/src/assets/locale/locale.constant-en_US.json

26
ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html

@ -62,30 +62,32 @@
</div> </div>
@if (entityAggregationConfiguration.get('interval.type').value === AggIntervalType.CUSTOM) { @if (entityAggregationConfiguration.get('interval.type').value === AggIntervalType.CUSTOM) {
<tb-time-unit-input required <tb-time-unit-input required
[minTime]="0" [minTime]="60"
sameWidthInputs sameWidthInputs
appearance="outline" appearance="outline"
subscriptSizing="dynamic" subscriptSizing="dynamic"
labelText="{{ 'calculated-fields.aggregate-interval-value' | translate }}" labelText="{{ 'calculated-fields.aggregate-interval-value' | translate }}"
minErrorText="{{ 'calculated-fields.aggregate-interval-value-min' | translate }}"
requiredText="{{ 'calculated-fields.aggregate-interval-value-required' | translate }}" requiredText="{{ 'calculated-fields.aggregate-interval-value-required' | translate }}"
formControlName="multiplier"> formControlName="durationSec">
</tb-time-unit-input> </tb-time-unit-input>
} }
<div class="tb-form-panel stroked"> <div class="tb-form-panel stroked">
<mat-slide-toggle class="mat-slide flex" formControlName="allowOffsetMillis"> <mat-slide-toggle class="mat-slide flex" formControlName="allowOffsetSec">
<div tb-hint-tooltip-icon="{{ 'calculated-fields.entity-aggregation.apply-offset-hint' | translate }}"> <div tb-hint-tooltip-icon="{{ 'calculated-fields.entity-aggregation.apply-offset-hint' | translate }}">
{{ 'calculated-fields.entity-aggregation.apply-offset' | translate }} {{ 'calculated-fields.entity-aggregation.apply-offset' | translate }}
</div> </div>
</mat-slide-toggle> </mat-slide-toggle>
@if (entityAggregationConfiguration.get('interval.allowOffsetMillis').value) { @if (entityAggregationConfiguration.get('interval.allowOffsetSec').value) {
<tb-time-unit-input required <tb-time-unit-input required
[minTime]="1" [minTime]="0"
sameWidthInputs sameWidthInputs
appearance="outline" appearance="outline"
subscriptSizing="dynamic" subscriptSizing="dynamic"
labelText="{{ 'calculated-fields.entity-aggregation.offset-value' | translate }}" labelText="{{ 'calculated-fields.entity-aggregation.offset-value' | translate }}"
minErrorText="{{ 'calculated-fields.entity-aggregation.offset-value-min' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.offset-value-required' | translate }}" requiredText="{{ 'calculated-fields.entity-aggregation.offset-value-required' | translate }}"
formControlName="offsetMillis"> formControlName="offsetSec">
</tb-time-unit-input> </tb-time-unit-input>
} }
</div> </div>
@ -99,21 +101,23 @@
@if (entityAggregationConfiguration.get('allowWatermark').value) { @if (entityAggregationConfiguration.get('allowWatermark').value) {
<ng-container formGroupName="watermark"> <ng-container formGroupName="watermark">
<tb-time-unit-input required <tb-time-unit-input required
[minTime]="1" [minTime]="60"
sameWidthInputs sameWidthInputs
appearance="outline" appearance="outline"
labelText="{{ 'calculated-fields.entity-aggregation.wait-duration' | translate }}" labelText="{{ 'calculated-fields.entity-aggregation.duration' | translate }}"
hintText="{{ 'calculated-fields.entity-aggregation.wait-duration-hint' | translate }}" minErrorText="{{ 'calculated-fields.entity-aggregation.duration-min' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.wait-duration-required' | translate }}" hintText="{{ 'calculated-fields.entity-aggregation.duration-hint' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.duration-required' | translate }}"
formControlName="duration"> formControlName="duration">
</tb-time-unit-input> </tb-time-unit-input>
<tb-time-unit-input required <tb-time-unit-input required
[minTime]="0" [minTime]="30"
[maxTime]="entityAggregationConfiguration.get('watermark.duration').value" [maxTime]="entityAggregationConfiguration.get('watermark.duration').value"
sameWidthInputs sameWidthInputs
appearance="outline" appearance="outline"
labelText="{{ 'calculated-fields.entity-aggregation.check-interval' | translate }}" labelText="{{ 'calculated-fields.entity-aggregation.check-interval' | translate }}"
hintText="{{ 'calculated-fields.entity-aggregation.check-interval-hint' | translate }}" hintText="{{ 'calculated-fields.entity-aggregation.check-interval-hint' | translate }}"
minErrorText="{{ 'calculated-fields.entity-aggregation.check-interval-min' | translate }}"
maxErrorText="{{ 'calculated-fields.entity-aggregation.check-interval-max' | translate }}" maxErrorText="{{ 'calculated-fields.entity-aggregation.check-interval-max' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.check-interval-required' | translate }}" requiredText="{{ 'calculated-fields.entity-aggregation.check-interval-required' | translate }}"
formControlName="checkInterval"> formControlName="checkInterval">

34
ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts

@ -41,7 +41,7 @@ import { HOUR, MINUTE, SECOND } from '@shared/models/time/time.models';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
interface CalculatedFieldEntityAggregationConfigurationValue extends CalculatedFieldEntityAggregationConfiguration { interface CalculatedFieldEntityAggregationConfigurationValue extends CalculatedFieldEntityAggregationConfiguration {
interval: AggInterval & {allowOffsetMillis?: boolean}; interval: AggInterval & {allowOffsetSec?: boolean};
allowWatermark: boolean; allowWatermark: boolean;
} }
@ -79,14 +79,14 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
interval: this.fb.group({ interval: this.fb.group({
type: [AggIntervalType.HOUR], type: [AggIntervalType.HOUR],
tz: ['', Validators.required], tz: ['', Validators.required],
multiplier: [HOUR/SECOND, Validators.required], durationSec: [HOUR/SECOND, Validators.required],
allowOffsetMillis: [false], allowOffsetSec: [false],
offsetMillis: [MINUTE/SECOND, Validators.required], offsetSec: [MINUTE/SECOND, Validators.required],
}), }),
allowWatermark: [false], allowWatermark: [false],
watermark: this.fb.group({ watermark: this.fb.group({
duration: [6 * MINUTE / SECOND, Validators.required], duration: [HOUR/SECOND, Validators.required],
checkInterval: [MINUTE / SECOND, Validators.required], checkInterval: [10 * MINUTE / SECOND, Validators.required],
}), }),
output: this.fb.control<CalculatedFieldOutput>({ output: this.fb.control<CalculatedFieldOutput>({
type: OutputType.Timeseries, type: OutputType.Timeseries,
@ -111,7 +111,7 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
this.checkAggIntervalType(type); this.checkAggIntervalType(type);
}); });
this.entityAggregationConfiguration.get('interval.allowOffsetMillis').valueChanges.pipe( this.entityAggregationConfiguration.get('interval.allowOffsetSec').valueChanges.pipe(
takeUntilDestroyed() takeUntilDestroyed()
).subscribe((allow: boolean) => { ).subscribe((allow: boolean) => {
this.checkIntervalDuration(allow); this.checkIntervalDuration(allow);
@ -138,11 +138,11 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
const data: CalculatedFieldEntityAggregationConfigurationValue = { const data: CalculatedFieldEntityAggregationConfigurationValue = {
...value, ...value,
allowWatermark: isDefinedAndNotNull(value.watermark), allowWatermark: isDefinedAndNotNull(value.watermark),
interval: {...value.interval, allowOffsetMillis: isDefinedAndNotNull(value?.interval?.offsetMillis)} interval: {...value.interval, allowOffsetSec: isDefinedAndNotNull(value?.interval?.offsetSec)}
} }
this.entityAggregationConfiguration.patchValue(data, {emitEvent: false}); this.entityAggregationConfiguration.patchValue(data, {emitEvent: false});
this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value);
this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetMillis').value); this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value);
this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value);
setTimeout(() => { setTimeout(() => {
this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true});
@ -161,17 +161,17 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
} else { } else {
this.entityAggregationConfiguration.enable({emitEvent: false}); this.entityAggregationConfiguration.enable({emitEvent: false});
this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value);
this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetMillis').value); this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value);
this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value);
} }
} }
private updatedModel(value: CalculatedFieldEntityAggregationConfigurationValue): void { private updatedModel(value: CalculatedFieldEntityAggregationConfigurationValue): void {
value.type = CalculatedFieldType.ENTITY_AGGREGATION; value.type = CalculatedFieldType.ENTITY_AGGREGATION;
if (!value.interval.allowOffsetMillis) { if (!value.interval.allowOffsetSec) {
delete value.interval.offsetMillis; delete value.interval.offsetSec;
} }
delete value.interval.offsetMillis; delete value.interval.offsetSec;
if (!value.allowWatermark) { if (!value.allowWatermark) {
delete value.watermark; delete value.watermark;
} }
@ -181,17 +181,17 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
private checkAggIntervalType(type: AggIntervalType) { private checkAggIntervalType(type: AggIntervalType) {
if (type === AggIntervalType.CUSTOM) { if (type === AggIntervalType.CUSTOM) {
this.entityAggregationConfiguration.get('interval.multiplier').enable({emitEvent: false}); this.entityAggregationConfiguration.get('interval.durationSec').enable({emitEvent: false});
} else { } else {
this.entityAggregationConfiguration.get('interval.multiplier').disable({emitEvent: false}); this.entityAggregationConfiguration.get('interval.durationSec').disable({emitEvent: false});
} }
} }
private checkIntervalDuration(allow: boolean) { private checkIntervalDuration(allow: boolean) {
if (allow) { if (allow) {
this.entityAggregationConfiguration.get('interval.offsetMillis').enable({emitEvent: false}); this.entityAggregationConfiguration.get('interval.offsetSec').enable({emitEvent: false});
} else { } else {
this.entityAggregationConfiguration.get('interval.offsetMillis').disable({emitEvent: false}); this.entityAggregationConfiguration.get('interval.offsetSec').disable({emitEvent: false});
} }
} }

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

@ -160,6 +160,14 @@
</tb-js-func> </tb-js-func>
} }
</ng-container> </ng-container>
@if (simpleMode) {
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'calculated-fields.default-value' | translate }}</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput autocomplete="off" type="number" name="value" formControlName="defaultValue" placeholder="{{ 'action.set' | translate }}"/>
</mat-form-field>
</div>
}
</div> </div>
<div class="tb-config-panel-buttons"> <div class="tb-config-panel-buttons">
<button mat-button <button mat-button

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

@ -16,14 +16,17 @@
import { Component, Input, OnInit, output } from '@angular/core'; import { Component, Input, OnInit, output } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, FormControl, ValidatorFn, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { charsWithNumRegex } from '@shared/models/regex.constants'; import { charsWithNumRegex } from '@shared/models/regex.constants';
import { import {
AggFunction, AggFunction,
AggFunctionTranslations, AggFunctionTranslations,
AggInputType, AggInputType,
AggInputTypeTranslations, AggInputTypeTranslations,
CalculatedFieldAggMetricValue CalculatedFieldAggMetricValue,
FORBIDDEN_NAMES,
forbiddenNamesValidator,
uniqueNameValidator
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EntityFilter } from '@shared/models/query/query.models'; import { EntityFilter } from '@shared/models/query/query.models';
@ -55,7 +58,7 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
functionArgs: Array<string> functionArgs: Array<string>
metricForm = this.fb.group({ metricForm = this.fb.group({
name: ['', [Validators.required, this.uniqNameRequired(), this.forbiddenNameValidator(), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]], name: ['', [Validators.required, forbiddenNamesValidator(FORBIDDEN_NAMES), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]],
function: [AggFunction.AVG], function: [AggFunction.AVG],
allowFilter: [false], allowFilter: [false],
filter: ['', Validators.required], filter: ['', Validators.required],
@ -63,7 +66,8 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
type: [AggInputType.key], type: [AggInputType.key],
key: ['', Validators.required], key: ['', Validators.required],
function: ['', Validators.required], function: ['', Validators.required],
}) }),
defaultValue: [null]
}); });
entityFilter: EntityFilter; entityFilter: EntityFilter;
@ -84,6 +88,8 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.updatedForm();
const data: CalculatedFieldAggMetricValuePanel = { const data: CalculatedFieldAggMetricValuePanel = {
...this.metric, ...this.metric,
allowFilter: !!this.metric.filter, allowFilter: !!this.metric.filter,
@ -110,6 +116,15 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
this.popover.hide(); this.popover.hide();
} }
private updatedForm(): void {
this.metricForm.get('name').addValidators(uniqueNameValidator(this.usedNames));
this.metricForm.get('name').updateValueAndValidity({emitEvent: false});
if (!this.simpleMode) {
this.metricForm.removeControl('defaultValue', {emitEvent: false});
}
}
private observeFilterAllowChange(): void { private observeFilterAllowChange(): void {
this.metricForm.get('allowFilter').valueChanges this.metricForm.get('allowFilter').valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
@ -148,21 +163,4 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
this.metricForm.get('input.key').markAsTouched(); this.metricForm.get('input.key').markAsTouched();
} }
} }
private uniqNameRequired(): ValidatorFn {
return (control: FormControl) => {
const newName = control.value.trim().toLowerCase();
const isDuplicate = this.usedNames?.some(name => name.toLowerCase() === newName);
return isDuplicate ? { duplicateName: true } : null;
};
}
private forbiddenNameValidator(): ValidatorFn {
return (control: FormControl) => {
const trimmedValue = control.value.trim().toLowerCase();
const forbiddenNames = ['ctx', 'e', 'pi'];
return forbiddenNames.includes(trimmedValue) ? { forbiddenName: true } : null;
};
}
} }

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.module.ts

@ -24,7 +24,6 @@ import {
CalculatedFieldMetricsPanelComponent CalculatedFieldMetricsPanelComponent
} from '@home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component'; } from '@home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,

5
ui-ngx/src/app/shared/models/calculated-field.models.ts

@ -314,14 +314,15 @@ export const AggIntervalTypeTranslations = new Map<AggIntervalType, string>(
export interface AggInterval { export interface AggInterval {
type: AggIntervalType; type: AggIntervalType;
tz: string; tz: string;
offsetMillis?: number offsetSec?: number
multiplier?: number durationSec?: number
} }
export interface CalculatedFieldAggMetric { export interface CalculatedFieldAggMetric {
function: AggFunction; function: AggFunction;
filter?: string; filter?: string;
input: AggKeyInput | AggFunctionInput; input: AggKeyInput | AggFunctionInput;
defaultValue?: number;
} }
export interface CalculatedFieldAggMetricValue extends CalculatedFieldAggMetric { export interface CalculatedFieldAggMetricValue extends CalculatedFieldAggMetric {

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

@ -1195,6 +1195,7 @@
"aggregate-interval-type": "Aggregate interval type", "aggregate-interval-type": "Aggregate interval type",
"aggregate-interval-value": "Aggregate interval value", "aggregate-interval-value": "Aggregate interval value",
"aggregate-interval-value-required": "Aggregate interval value is required", "aggregate-interval-value-required": "Aggregate interval value is required",
"aggregate-interval-value-min": "Aggregate interval value should be at least 1 minute",
"aggregate-period": { "aggregate-period": {
"hour": "Hour", "hour": "Hour",
"day": "Day", "day": "Day",
@ -1213,15 +1214,18 @@
"apply-offset-hint": "Defines how much to shift the start of each aggregation period (e.g., +10 minutes - 00:10, 01:10).", "apply-offset-hint": "Defines how much to shift the start of each aggregation period (e.g., +10 minutes - 00:10, 01:10).",
"offset-value": "Offset value", "offset-value": "Offset value",
"offset-value-required": "Offset value is required", "offset-value-required": "Offset value is required",
"offset-value-min": "Offset value must be a positive integer",
"wait-delay": "Wait for delayed telemetry", "wait-delay": "Wait for delayed telemetry",
"wait-delay-hint": "Waits for delayed telemetry after the interval ends.", "wait-delay-hint": "Waits for delayed telemetry after the interval ends",
"wait-duration": "Duration", "duration": "Duration",
"wait-duration-required": "Duration is required", "duration-required": "Duration is required",
"wait-duration-hint": "Defines how long to wait for delayed data after the interval ends.", "duration-min": "Duration should be at least 1 minute",
"duration-hint": "Defines how long to wait for delayed data after the interval ends",
"check-interval": "Check for telemetry every", "check-interval": "Check for telemetry every",
"check-interval-required": "Check for telemetry every is required", "check-interval-required": "Check for telemetry every is required",
"check-interval-min": "Check interval should be at least 30 seconds",
"check-interval-max": "Check interval need be less than the duration", "check-interval-max": "Check interval need be less than the duration",
"check-interval-hint": "Defines how often to recheck for late telemetry during the watermark period." "check-interval-hint": "Defines how often to recheck for late telemetry during the watermark period"
}, },
"hint": { "hint": {
"arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.", "arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.",

Loading…
Cancel
Save