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>
@if (entityAggregationConfiguration.get('interval.type').value === AggIntervalType.CUSTOM) {
<tb-time-unit-input required
[minTime]="0"
[minTime]="60"
sameWidthInputs
appearance="outline"
subscriptSizing="dynamic"
labelText="{{ 'calculated-fields.aggregate-interval-value' | translate }}"
minErrorText="{{ 'calculated-fields.aggregate-interval-value-min' | translate }}"
requiredText="{{ 'calculated-fields.aggregate-interval-value-required' | translate }}"
formControlName="multiplier">
formControlName="durationSec">
</tb-time-unit-input>
}
<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 }}">
{{ 'calculated-fields.entity-aggregation.apply-offset' | translate }}
</div>
</mat-slide-toggle>
@if (entityAggregationConfiguration.get('interval.allowOffsetMillis').value) {
@if (entityAggregationConfiguration.get('interval.allowOffsetSec').value) {
<tb-time-unit-input required
[minTime]="1"
[minTime]="0"
sameWidthInputs
appearance="outline"
subscriptSizing="dynamic"
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 }}"
formControlName="offsetMillis">
formControlName="offsetSec">
</tb-time-unit-input>
}
</div>
@ -99,21 +101,23 @@
@if (entityAggregationConfiguration.get('allowWatermark').value) {
<ng-container formGroupName="watermark">
<tb-time-unit-input required
[minTime]="1"
[minTime]="60"
sameWidthInputs
appearance="outline"
labelText="{{ 'calculated-fields.entity-aggregation.wait-duration' | translate }}"
hintText="{{ 'calculated-fields.entity-aggregation.wait-duration-hint' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.wait-duration-required' | translate }}"
labelText="{{ 'calculated-fields.entity-aggregation.duration' | translate }}"
minErrorText="{{ 'calculated-fields.entity-aggregation.duration-min' | translate }}"
hintText="{{ 'calculated-fields.entity-aggregation.duration-hint' | translate }}"
requiredText="{{ 'calculated-fields.entity-aggregation.duration-required' | translate }}"
formControlName="duration">
</tb-time-unit-input>
<tb-time-unit-input required
[minTime]="0"
[minTime]="30"
[maxTime]="entityAggregationConfiguration.get('watermark.duration').value"
sameWidthInputs
appearance="outline"
labelText="{{ 'calculated-fields.entity-aggregation.check-interval' | 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 }}"
requiredText="{{ 'calculated-fields.entity-aggregation.check-interval-required' | translate }}"
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';
interface CalculatedFieldEntityAggregationConfigurationValue extends CalculatedFieldEntityAggregationConfiguration {
interval: AggInterval & {allowOffsetMillis?: boolean};
interval: AggInterval & {allowOffsetSec?: boolean};
allowWatermark: boolean;
}
@ -79,14 +79,14 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
interval: this.fb.group({
type: [AggIntervalType.HOUR],
tz: ['', Validators.required],
multiplier: [HOUR/SECOND, Validators.required],
allowOffsetMillis: [false],
offsetMillis: [MINUTE/SECOND, Validators.required],
durationSec: [HOUR/SECOND, Validators.required],
allowOffsetSec: [false],
offsetSec: [MINUTE/SECOND, Validators.required],
}),
allowWatermark: [false],
watermark: this.fb.group({
duration: [6 * MINUTE / SECOND, Validators.required],
checkInterval: [MINUTE / SECOND, Validators.required],
duration: [HOUR/SECOND, Validators.required],
checkInterval: [10 * MINUTE / SECOND, Validators.required],
}),
output: this.fb.control<CalculatedFieldOutput>({
type: OutputType.Timeseries,
@ -111,7 +111,7 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
this.checkAggIntervalType(type);
});
this.entityAggregationConfiguration.get('interval.allowOffsetMillis').valueChanges.pipe(
this.entityAggregationConfiguration.get('interval.allowOffsetSec').valueChanges.pipe(
takeUntilDestroyed()
).subscribe((allow: boolean) => {
this.checkIntervalDuration(allow);
@ -138,11 +138,11 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
const data: CalculatedFieldEntityAggregationConfigurationValue = {
...value,
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.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);
setTimeout(() => {
this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true});
@ -161,17 +161,17 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
} else {
this.entityAggregationConfiguration.enable({emitEvent: false});
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);
}
}
private updatedModel(value: CalculatedFieldEntityAggregationConfigurationValue): void {
value.type = CalculatedFieldType.ENTITY_AGGREGATION;
if (!value.interval.allowOffsetMillis) {
delete value.interval.offsetMillis;
if (!value.interval.allowOffsetSec) {
delete value.interval.offsetSec;
}
delete value.interval.offsetMillis;
delete value.interval.offsetSec;
if (!value.allowWatermark) {
delete value.watermark;
}
@ -181,17 +181,17 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor
private checkAggIntervalType(type: AggIntervalType) {
if (type === AggIntervalType.CUSTOM) {
this.entityAggregationConfiguration.get('interval.multiplier').enable({emitEvent: false});
this.entityAggregationConfiguration.get('interval.durationSec').enable({emitEvent: false});
} else {
this.entityAggregationConfiguration.get('interval.multiplier').disable({emitEvent: false});
this.entityAggregationConfiguration.get('interval.durationSec').disable({emitEvent: false});
}
}
private checkIntervalDuration(allow: boolean) {
if (allow) {
this.entityAggregationConfiguration.get('interval.offsetMillis').enable({emitEvent: false});
this.entityAggregationConfiguration.get('interval.offsetSec').enable({emitEvent: false});
} 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>
}
</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 class="tb-config-panel-buttons">
<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 { 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 {
AggFunction,
AggFunctionTranslations,
AggInputType,
AggInputTypeTranslations,
CalculatedFieldAggMetricValue
CalculatedFieldAggMetricValue,
FORBIDDEN_NAMES,
forbiddenNamesValidator,
uniqueNameValidator
} from '@shared/models/calculated-field.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EntityFilter } from '@shared/models/query/query.models';
@ -55,7 +58,7 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
functionArgs: Array<string>
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],
allowFilter: [false],
filter: ['', Validators.required],
@ -63,7 +66,8 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
type: [AggInputType.key],
key: ['', Validators.required],
function: ['', Validators.required],
})
}),
defaultValue: [null]
});
entityFilter: EntityFilter;
@ -84,6 +88,8 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
}
ngOnInit(): void {
this.updatedForm();
const data: CalculatedFieldAggMetricValuePanel = {
...this.metric,
allowFilter: !!this.metric.filter,
@ -110,6 +116,15 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
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 {
this.metricForm.get('allowFilter').valueChanges
.pipe(takeUntilDestroyed())
@ -148,21 +163,4 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit {
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
} from '@home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component';
@NgModule({
imports: [
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 {
type: AggIntervalType;
tz: string;
offsetMillis?: number
multiplier?: number
offsetSec?: number
durationSec?: number
}
export interface CalculatedFieldAggMetric {
function: AggFunction;
filter?: string;
input: AggKeyInput | AggFunctionInput;
defaultValue?: number;
}
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-value": "Aggregate interval value",
"aggregate-interval-value-required": "Aggregate interval value is required",
"aggregate-interval-value-min": "Aggregate interval value should be at least 1 minute",
"aggregate-period": {
"hour": "Hour",
"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).",
"offset-value": "Offset value",
"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-hint": "Waits for delayed telemetry after the interval ends.",
"wait-duration": "Duration",
"wait-duration-required": "Duration is required",
"wait-duration-hint": "Defines how long to wait for delayed data after the interval ends.",
"wait-delay-hint": "Waits for delayed telemetry after the interval ends",
"duration": "Duration",
"duration-required": "Duration is required",
"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-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-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": {
"arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.",

Loading…
Cancel
Save