diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 6e4d324b5b..582a407841 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -32,6 +32,7 @@ export interface SysParamsState { maxDataPointsPerRollingArg: number; maxArgumentsPerCF: number; minAllowedDeduplicationIntervalInSecForCF: number; + minAggregationIntervalInSecForCF: number; minAllowedScheduledUpdateIntervalInSecForCF: number; maxRelationLevelPerCfArgument: number; ruleChainDebugPerTenantLimitsConfiguration?: string; diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 777cf5308e..8f8258d7b9 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -34,6 +34,7 @@ const emptyUserAuthState: AuthPayload = { maxResourceSize: 0, maxArgumentsPerCF: 0, minAllowedDeduplicationIntervalInSecForCF: 0, + minAggregationIntervalInSecForCF: 0, minAllowedScheduledUpdateIntervalInSecForCF: 0, maxRelationLevelPerCfArgument: 0, maxDataPointsPerRollingArg: 0, diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html index 5114a4cdc1..1b45b7f245 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html @@ -62,13 +62,16 @@ @if (entityAggregationConfiguration.get('interval.type').value === AggIntervalType.CUSTOM) { } @@ -84,6 +87,7 @@ sameWidthInputs appearance="outline" subscriptSizing="dynamic" + containerClass="flex gap-3" 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 }}" @@ -104,24 +108,13 @@ [minTime]="60" sameWidthInputs appearance="outline" + containerClass="flex gap-3" 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"> - - } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts index 5fd958518b..44aba5b6fa 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts @@ -37,8 +37,11 @@ import { } from '@shared/models/calculated-field.models'; import { map } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { HOUR, MINUTE, SECOND } from '@shared/models/time/time.models'; +import { DAY, HOUR, MINUTE, SECOND } from '@shared/models/time/time.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; interface CalculatedFieldEntityAggregationConfigurationValue extends CalculatedFieldEntityAggregationConfiguration { interval: AggInterval & {allowOffsetSec?: boolean}; @@ -72,6 +75,8 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor @Input({required: true}) entityName: string; + readonly minAggregationIntervalInSecForCF = getCurrentAuthState(this.store).minAggregationIntervalInSecForCF; + readonly DayInSec = DAY / SECOND; entityAggregationConfiguration = this.fb.group({ arguments: this.fb.control({}, notEmptyObjectValidator()), @@ -79,14 +84,13 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor interval: this.fb.group({ type: [AggIntervalType.HOUR], tz: ['', Validators.required], - durationSec: [HOUR/SECOND, Validators.required], + durationSec: [this.minAggregationIntervalInSecForCF, Validators.required], allowOffsetSec: [false], offsetSec: [MINUTE/SECOND, Validators.required], }), allowWatermark: [false], watermark: this.fb.group({ duration: [HOUR/SECOND, Validators.required], - checkInterval: [10 * MINUTE / SECOND, Validators.required], }), output: this.fb.control({ type: OutputType.Timeseries, @@ -103,7 +107,8 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor private propagateChange: (config: CalculatedFieldEntityAggregationConfiguration) => void = () => { }; - constructor(private fb: FormBuilder) { + constructor(private fb: FormBuilder, + private store: Store) { this.entityAggregationConfiguration.get('interval.type').valueChanges.pipe( takeUntilDestroyed() diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 8cfa8fd0ed..2c2f7b5973 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -342,17 +342,17 @@
- tenant-profile.relation-search-entity-limit - tenant-profile.min-allowed-aggregation-interval + - - {{ 'tenant-profile.relation-search-entity-limit-required' | translate}} + + {{ 'tenant-profile.min-allowed-aggregation-interval-required' | translate}} - - {{ 'tenant-profile.relation-search-entity-limit-range' | translate}} + + {{ 'tenant-profile.min-allowed-aggregation-interval-range' | translate}} - tenant-profile.relation-search-entity-limit-hint + tenant-profile.min-allowed-deduplication-interval @@ -368,6 +368,22 @@
+
+ + tenant-profile.relation-search-entity-limit + + + {{ 'tenant-profile.relation-search-entity-limit-required' | translate}} + + + {{ 'tenant-profile.relation-search-entity-limit-range' | translate}} + + tenant-profile.relation-search-entity-limit-hint + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 0000d01995..16997d18a7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -117,6 +117,7 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxArgumentsPerCF: [0, [Validators.required, Validators.min(0)]], maxRelationLevelPerCfArgument: [1, [Validators.required, Validators.min(1)]], minAllowedDeduplicationIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], + minAggregationIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], maxRelatedEntitiesToReturnPerCfArgument: [1, [Validators.required, Validators.min(1)]], minAllowedScheduledUpdateIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], maxDataPointsPerRollingArg: [0, [Validators.required, Validators.min(0)]], diff --git a/ui-ngx/src/app/shared/components/time-unit-input.component.html b/ui-ngx/src/app/shared/components/time-unit-input.component.html index f81fc68777..c49ce517b3 100644 --- a/ui-ngx/src/app/shared/components/time-unit-input.component.html +++ b/ui-ngx/src/app/shared/components/time-unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/shared/components/time-unit-input.component.ts b/ui-ngx/src/app/shared/components/time-unit-input.component.ts index bcfdabd574..65acaa88f1 100644 --- a/ui-ngx/src/app/shared/components/time-unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/time-unit-input.component.ts @@ -23,6 +23,7 @@ import { NG_VALUE_ACCESSOR, ValidationErrors, Validator, + ValidatorFn, Validators } from '@angular/forms'; import { TimeUnit, timeUnitTranslations } from '@home/components/rule-node/rule-node-config.models'; @@ -79,6 +80,13 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, @Input() maxErrorText: string; + @Input() + @coerceNumber() + stepMultipleOf: number; + + @Input() + stepMultipleOfErrorText: string; + @Input() subscriptSizing: SubscriptSizing = 'fixed'; @@ -93,6 +101,9 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, @coerceBoolean() sameWidthInputs: boolean = false; + @Input() + containerClass: string | string[] | Record = "flex gap-4"; + timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[]; timeUnitTranslations = timeUnitTranslations; @@ -128,7 +139,7 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.DAYS); } } - if(this.required || this.maxTime) { + if (this.required || this.maxTime || isDefinedAndNotNull(this.minTime) || this.stepMultipleOf) { const timeControl = this.timeInputForm.get('time'); const validators = [Validators.pattern(/^\d*$/)]; if (this.required) { @@ -145,6 +156,10 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, ); } + if (isDefinedAndNotNull(this.stepMultipleOf) && this.stepMultipleOf > 0) { + validators.push(this.createStepMultipleOfValidator()); + } + timeControl.setValidators(validators); timeControl.updateValueAndValidity({ emitEvent: false }); } @@ -170,6 +185,8 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, return this.minErrorText; } else if (this.timeInputForm.get('time').hasError('max') && this.maxErrorText) { return this.maxErrorText; + } else if (this.timeInputForm.get('time').hasError('stepMultipleOf') && this.stepMultipleOfErrorText) { + return this.stepMultipleOfErrorText; } } @@ -232,4 +249,34 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, } } + private createStepMultipleOfValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const time = control.value; + if (!isDefinedAndNotNull(time) || !isNumeric(time)) { + return null; + } + const numericTime = Number(time); + if (numericTime === 0) { + return null; + } + + const timeUnit = control.parent?.get('timeUnit')?.value as TimeUnit; + if (!timeUnit) { + return null; + } + + const unitInSec = this.timeIntervalsInSec.get(timeUnit); + const totalTimeInSec = numericTime * unitInSec; + const multipleOfVal = this.stepMultipleOf; + + let isValid: boolean; + if (totalTimeInSec < multipleOfVal) { + isValid = (multipleOfVal % totalTimeInSec === 0); + } else { + isValid = (totalTimeInSec % multipleOfVal === 0); + } + return isValid ? null : { stepMultipleOf: true }; + }; + } + } diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index 321c176cde..e0df001e3f 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -135,8 +135,7 @@ export interface CalculatedFieldEntityAggregationConfiguration { } export interface WatermarkConfig { - duration?: number; - checkInterval?: number; + duration: number; } interface BasePropagationConfiguration { diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index ae7a0ae8b8..ac7b77b9cc 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -108,6 +108,7 @@ export interface DefaultTenantProfileConfiguration { maxArgumentsPerCF: number; maxRelationLevelPerCfArgument: number; minAllowedDeduplicationIntervalInSecForCF: number; + minAggregationIntervalInSecForCF: number; maxRelatedEntitiesToReturnPerCfArgument: number; minAllowedScheduledUpdateIntervalInSecForCF: number; maxDataPointsPerRollingArg: number; @@ -176,6 +177,7 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxDataPointsPerRollingArg: 1000, maxRelationLevelPerCfArgument: 10, minAllowedDeduplicationIntervalInSecForCF: 3600, + minAggregationIntervalInSecForCF: 60, maxRelatedEntitiesToReturnPerCfArgument: 100, minAllowedScheduledUpdateIntervalInSecForCF: 0, maxStateSizeInKBytes: 32, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 6e00b883b4..c05686b648 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1195,7 +1195,8 @@ "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-interval-value-min": "Aggregate interval value should be at least { sec, plural, =0 {0 second} =1 {1 second} other {# seconds} }", + "aggregate-interval-value-step-multiple-of": "Aggregate interval value must be a divisor or multiple of 1 day", "aggregate-period": { "hour": "Hour", "day": "Day", @@ -1220,12 +1221,7 @@ "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" + "duration-hint": "How long to wait for delayed data after the interval ends" }, "hint": { "arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.", @@ -6001,6 +5997,9 @@ "min-allowed-deduplication-interval": "Min allowed deduplication interval (seconds)", "min-allowed-deduplication-interval-range": "Min allowed deduplication interval value can't be negative", "min-allowed-deduplication-interval-required": "Min allowed deduplication interval is required", + "min-allowed-aggregation-interval": "Min allowed aggregation interval (seconds)", + "min-allowed-aggregation-interval-range": "Min allowed aggregation interval value can't be negative", + "min-allowed-aggregation-interval-required": "Min allowed aggregation interval is required", "min-allowed-scheduled-update-interval-required": "Min allowed update interval min number is required", "max-state-size": "State maximum size in KB", "max-state-size-range": "State maximum size in KB can't be negative",