Browse Source

Merge pull request #14440 from ArtemDzhereleiko/AD/alarm-rule/missing-for

Add missing for option for filters alarm rule
pull/14463/head
Vladyslav Prykhodko 10 months ago
committed by GitHub
parent
commit
f836bf7030
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts
  2. 6
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html
  3. 32
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts
  4. 11
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts
  5. 2
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts
  6. 2
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html
  7. 26
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts
  8. 77
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component.html
  9. 159
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component.ts
  10. 9
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html
  11. 26
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts
  12. 17
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html
  13. 86
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts
  14. 34
      ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts
  15. 94
      ui-ngx/src/app/shared/models/alarm-rule.models.ts
  16. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts

@ -50,6 +50,9 @@ import { AlarmRuleFilterDialogComponent } from "@home/components/alarm-rules/fil
import { AlarmRuleDetailsDialogComponent } from "@home/components/alarm-rules/alarm-rule-details-dialog.component";
import { AlarmRuleFilterConfigComponent } from "@home/components/alarm-rules/alarm-rule-filter-config.component";
import { AlarmRuleTableHeaderComponent } from "@home/components/alarm-rules/alarm-rule-table-header.component";
import {
AlarmRuleFilterPredicateNoDataValueComponent
} from "@home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component";
@NgModule({
declarations: [
@ -69,7 +72,8 @@ import { AlarmRuleTableHeaderComponent } from "@home/components/alarm-rules/alar
AlarmRuleComplexFilterPredicateDialogComponent,
AlarmRuleDetailsDialogComponent,
AlarmRuleFilterConfigComponent,
AlarmRuleTableHeaderComponent
AlarmRuleTableHeaderComponent,
AlarmRuleFilterPredicateNoDataValueComponent
],
imports: [
CommonModule,

6
ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html

@ -78,10 +78,14 @@
<mat-form-field class="mat-block" hideRequiredMarker appearance="outline" subscriptSizing="dynamic">
<mat-label translate>alarm-rule.condition-type</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let alarmConditionType of alarmConditionTypes" [value]="alarmConditionType">
<mat-option *ngFor="let alarmConditionType of alarmConditionTypes" [value]="alarmConditionType"
[disabled]="isNoData ? alarmConditionType === AlarmConditionType.REPEATING || alarmConditionType === AlarmConditionType.DURATION : false">
{{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }}
</mat-option>
</mat-select>
@if (isNoData) {
<mat-hint translate>alarm-rule.condition-type-hint</mat-hint>
}
</mat-form-field>
@if (conditionFormGroup.get('type').value == AlarmConditionType.DURATION) {
<div class="tb-form-panel stroked no-padding-bottom">

32
ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts

@ -28,7 +28,8 @@ import {
AlarmRuleCondition,
AlarmRuleConditionType,
AlarmRuleConditionTypeTranslationMap,
AlarmRuleExpressionType
AlarmRuleExpressionType,
AlarmRuleFilter
} from "@shared/models/alarm-rule.models";
import {
CalculatedFieldArgument,
@ -102,6 +103,8 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent<CfAlarm
arguments = this.data.arguments;
argumentsList: Array<string>;
isNoData: boolean = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: CfAlarmRuleConditionDialogData,
@ -136,12 +139,20 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent<CfAlarm
this.durationDynamicModeControl.patchValue(!!this.condition?.value?.dynamicValueArgument, {emitEvent: false});
this.repeatingDynamicModeControl.patchValue(!!this.condition?.count?.dynamicValueArgument, {emitEvent: false});
this.checkIsNoData(this.condition?.expression?.filters);
this.conditionFormGroup.get('type').valueChanges.pipe(
takeUntilDestroyed()
).subscribe((type) => {
this.updateValidators(type, true);
});
this.conditionFormGroup.get('expression.filters').valueChanges.pipe(
takeUntilDestroyed()
).subscribe((filters) => {
this.checkIsNoData(filters);
});
this.conditionFormGroup.get('expression.type').valueChanges.pipe(
takeUntilDestroyed()
).subscribe((type) => {
@ -185,6 +196,25 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent<CfAlarm
}
}
private checkIsNoData(filters: Array<AlarmRuleFilter>) {
this.isNoData = this.hasNoData(filters);
if (this.isNoData && this.conditionFormGroup.get('type').value !== AlarmRuleConditionType.SIMPLE) {
this.conditionFormGroup.get('type').patchValue(AlarmRuleConditionType.SIMPLE);
}
}
private hasNoData(data: Array<AlarmRuleFilter>) {
const search = (filter) => {
if (!filter) return false;
if (Array.isArray(filter)) return filter.some(search);
if (typeof filter !== 'object') return false;
if (filter.type === 'NO_DATA') return true;
if (filter.predicates?.length) return filter.predicates.some(search);
return false;
};
return search(data);
};
private updateValidators(type: AlarmRuleConditionType, emitEvent = false) {
switch (type) {
case AlarmRuleConditionType.DURATION:

11
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts

@ -24,10 +24,13 @@ import { DialogComponent } from '@app/shared/components/dialog.component';
import {
ComplexOperation,
complexOperationTranslationMap,
EntityKeyValueType,
FilterPredicateType
EntityKeyValueType
} from '@shared/models/query/query.models';
import { AlarmRuleFilterPredicate, ComplexAlarmRuleFilterPredicate } from "@shared/models/alarm-rule.models";
import {
AlarmRuleFilterPredicate,
AlarmRuleFilterPredicateType,
ComplexAlarmRuleFilterPredicate
} from "@shared/models/alarm-rule.models";
import { CalculatedFieldArgument } from "@shared/models/calculated-field.models";
export interface AlarmRuleComplexFilterPredicateDialogData {
@ -81,7 +84,7 @@ export class AlarmRuleComplexFilterPredicateDialogComponent extends
save(): void {
const predicate = this.complexFilterFormGroup.value as ComplexAlarmRuleFilterPredicate;
predicate.type = FilterPredicateType.COMPLEX;
predicate.type = AlarmRuleFilterPredicateType.COMPLEX;
this.dialogRef.close(predicate);
}
}

2
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts

@ -78,7 +78,7 @@ export class AlarmRuleFilterDialogComponent extends DialogComponent<AlarmRuleFil
this.filterFormGroup = this.fb.group(
{
argument: [this.data.filter.argument, [Validators.required]],
argument: [this.argumentsList.includes(this.data.filter.argument) ? this.data.filter.argument : '' , [Validators.required]],
valueType: [this.data.filter.valueType ?? EntityKeyValueType.STRING, [Validators.required]],
predicates: [this.data.filter.predicates, [Validators.required]],
operation: [this.data.filter.operation ?? ComplexOperation.AND]

2
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html

@ -40,7 +40,7 @@
<div class="flex max-w-92% flex-full flex-col">
<div class="flex flex-row items-center justify-start">
<div class="flex-1">{{ filterControl.value?.argument }}</div>
<div class="flex-1">{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.predicates[0]?.type) | translate }}</div>
<div class="flex-1">{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.valueType) | translate }}</div>
<button mat-icon-button color="primary"
type="button"
(click)="editFilter(index)"

26
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts

@ -28,14 +28,10 @@ import {
} from '@angular/forms';
import { Observable, of } from 'rxjs';
import {
BooleanOperation,
ComplexOperation,
complexOperationTranslationMap,
EntityKeyValueType,
entityKeyValueTypeToFilterPredicateType,
FilterPredicateType,
NumericOperation,
StringOperation
entityKeyValueTypeToFilterPredicateType
} from '@shared/models/query/query.models';
import { MatDialog } from '@angular/material/dialog';
import { map } from 'rxjs/operators';
@ -44,8 +40,12 @@ import {
AlarmRuleComplexFilterPredicateDialogData
} from "@home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component";
import {
AlarmRuleBooleanOperation,
AlarmRuleFilterPredicate,
AlarmRuleFilterPredicateType,
AlarmRuleNumericOperation,
AlarmRulePredicateInfo,
AlarmRuleStringOperation,
ComplexAlarmRuleFilterPredicate
} from "@shared/models/alarm-rule.models";
import { CalculatedFieldArgument } from "@shared/models/calculated-field.models";
@ -157,29 +157,29 @@ export class AlarmRuleFilterPredicateListComponent implements ControlValueAccess
private createDefaultFilterPredicate(valueType: EntityKeyValueType, complex: boolean): AlarmRuleFilterPredicate {
const predicate = {
type: complex ? FilterPredicateType.COMPLEX : entityKeyValueTypeToFilterPredicateType(valueType)
type: complex ? AlarmRuleFilterPredicateType.COMPLEX : entityKeyValueTypeToFilterPredicateType(valueType)
} as AlarmRuleFilterPredicate;
switch (predicate.type) {
case FilterPredicateType.STRING:
predicate.operation = StringOperation.STARTS_WITH;
case AlarmRuleFilterPredicateType.STRING:
predicate.operation = AlarmRuleStringOperation.STARTS_WITH;
predicate.value = {
staticValue: ''
};
predicate.ignoreCase = false;
break;
case FilterPredicateType.NUMERIC:
predicate.operation = NumericOperation.EQUAL;
case AlarmRuleFilterPredicateType.NUMERIC:
predicate.operation = AlarmRuleNumericOperation.EQUAL;
predicate.value = {
staticValue: valueType === EntityKeyValueType.DATE_TIME ? Date.now() : 0
};
break;
case FilterPredicateType.BOOLEAN:
predicate.operation = BooleanOperation.EQUAL;
case AlarmRuleFilterPredicateType.BOOLEAN:
predicate.operation = AlarmRuleBooleanOperation.EQUAL;
predicate.value = {
staticValue: false
};
break;
case FilterPredicateType.COMPLEX:
case AlarmRuleFilterPredicateType.COMPLEX:
predicate.operation = ComplexOperation.AND;
predicate.predicates = [];
break;

77
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component.html

@ -0,0 +1,77 @@
<!--
Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-form-panel no-border no-gap no-padding" [formGroup]="filterPredicateValueNoDataFormGroup">
<div class="tb-form-row no-padding no-border column-xs">
<mat-form-field hideRequiredMarker class="flex-1 w-full max-w-30% xs:max-w-full" appearance="outline" subscriptSizing="dynamic">
<mat-select [formControl]="dynamicModeControl" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="false">{{'alarm-rule.static' | translate}}</mat-option>
<mat-option [value]="true">{{'alarm-rule.dynamic' | translate}}</mat-option>
</mat-select>
</mat-form-field>
<ng-container formGroupName="duration">
@if (!dynamicModeControl.value) {
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input required type="number" matInput formControlName="staticValue" min="1"
placeholder="{{'filter.value' | translate}}">
@if (filterPredicateValueNoDataFormGroup.get('duration.staticValue').touched && filterPredicateValueNoDataFormGroup.get('duration.staticValue').errors) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(filterPredicateValueNoDataFormGroup.get('duration.staticValue').hasError('required') ? 'alarm-rule.value-required' : 'alarm-rule.min-value') | translate"
class="tb-error">
warning
</mat-icon>
}
</mat-form-field>
} @else {
<mat-form-field class="flex-1 w-full" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="dynamicValueArgument" placeholder="{{ 'action.set' | translate }}">
@for (argument of argumentsList; track argument) {
<mat-option [value]="argument" [disabled]="argument === argumentInUse">{{ argument }}</mat-option>
}
</mat-select>
@if (filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').touched && filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').errors) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').hasError('required') ? 'calculated-fields.hint.argument-name-required' : 'alarm-rule.argument-in-use') | translate"
class="tb-error !block">
warning
</mat-icon>
}
@if (filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').hasError('argumentInUse')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'alarm-rule.argument-in-use' | translate"
class="tb-error !block">
warning
</mat-icon>
}
</mat-form-field>
}
</ng-container>
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="unit" required placeholder="{{'alarm-rule.time-unit' | translate}}">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitsTranslationMap.get(timeUnit) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>

159
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component.ts

@ -0,0 +1,159 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, DestroyRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CalculatedFieldArgument } from "@shared/models/calculated-field.models";
import { TimeUnit, timeUnitTranslations } from "@home/components/rule-node/rule-node-config.models";
import { AlarmRuleFilterPredicateType, NoDataAlarmRuleFilterPredicate } from "@shared/models/alarm-rule.models";
@Component({
selector: 'tb-alarm-rule-filter-predicate-no-data-value',
templateUrl: './alarm-rule-filter-predicate-no-data-value.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmRuleFilterPredicateNoDataValueComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AlarmRuleFilterPredicateNoDataValueComponent),
multi: true
}
]
})
export class AlarmRuleFilterPredicateNoDataValueComponent implements ControlValueAccessor, Validator, OnInit, OnChanges {
@Input()
arguments: Record<string, CalculatedFieldArgument>;
@Input()
valueType: AlarmRuleFilterPredicateType;
@Input()
argumentInUse: string;
valueTypeEnum = AlarmRuleFilterPredicateType;
filterPredicateValueNoDataFormGroup = this.fb.group({
type: ['NO_DATA'],
unit: [TimeUnit.MINUTES, Validators.required],
duration: this.fb.group({
staticValue: [null as null | number, [Validators.required, Validators.min(1)]],
dynamicValueArgument: ['', Validators.required]
})
});
timeUnits = [TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS];
timeUnitsTranslationMap = timeUnitTranslations;
dynamicModeControl = this.fb.control(false);
argumentsList: Array<string>;
private propagateChange= (v: any) => { };
constructor(private fb: FormBuilder,
private destroyRef: DestroyRef) {
}
ngOnInit(): void {
this.argumentsList = this.arguments ? Object.keys(this.arguments): [];
this.filterPredicateValueNoDataFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.updateModel();
});
this.dynamicModeControl.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(value => this.updateValueModeValidators(value));
}
ngOnChanges(changes: SimpleChanges) {
if (changes.argumentInUse) {
const argumentInUseChanges = changes.argumentInUse;
if (!argumentInUseChanges.firstChange && argumentInUseChanges.currentValue !== argumentInUseChanges.previousValue) {
if (this.dynamicModeControl.value) {
if (this.argumentInUse === this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').value) {
this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').setErrors({argumentInUse: true});
this.filterPredicateValueNoDataFormGroup.updateValueAndValidity();
}
}
}
}
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.filterPredicateValueNoDataFormGroup.disable({emitEvent: false});
this.dynamicModeControl.disable({emitEvent: false});
} else {
this.filterPredicateValueNoDataFormGroup.enable({emitEvent: false});
this.dynamicModeControl.enable({emitEvent: false});
this.updateValueModeValidators(this.dynamicModeControl.value);
}
}
private updateValueModeValidators(isDynamicMode: boolean): void {
if (isDynamicMode) {
this.filterPredicateValueNoDataFormGroup.get('duration.staticValue').disable({emitEvent: false});
this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').enable();
setTimeout(()=> {
if (this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').value && this.argumentInUse === this.filterPredicateValueNoDataFormGroup.get('dynamicValueArgument').value) {
this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').setErrors({argumentInUse: true});
this.filterPredicateValueNoDataFormGroup.updateValueAndValidity();
}
}, 0);
} else {
this.filterPredicateValueNoDataFormGroup.get('duration.dynamicValueArgument').disable({emitEvent: false});
this.filterPredicateValueNoDataFormGroup.get('duration.staticValue').enable();
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
validate(): ValidationErrors | null {
return this.filterPredicateValueNoDataFormGroup.valid ? null : {
filterPredicateValue: {valid: false}
};
}
writeValue(predicateValue: NoDataAlarmRuleFilterPredicate): void {
this.filterPredicateValueNoDataFormGroup.patchValue(predicateValue, {emitEvent: false});
this.dynamicModeControl.patchValue(!!predicateValue?.duration?.dynamicValueArgument?.length, {emitEvent: false});
}
private updateModel() {
this.propagateChange(this.filterPredicateValueNoDataFormGroup.value);
}
}

9
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html

@ -67,6 +67,15 @@
warning
</mat-icon>
}
@if (filterPredicateValueFormGroup.get('dynamicValueArgument').hasError('argumentInUse')) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'alarm-rule.argument-in-use' | translate"
class="tb-error !block">
warning
</mat-icon>
}
</mat-form-field>
}
</div>

26
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core';
import { Component, DestroyRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
@ -49,7 +49,7 @@ import { FormControlsFrom } from "@shared/models/tenant.model";
}
]
})
export class AlarmRuleFilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit {
export class AlarmRuleFilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit, OnChanges {
@Input()
arguments: Record<string, CalculatedFieldArgument>;
@ -110,6 +110,20 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces
).subscribe(value => this.updateValueModeValidators(value));
}
ngOnChanges(changes: SimpleChanges) {
if (changes.argumentInUse) {
const argumentInUseChanges = changes.argumentInUse;
if (!argumentInUseChanges.firstChange && argumentInUseChanges.currentValue !== argumentInUseChanges.previousValue) {
if (this.dynamicModeControl.value) {
if (this.argumentInUse === this.filterPredicateValueFormGroup.get('dynamicValueArgument').value) {
this.filterPredicateValueFormGroup.get('dynamicValueArgument').setErrors({argumentInUse: true});
this.filterPredicateValueFormGroup.updateValueAndValidity();
}
}
}
}
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.filterPredicateValueFormGroup.disable({emitEvent: false});
@ -125,6 +139,12 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces
if (isDynamicMode) {
this.filterPredicateValueFormGroup.get('staticValue').disable({emitEvent: false});
this.filterPredicateValueFormGroup.get('dynamicValueArgument').enable();
setTimeout(()=> {
if (this.filterPredicateValueFormGroup.get('dynamicValueArgument').value && this.argumentInUse === this.filterPredicateValueFormGroup.get('dynamicValueArgument').value) {
this.filterPredicateValueFormGroup.get('dynamicValueArgument').setErrors({argumentInUse: true});
this.filterPredicateValueFormGroup.updateValueAndValidity();
}
}, 0);
} else {
this.filterPredicateValueFormGroup.get('dynamicValueArgument').disable({emitEvent: false});
this.filterPredicateValueFormGroup.get('staticValue').enable();
@ -146,7 +166,7 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces
writeValue(predicateValue: AlarmRuleValue<string | number | boolean>): void {
this.filterPredicateValueFormGroup.patchValue(predicateValue, {emitEvent: false});
this.dynamicModeControl.patchValue(!!predicateValue.dynamicValueArgument?.length, {emitEvent: false});
this.dynamicModeControl.patchValue(!!predicateValue?.dynamicValueArgument?.length, {emitEvent: false});
}
private updateModel() {

17
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html

@ -27,9 +27,11 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-chip-listbox formControlName="ignoreCase" [hideSingleSelectionIndicator]="true">
<mat-chip-option color="primary" [value]="true">{{ 'alarm-rule.ignore-case' | translate }}</mat-chip-option>
</mat-chip-listbox>
@if (filterPredicateFormGroup.get('operation').value !== stringOperation.NO_DATA) {
<mat-chip-listbox formControlName="ignoreCase" [hideSingleSelectionIndicator]="true">
<mat-chip-option color="primary" [value]="true">{{ 'alarm-rule.ignore-case' | translate }}</mat-chip-option>
</mat-chip-listbox>
}
</div>
}
@case (filterPredicateType.NUMERIC) {
@ -68,7 +70,14 @@
</div>
}
}
@if (type !== filterPredicateType.COMPLEX) {
@if (filterPredicateFormGroup.get('operation').value === stringOperation.NO_DATA) {
<tb-alarm-rule-filter-predicate-no-data-value class="flex-full"
[arguments]="arguments"
[argumentInUse]="argumentInUse"
[valueType]="valueType"
formControlName="duration">
</tb-alarm-rule-filter-predicate-no-data-value>
} @else if (type !== filterPredicateType.COMPLEX) {
<tb-alarm-rule-filter-predicate-value class="flex-full"
[arguments]="arguments"
[argumentInUse]="argumentInUse"

86
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts

@ -23,18 +23,19 @@ import {
ValidationErrors,
Validator
} from '@angular/forms';
import {
BooleanOperation,
booleanOperationTranslationMap,
EntityKeyValueType,
FilterPredicateType,
NumericOperation,
numericOperationTranslationMap,
StringOperation,
stringOperationTranslationMap
} from '@shared/models/query/query.models';
import { EntityKeyValueType } from '@shared/models/query/query.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AlarmRuleFilterPredicate, ComplexAlarmRuleFilterPredicate } from "@shared/models/alarm-rule.models";
import {
AlarmRuleBooleanOperation,
alarmRuleBooleanOperationTranslationMap,
AlarmRuleFilterPredicate,
AlarmRuleFilterPredicateType,
AlarmRuleNumericOperation,
alarmRuleNumericOperationTranslationMap,
AlarmRuleStringOperation,
alarmRuleStringOperationTranslationMap,
ComplexAlarmRuleFilterPredicate
} from "@shared/models/alarm-rule.models";
import { MatDialog } from "@angular/material/dialog";
import {
AlarmRuleComplexFilterPredicateDialogComponent,
@ -77,24 +78,25 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor,
operation: [],
ignoreCase: false,
predicates: [],
value: []
value: [],
duration: []
});
type: FilterPredicateType;
type: AlarmRuleFilterPredicateType;
filterPredicateType = FilterPredicateType;
filterPredicateType = AlarmRuleFilterPredicateType;
stringOperations = Object.keys(StringOperation);
stringOperation = StringOperation;
stringOperationTranslationMap = stringOperationTranslationMap;
stringOperations = Object.keys(AlarmRuleStringOperation);
stringOperation = AlarmRuleStringOperation;
stringOperationTranslationMap = alarmRuleStringOperationTranslationMap;
numericOperations = Object.keys(NumericOperation);
numericOperationEnum = NumericOperation;
numericOperationTranslations = numericOperationTranslationMap;
numericOperations = Object.keys(AlarmRuleNumericOperation);
numericOperationEnum = AlarmRuleNumericOperation;
numericOperationTranslations = alarmRuleNumericOperationTranslationMap;
booleanOperations = Object.keys(BooleanOperation);
booleanOperationEnum = BooleanOperation;
booleanOperationTranslations = booleanOperationTranslationMap;
booleanOperations = Object.keys(AlarmRuleBooleanOperation);
booleanOperationEnum = AlarmRuleBooleanOperation;
booleanOperationTranslations = alarmRuleBooleanOperationTranslationMap;
private propagateChange= (v: any) => { };
@ -132,11 +134,45 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor,
writeValue(predicate: AlarmRuleFilterPredicate): void {
this.type = predicate.type;
this.filterPredicateFormGroup.patchValue(predicate, {emitEvent: false});
if (predicate.type === AlarmRuleFilterPredicateType.NO_DATA) {
this.type = AlarmRuleFilterPredicateType[this.valueType];
this.filterPredicateFormGroup.patchValue({operation: 'NO_DATA', duration: predicate}, {emitEvent: false});
} else {
this.filterPredicateFormGroup.patchValue(predicate, {emitEvent: false});
}
}
private updateModel() {
this.propagateChange({type: this.type, ...this.filterPredicateFormGroup.value});
const predicate = this.filterPredicateFormGroup.value;
if (predicate.operation === 'NO_DATA') {
this.propagateChange(predicate.duration);
} else {
if (!predicate.value) {
switch (this.valueType) {
case EntityKeyValueType.STRING:
predicate.value = {
staticValue: ''
};
break;
case EntityKeyValueType.NUMERIC:
predicate.value = {
staticValue: 0
};
break;
case EntityKeyValueType.DATE_TIME:
predicate.value = {
staticValue: Date.now()
};
break;
case EntityKeyValueType.BOOLEAN:
predicate.value = {
staticValue: false
};
break;
}
}
this.propagateChange({type: this.type, ...predicate});
}
}
public openComplexFilterDialog() {

34
ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts

@ -16,25 +16,27 @@
import { Component, Input } from '@angular/core';
import {
booleanOperationTranslationMap,
ComplexOperation,
complexOperationTranslationMap,
EntityKeyValueType,
FilterPredicateType,
numericOperationTranslationMap,
stringOperationTranslationMap
EntityKeyValueType
} from '@shared/models/query/query.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import {
alarmRuleBooleanOperationTranslationMap,
AlarmRuleExpression,
AlarmRuleExpressionType,
AlarmRuleFilter,
AlarmRuleFilterPredicate,
AlarmRuleFilterPredicateType,
alarmRuleNumericOperationTranslationMap,
AlarmRuleStringOperation,
alarmRuleStringOperationTranslationMap,
ComplexAlarmRuleFilterPredicate
} from "@shared/models/alarm-rule.models";
import { CalculatedFieldArgument } from "@shared/models/calculated-field.models";
import { coerceBoolean } from "@shared/decorators/coercion";
import { timeUnitTranslationMap } from "@shared/models/time/time.models";
@Component({
selector: 'tb-alarm-rule-filter-text',
@ -137,21 +139,21 @@ export class AlarmRuleFilterTextComponent {
const filterOperation: ComplexOperation = complexOperation ? complexOperation : (keyFilter.operation ?? ComplexOperation.AND);
const predicates = keyFilterPredicates.map((keyFilterPredicate: AlarmRuleFilterPredicate) => {
if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
if (keyFilterPredicate.type === AlarmRuleFilterPredicateType.COMPLEX) {
const complexPredicate = keyFilterPredicate as ComplexAlarmRuleFilterPredicate;
const complexOperation = complexPredicate.operation ?? ComplexOperation.AND;
return this.filterPredicateToText(translate, datePipe, keyFilter, complexPredicate.predicates, complexOperation);
} else {
let operation: string;
let value: string;
const val = keyFilterPredicate.value;
const val = keyFilterPredicate.type === AlarmRuleFilterPredicateType.NO_DATA ? keyFilterPredicate.duration : keyFilterPredicate.value;
const dynamicValue = val?.dynamicValueArgument?.length;
if (dynamicValue) {
value = '<span class="tb-filter-dynamic-value"><span class="tb-filter-value">' + val?.dynamicValueArgument + '</span></span>';
}
switch (keyFilterPredicate.type) {
case FilterPredicateType.STRING:
operation = translate.instant(stringOperationTranslationMap.get(keyFilterPredicate.operation));
case AlarmRuleFilterPredicateType.STRING:
operation = translate.instant(alarmRuleStringOperationTranslationMap.get(keyFilterPredicate.operation));
if (keyFilterPredicate.ignoreCase) {
operation += ' ' + translate.instant('filter.ignore-case');
}
@ -159,8 +161,8 @@ export class AlarmRuleFilterTextComponent {
value = `'${keyFilterPredicate.value.staticValue}'`;
}
break;
case FilterPredicateType.NUMERIC:
operation = translate.instant(numericOperationTranslationMap.get(keyFilterPredicate.operation));
case AlarmRuleFilterPredicateType.NUMERIC:
operation = translate.instant(alarmRuleNumericOperationTranslationMap.get(keyFilterPredicate.operation));
if (!dynamicValue) {
if (keyFilter.valueType === EntityKeyValueType.DATE_TIME) {
value = datePipe.transform(keyFilterPredicate.value.staticValue, 'yyyy-MM-dd HH:mm');
@ -169,12 +171,18 @@ export class AlarmRuleFilterTextComponent {
}
}
break;
case FilterPredicateType.BOOLEAN:
operation = translate.instant(booleanOperationTranslationMap.get(keyFilterPredicate.operation));
case AlarmRuleFilterPredicateType.BOOLEAN:
operation = translate.instant(alarmRuleBooleanOperationTranslationMap.get(keyFilterPredicate.operation));
if (!dynamicValue) {
value = translate.instant(keyFilterPredicate.value.staticValue ? 'value.true' : 'value.false');
}
break;
case AlarmRuleFilterPredicateType.NO_DATA:
operation = translate.instant(alarmRuleStringOperationTranslationMap.get(AlarmRuleStringOperation.NO_DATA));
if (!dynamicValue) {
value = keyFilterPredicate.duration.staticValue + ' ' + translate.instant(timeUnitTranslationMap.get(keyFilterPredicate.unit)).toLowerCase();
}
break;
}
if (!dynamicValue) {
value = `<span class="tb-filter-value">${value}</span>`;

94
ui-ngx/src/app/shared/models/alarm-rule.models.ts

@ -118,7 +118,8 @@ export interface AlarmRulePredicateInfo {
export type AlarmRuleFilterPredicate = StringAlarmRuleFilterPredicate |
NumericAlarmRuleFilterPredicate |
BooleanAlarmRuleFilterPredicate |
ComplexAlarmRuleFilterPredicate;
ComplexAlarmRuleFilterPredicate |
NoDataAlarmRuleFilterPredicate;
export interface AlarmRuleValue<T> {
dynamicValueArgument?: string;
@ -126,26 +127,33 @@ export interface AlarmRuleValue<T> {
}
export interface StringAlarmRuleFilterPredicate {
type: FilterPredicateType.STRING;
operation: StringOperation;
type: AlarmRuleFilterPredicateType.STRING;
operation: AlarmRuleStringOperation;
value: AlarmRuleValue<string>;
ignoreCase: boolean;
}
export interface NumericAlarmRuleFilterPredicate {
type: FilterPredicateType.NUMERIC;
operation: NumericOperation;
type: AlarmRuleFilterPredicateType.NUMERIC;
operation: AlarmRuleNumericOperation;
value: AlarmRuleValue<number>;
}
export interface BooleanAlarmRuleFilterPredicate {
type: FilterPredicateType.BOOLEAN;
operation: BooleanOperation;
type: AlarmRuleFilterPredicateType.BOOLEAN;
operation: AlarmRuleBooleanOperation;
value: AlarmRuleValue<boolean>;
}
export interface NoDataAlarmRuleFilterPredicate {
type: AlarmRuleFilterPredicateType.NO_DATA;
unit: TimeUnit,
operation: AlarmRuleStringOperation.NO_DATA | AlarmRuleNumericOperation.NO_DATA | AlarmRuleBooleanOperation.NO_DATA;
duration: AlarmRuleValue<number>;
}
export interface BaseComplexFilterPredicate<T extends AlarmRuleFilterPredicate> {
type: FilterPredicateType.COMPLEX;
type: AlarmRuleFilterPredicateType.COMPLEX;
operation: ComplexOperation;
predicates: Array<T>;
}
@ -157,3 +165,73 @@ export interface AlarmRuleFilterConfig {
entityType?: EntityType;
entities?: Array<string>;
}
export enum AlarmRuleFilterPredicateType {
STRING = 'STRING',
NUMERIC = 'NUMERIC',
BOOLEAN = 'BOOLEAN',
COMPLEX = 'COMPLEX',
NO_DATA = 'NO_DATA'
}
export enum AlarmRuleStringOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL',
NO_DATA = 'NO_DATA',
STARTS_WITH = 'STARTS_WITH',
ENDS_WITH = 'ENDS_WITH',
CONTAINS = 'CONTAINS',
NOT_CONTAINS = 'NOT_CONTAINS',
IN = 'IN',
NOT_IN = 'NOT_IN',
}
export const alarmRuleStringOperationTranslationMap = new Map<AlarmRuleStringOperation, string>(
[
[AlarmRuleStringOperation.EQUAL, 'filter.operation.equal'],
[AlarmRuleStringOperation.NOT_EQUAL, 'filter.operation.not-equal'],
[AlarmRuleStringOperation.STARTS_WITH, 'filter.operation.starts-with'],
[AlarmRuleStringOperation.ENDS_WITH, 'filter.operation.ends-with'],
[AlarmRuleStringOperation.CONTAINS, 'filter.operation.contains'],
[AlarmRuleStringOperation.NOT_CONTAINS, 'filter.operation.not-contains'],
[AlarmRuleStringOperation.IN, 'filter.operation.in'],
[AlarmRuleStringOperation.NOT_IN, 'filter.operation.not-in'],
[AlarmRuleStringOperation.NO_DATA, 'alarm-rule.missing-for']
]
);
export enum AlarmRuleNumericOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL',
NO_DATA = 'NO_DATA',
GREATER = 'GREATER',
LESS = 'LESS',
GREATER_OR_EQUAL = 'GREATER_OR_EQUAL',
LESS_OR_EQUAL = 'LESS_OR_EQUAL'
}
export const alarmRuleNumericOperationTranslationMap = new Map<AlarmRuleNumericOperation, string>(
[
[AlarmRuleNumericOperation.EQUAL, 'filter.operation.equal'],
[AlarmRuleNumericOperation.NOT_EQUAL, 'filter.operation.not-equal'],
[AlarmRuleNumericOperation.GREATER, 'filter.operation.greater'],
[AlarmRuleNumericOperation.LESS, 'filter.operation.less'],
[AlarmRuleNumericOperation.GREATER_OR_EQUAL, 'filter.operation.greater-or-equal'],
[AlarmRuleNumericOperation.LESS_OR_EQUAL, 'filter.operation.less-or-equal'],
[AlarmRuleNumericOperation.NO_DATA, 'alarm-rule.missing-for']
]
);
export enum AlarmRuleBooleanOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL',
NO_DATA = 'NO_DATA',
}
export const alarmRuleBooleanOperationTranslationMap = new Map<AlarmRuleBooleanOperation, string>(
[
[AlarmRuleBooleanOperation.EQUAL, 'filter.operation.equal'],
[AlarmRuleBooleanOperation.NOT_EQUAL, 'filter.operation.not-equal'],
[AlarmRuleBooleanOperation.NO_DATA, 'alarm-rule.missing-for']
]
);

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

@ -1466,6 +1466,7 @@
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition",
"condition-type": "Condition type",
"condition-type-hint": "\"Duration\" and \"Repeating\" options are not available when the \"Missing for\" operation is used in the filter.",
"select-alarm-severity": "Select alarm severity",
"add-create-alarm-rule-prompt": "Please add create alarm rule",
"add-create-alarm-rule": "Add create condition",
@ -1497,7 +1498,12 @@
"no-alarm-rule-types-matching": "No alarm rule types matching '{{entitySubtype}}' were found.",
"alarm-rule-type-list-empty": "No alarm rule types selected.",
"alarm-rule-type-list": "Alarm rule type list",
"alarm-rule-entity-list": "Entity list"
"alarm-rule-entity-list": "Entity list",
"missing-for": "missing for",
"time-unit": "Unit",
"value-required": "Value is required.",
"min-value": "Value must be 0 or greater.",
"argument-in-use": "Argument is used as general argument."
},
"ai-models": {
"ai-models": "AI models",

Loading…
Cancel
Save