diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html index 20bd3e1bde..343c3123ab 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html @@ -29,10 +29,11 @@
{{ 'common.general' | translate }}
-
+
{{ 'alarm-rule.alarm-type' | translate }} + alarm-rule.alarm-type-hint @if (fieldFormGroup.get('name').errors && fieldFormGroup.get('name').touched) { @if (fieldFormGroup.get('name').hasError('required')) { @@ -48,15 +49,18 @@
@if (!data.entityId) { -
- + @if (fieldFormGroup.get('entityId.entityType').value) { - }
@@ -85,8 +90,8 @@ [tenantId]="data.tenantId" [ownerId]="data.ownerId" [watchKeyChange]="true" - [disabledAddButton]="!fieldFormGroup.get('entityId.id').value" - [entityName]="data.entityName"/> + [disable]="!fieldFormGroup.get('entityId.id').value || !fieldFormGroup.get('name').value" + [entityName]="entityName"/>
{{ 'alarm-rule.create-conditions' | translate }}
@@ -113,11 +118,11 @@
- alarm-rule.no-clear-alarm-rule + alarm-rule.no-clear-alarm-rule
@if (configFormGroup.get('propagate').value) { - - alarm-rule.alarm-rule-relation-types-list - - - {{key}} - close - - - - - + + }
@@ -181,7 +177,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts index f45e205246..fea0df6024 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -40,6 +40,11 @@ import { import { deepTrim } from "@core/utils"; import { Observable } from "rxjs"; import { switchMap } from "rxjs/operators"; +import { EntityTypeSelectComponent } from "@shared/components/entity/entity-type-select.component"; +import { EntityAutocompleteComponent } from "@shared/components/entity/entity-autocomplete.component"; +import { EntityService } from "@core/http/entity.service"; +import { RelationTypes } from "@shared/models/relation.models"; +import { StringItemsOption } from "@shared/components/string-items-list.component"; export interface AlarmRuleDialogData { value?: CalculatedField; @@ -66,7 +71,7 @@ export class AlarmRuleDialogComponent extends DialogComponent(null, Validators.required), + entityType: this.fb.control(EntityType.DEVICE_PROFILE, Validators.required), id: [null as null | string, Validators.required], }), configuration: this.fb.group({ @@ -93,15 +98,45 @@ export class AlarmRuleDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AlarmRuleDialogData, protected dialogRef: MatDialogRef, private calculatedFieldsService: CalculatedFieldsService, + private entityService: EntityService, private destroyRef: DestroyRef, private fb: FormBuilder) { super(store, router, dialogRef); this.applyDialogData(); + this.updateRulesValidators(); + + this.fieldFormGroup.get('configuration.arguments').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateRulesValidators(); + }); + + if (!this.entityName) { + this.fieldFormGroup.get('entityId.id').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((entityId) => { + if (entityId && (this.fieldFormGroup.get('entityId.entityType').value === EntityType.DEVICE_PROFILE || + this.fieldFormGroup.get('entityId.entityType').value === EntityType.ASSET_PROFILE)) { + this.entityService.getEntity(this.fieldFormGroup.get('entityId.entityType').value as EntityType, entityId, {ignoreLoading: true, ignoreErrors: true}).subscribe( + value => { + this.entityName = value.name; + } + ) + } + }); + } } get configFormGroup(): FormGroup { @@ -169,6 +204,10 @@ export class AlarmRuleDialogComponent extends DialogComponent this.dialogRef.close(calculatedField)); + } else { + this.fieldFormGroup.get('name').markAsTouched(); + this.entityTypeSelect.markAsTouched(); + this.entityAutocompleteComponent.markAsTouched(); } } @@ -191,4 +230,23 @@ export class AlarmRuleDialogComponent extends DialogComponent 0) { + this.fieldFormGroup.get('configuration.createRules').enable({emitEvent: false}); + this.fieldFormGroup.get('configuration.clearRule').enable({emitEvent: false}); + this.disabledClearRuleButton = true; + } else { + this.fieldFormGroup.get('configuration.createRules').disable({emitEvent: false}); + this.fieldFormGroup.get('configuration.clearRule').disable({emitEvent: false}); + this.disabledClearRuleButton = false; + } + } + get predefinedTypeValues(): StringItemsOption[] { + return RelationTypes.map(type => ({ + name: type, + value: type + })); + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index b20f1cee7b..f73633ad8e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -287,7 +287,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig { } private importCalculatedField(): void { - this.importExportService.openCalculatedFieldImportDialog() + this.importExportService.openCalculatedFieldImportDialog('alarm-rule.import', 'alarm-rule.file') .pipe( filter(Boolean), switchMap(calculatedField => { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html index d67cd51a9a..e7bc32a19f 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html @@ -75,14 +75,14 @@ matTooltipPosition="above" class="tb-mat-32" [disabled]="!argumentsList.length" - (click)="onTestScript()"> + (click)="onTestScript($event)"> bug_report
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts index f565a66168..20ad4a4bfb 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts @@ -41,6 +41,7 @@ import { alarmRuleDefaultScript, AlarmRuleExpressionType, AlarmRuleFilter, + areFiltersAndPredicateArgumentsValid, filterOperationTranslationMap } from "@shared/models/alarm-rule.models"; @@ -137,7 +138,7 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent { - this.filtersValid = this.areFilterAndPredicateArgumentsValid(filters, this.argumentsList); + this.filtersValid = areFiltersAndPredicateArgumentsValid(filters, this.data.arguments); this.checkIsNoData(filters); }); @@ -206,39 +207,6 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent { this.conditionFormGroup.get('expression.expression').setValue(expression); diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html index 638075849f..4892fcdecd 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html @@ -26,13 +26,15 @@
- + {{ conditionSet() ? 'edit' : 'add' }}
@@ -48,7 +50,10 @@ (click)="openScheduleDialog($event)">
- edit + + edit +
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts index 6f17aa8b59..08004d7497 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts @@ -32,7 +32,7 @@ import { getAlarmScheduleRangeText, utcTimestampToTimeOfDay } from '@shared/models/device.models'; -import { TimeUnit } from '@shared/models/time/time.models'; +import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; import { CfAlarmRuleConditionDialogComponent, CfAlarmRuleConditionDialogData @@ -42,7 +42,8 @@ import { AlarmRuleConditionType, AlarmRuleExpressionType, AlarmRuleSchedule, - AlarmRuleScheduleType + AlarmRuleScheduleType, + checkPredicates } from "@shared/models/alarm-rule.models"; import { CalculatedFieldArgument } from "@shared/models/calculated-field.models"; import { @@ -154,32 +155,17 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali return !arg || validArguments.includes(arg); } - private areFilterAndPredicateArgumentsValid(obj: any, validArguments: string[]): boolean { - const validSet = new Set(validArguments); + private areFilterAndPredicateArgumentsValid(obj: any, args: Record): boolean { + const validSet = new Set(Object.keys(args)); const filters = obj?.expression?.filters || obj?.filters || []; for (const filter of filters) { if (filter.argument && !validSet.has(filter.argument)) { return false; } } - function checkPredicates(predicates: any[]): boolean { - for (const p of predicates) { - if (p.value?.dynamicValueArgument) { - if (!validSet.has(p.value.dynamicValueArgument)) { - return false; - } - } - if (p.type === 'COMPLEX' && Array.isArray(p.predicates)) { - if (!checkPredicates(p.predicates)) { - return false; - } - } - } - return true; - } for (const filter of filters) { if (Array.isArray(filter.predicates)) { - if (!checkPredicates(filter.predicates)) { + if (!checkPredicates(filter.predicates, validSet)) { return false; } } @@ -192,7 +178,7 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali } public validate(control: AbstractControl): ValidationErrors | null { - this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, Object.keys(this.arguments)); + this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); this.onValidatorChange = () => { control.updateValueAndValidity({ emitEvent: true }); @@ -265,7 +251,7 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali if (this.modelValue.value.dynamicValueArgument) { this.specText = this.translate.instant('alarm-rule.condition-during-dynamic', { attribute: `${this.modelValue.value.dynamicValueArgument}` - }); + }) + ' ' + this.translate.instant(timeUnitTranslationMap.get(this.modelValue.unit)).toLowerCase(); } else { this.specText = this.translate.instant('alarm-rule.condition-during', { during: duringText diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html index a9f4ad61a5..a7ccc25fe6 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html @@ -18,33 +18,29 @@
- @if (!disabled || alarmRuleFormGroup.get('alarmDetails').value) { -
-
- alarm-rule.alarm-rule-additional-info -
- - - - +
+
+ alarm-rule.alarm-rule-additional-info
- } - @if (!disabled || alarmRuleFormGroup.get('dashboardId').value) { -
-
- alarm-rule.alarm-rule-mobile-dashboard -
- - + + + + +
+
+
+ alarm-rule.alarm-rule-mobile-dashboard
- } + + +
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html index 359ad104f1..bde332b383 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html @@ -16,7 +16,7 @@ --> -
+
@for (createAlarmRuleControl of createAlarmRulesFormArray().controls; track createAlarmRuleControl; let index = $index) {
} -
- + @if (!createAlarmRulesFormArray().controls.length) { + alarm-rule.add-create-alarm-rule-prompt - -
-
+ + } +
+
- } -
-
-
-
{{ filterControl.value?.argument }}
-
{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.valueType) | translate }}
- -
+ @if (index) { + + }
- @if (index) { - - } -
- } - @if (!filtersFormArray.length) { - - alarm-rule.no-filter - - - } -
- + } +
+ +} @else { + alarm-rule.no-filter +} + +
+ @for (predicateControl of predicatesFormArray.controls; track predicateControl; let index = $index) { +
+ @if (index) { +
+ {{ complexOperationTranslations.get(operation) | translate }} +
+ } +
+
+ + + +
-
- } - @if (!predicatesFormArray.length) { - - alarm-rule.no-filter - - - } + } +
-
-
+ } @else { + alarm-rule.no-filter + } +
} } - @if (filterPredicateFormGroup.get('operation').value === stringOperation.NO_DATA) { - - - } @else if (type !== filterPredicateType.COMPLEX) { - - - } + + + +
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts index 2f542f048e..32791ade96 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts @@ -34,6 +34,7 @@ import { alarmRuleNumericOperationTranslationMap, AlarmRuleStringOperation, alarmRuleStringOperationTranslationMap, + checkPredicates, ComplexAlarmRuleFilterPredicate } from "@shared/models/alarm-rule.models"; import { MatDialog } from "@angular/material/dialog"; @@ -111,6 +112,18 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, this.updateModel(); }); + this.filterPredicateFormGroup.get('operation').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(value => { + if (value === 'NO_DATA') { + this.filterPredicateFormGroup.get('duration').enable({emitEvent: false}); + this.filterPredicateFormGroup.get('value').disable({emitEvent: false}); + } else { + this.filterPredicateFormGroup.get('duration').disable({emitEvent: false}); + this.filterPredicateFormGroup.get('value').enable({emitEvent: false}); + } + }) + this.filterPredicateFormGroup.get('predicates').valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(predicates => { @@ -140,25 +153,10 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, } } - private isPredicateArgumentsValid(predicates: any): boolean { + private isPredicateArgumentsValid(predicates: AlarmRuleFilterPredicate[]): boolean { const validSet = new Set(Object.keys(this.arguments)); - function checkPredicates(predicates: any[]): boolean { - for (const p of predicates) { - if (p.value?.dynamicValueArgument) { - if (!validSet.has(p.value.dynamicValueArgument)) { - return false; - } - } - if (p.type === 'COMPLEX' && Array.isArray(p.predicates)) { - if (!checkPredicates(p.predicates)) { - return false; - } - } - } - return true; - } if (Array.isArray(predicates)) { - if (!checkPredicates(predicates)) { + if (!checkPredicates(predicates, validSet)) { return false; } } @@ -172,8 +170,12 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, } if (predicate.type === AlarmRuleFilterPredicateType.NO_DATA) { this.type = AlarmRuleFilterPredicateType[this.valueType]; + this.filterPredicateFormGroup.get('duration').enable({emitEvent: false}); + this.filterPredicateFormGroup.get('value').disable({emitEvent: false}); this.filterPredicateFormGroup.patchValue({operation: 'NO_DATA', duration: predicate}, {emitEvent: false}); } else { + this.filterPredicateFormGroup.get('duration').disable({emitEvent: false}); + this.filterPredicateFormGroup.get('value').enable({emitEvent: false}); this.filterPredicateFormGroup.patchValue(predicate, {emitEvent: false}); } } @@ -183,30 +185,6 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, 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}); } } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.html index 4ecba0f3d2..d070c2de84 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.html @@ -18,6 +18,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.scss b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.scss index 8712fdfd32..dbaf7dd966 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.scss @@ -23,6 +23,9 @@ color: #f44336; padding: 0; } + &.disabled { + color: rgba(0,0,0,0.38); + } &.nowrap { white-space: nowrap; text-overflow: ellipsis; diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts index 703c41665e..aa8aa2bfec 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-text.component.ts @@ -63,6 +63,10 @@ export class AlarmRuleFilterTextComponent { @Input() arguments: Record; + @Input() + @coerceBoolean() + disabled = false; + private alarmRuleExpressionValue: AlarmRuleExpression; get alarmRuleExpression(): AlarmRuleExpression { return this.alarmRuleExpressionValue; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html index 6b8f4fda79..d887c0f40a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html @@ -15,8 +15,10 @@ limitations under the License. --> -
-
+
+
@@ -94,6 +96,7 @@
-
- {{ 'calculated-fields.no-arguments' | translate }} -
@if (errorText || (dataSource.isEmpty() | async)) { }
+
+ {{ 'calculated-fields.no-arguments' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index bca335211e..002a515164 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -16,6 +16,7 @@ import { AfterViewInit, + booleanAttribute, ChangeDetectorRef, Component, DestroyRef, @@ -87,7 +88,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces @Input() entityName: string; @Input() ownerId: EntityId; @Input() isScript: boolean; - @Input() disabledAddButton = false; + @Input({transform: booleanAttribute}) disable = false; @Input() watchKeyChange = false; @ViewChild(MatSort, { static: true }) sort: MatSort; @@ -220,8 +221,6 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling'; } else if (this.argumentsFormArray.controls.some(control => control.value.refEntityId?.id === NULL_UUID)) { this.errorText = 'calculated-fields.hint.arguments-entity-not-found'; - } else if (!this.argumentsFormArray.controls.length) { - this.errorText = 'calculated-fields.hint.arguments-empty'; } else { this.errorText = ''; } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 3070a6cbb2..14ec6d1ce8 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ 'entity.type-calculated-field' | translate}}

diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html index dcfd37796d..67ec7f097b 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html @@ -16,7 +16,9 @@ -->
-
+
@@ -121,14 +123,14 @@ *matHeaderRowDef="['name', 'entityType', 'target', 'key', 'reportStrategy', 'actions']">
-
- {{ 'calculated-fields.no-zone-configured' | translate }} -
@if (errorText) { }
+
+ {{ 'calculated-fields.no-zone-configured' | translate }} +
-
- -
-
calculated-fields.use-latest-timestamp
-
-
-
+ @if (relatedAggregationConfiguration.get('output').value?.type === OutputType.Timeseries) { +
+ +
+
calculated-fields.use-latest-timestamp
+
+
+
+ }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html index cfab9d9def..178f8b47d6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html @@ -23,6 +23,7 @@ [tenantId]="tenantId" [ownerId]="ownerId" [entityName]="entityName" + [watchKeyChange]="true" [isScript]="isScript" />
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html index e836e5e28b..67f4f86970 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -104,8 +104,20 @@ {{ 'relation.type' | translate }} - - {{ relation.type }} + +
+ {{ relation.type }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss index 263ebfeed3..a7dffdfe0a 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss @@ -59,6 +59,17 @@ overflow: hidden; text-overflow: ellipsis; } + + .type-copy { + visibility: hidden; + transition: visibility 0.1s; + } + + .type:hover { + .type-copy { + visibility: visible; + } + } } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index be8c1cd4c5..dba5351df3 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -475,4 +475,8 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit get showEntityLink(): boolean { return this.selectEntityFormGroup.get('entity').value && this.disabled && this.entityURL !== ''; } + + markAsTouched(): void { + this.selectEntityFormGroup.get('entity').markAsTouched(); + } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index 9e9ffcb474..fcfe158da0 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -173,4 +173,8 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, return ''; } } + + markAsTouched(): void { + this.entityTypeFormGroup.get('entityType').markAsTouched(); + } } diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 34e584a527..d416917bb8 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -183,8 +183,8 @@ export class ImportExportService { }); } - public openCalculatedFieldImportDialog(): Observable { - return this.openImportDialog('calculated-fields.import', 'calculated-fields.file').pipe( + public openCalculatedFieldImportDialog(importTitle = 'calculated-fields.import', importFileLabel = 'calculated-fields.file'): Observable { + return this.openImportDialog(importTitle, importFileLabel).pipe( catchError(() => of(null)), ); } diff --git a/ui-ngx/src/app/shared/models/alarm-rule.models.ts b/ui-ngx/src/app/shared/models/alarm-rule.models.ts index 484c6be3a2..3d94461684 100644 --- a/ui-ngx/src/app/shared/models/alarm-rule.models.ts +++ b/ui-ngx/src/app/shared/models/alarm-rule.models.ts @@ -18,17 +18,10 @@ import { CustomTimeSchedulerItem } from "@shared/models/device.models"; import { DashboardId } from "@shared/models/id/dashboard-id"; import { TimeUnit } from "@shared/models/time/time.models"; -import { - BooleanOperation, - ComplexOperation, - EntityKeyValueType, - FilterPredicateType, - NumericOperation, - StringOperation -} from "@shared/models/query/query.models"; +import { ComplexOperation, EntityKeyValueType, FilterPredicateType } from "@shared/models/query/query.models"; import { EntityType } from "@shared/models/entity-type.models"; import { Observable } from "rxjs"; -import { CalculatedField } from "@shared/models/calculated-field.models"; +import { CalculatedField, CalculatedFieldArgument } from "@shared/models/calculated-field.models"; export enum AlarmRuleScheduleType { ANY_TIME = 'ANY_TIME', @@ -250,3 +243,62 @@ export const alarmRuleDefaultScript = 'return temperature > 20;' export type AlarmRuleTestScriptFn = (calculatedField: CalculatedField, expression: string, argumentsObj?: Record, closeAllOnSave?: boolean) => Observable; + +export function checkPredicates(predicates: any[], validSet: Set): boolean { + for (const predicate of predicates) { + if (!predicate) continue; + if (predicate?.value?.dynamicValueArgument) { + if (!validSet.has(predicate.value.dynamicValueArgument)) { + return false; + } + } + if (predicate.type === 'COMPLEX' && Array.isArray(predicate.predicates)) { + if (!checkPredicates(predicate.predicates, validSet)) { + return false; + } + } + } + return true; +} + +export function areFilterAndPredicateArgumentsValid(obj: any, args: Record): boolean { + const validSet = new Set(Object.keys(args)); + const filter = obj || []; + if (filter.argument && !validSet.has(filter.argument)) { + return false; + } + if (Array.isArray(filter.predicates)) { + if (!checkPredicates(filter.predicates, validSet)) { + return false; + } + } + return true; +} + +export function areFiltersAndPredicateArgumentsValid(obj: any, args: Record): boolean { + const validSet = new Set(Object.keys(args)); + const filters = obj || []; + for (const filter of filters) { + if (filter.argument && !validSet.has(filter.argument)) { + return false; + } + } + for (const filter of filters) { + if (Array.isArray(filter.predicates)) { + if (!checkPredicates(filter.predicates, validSet)) { + return false; + } + } + } + return true; +} + +export function isPredicateArgumentsValid(predicates: any, args: Record): boolean { + const validSet = new Set(Object.keys(args)); + if (Array.isArray(predicates)) { + if (!checkPredicates(predicates, validSet)) { + return false; + } + } + return true; +} diff --git a/ui-ngx/src/assets/help/en_US/alarm-rule/alarm_rule_schedule_format.md b/ui-ngx/src/assets/help/en_US/alarm-rule/alarm_rule_schedule_format.md index c49d1029bf..bc332f1ea5 100644 --- a/ui-ngx/src/assets/help/en_US/alarm-rule/alarm_rule_schedule_format.md +++ b/ui-ngx/src/assets/help/en_US/alarm-rule/alarm_rule_schedule_format.md @@ -49,7 +49,7 @@ The argument value for a specific time schedule must be a JSON object in the fol ```javascript { - "type": "CUSTOM" + "type": "CUSTOM", "timezone": "Europe/Kiev", "items": [ { 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 477c1fa012..dc11b649e7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1122,7 +1122,7 @@ "add-argument": "Add argument", "test-script-function": "Test script function", "test-expression-function": "Test expression function", - "no-arguments": "No arguments configured", + "no-arguments": "At least one argument is required.", "argument-settings": "Argument settings", "argument-current": "Current entity", "argument-current-tenant": "Current tenant", @@ -1176,7 +1176,7 @@ "target-zone": "Target zone", "perimeter-key": "Perimeter key", "report-strategy": "Report strategy", - "no-zone-configured": "No zone group configured", + "no-zone-configured": "At least one zone is required.", "no-zone-configured-required": "At least one zone group must be configured.", "add-zone-group": "Add zone group", "report-transition-event-only": "Transition events only", @@ -1245,7 +1245,7 @@ "key": "Key", "function": "Function" }, - "no-metrics-configured": "No metrics configured", + "no-metrics-configured": "At least one metric is required.", "add-metric": "Add metric", "max-metrics": "Maximum number of metrics reached.", "metric-settings": "Metric settings", @@ -1268,7 +1268,7 @@ "ttl-min": "Only 0 minimum TTL is allowed", "processing-parameters": "Processing parameters", "hint": { - "strategy": "Controls whether the result is processed immediately or sent to a rule chain for additional processing", + "strategy": "Controls whether the result is processed immediately or sent to a rule chain for additional processing.", "processing-options": "Processing options", "update-attribute-only-on-value-change": "Updates attribute on every incoming message, regardless of whether the value has changed. This increases API usage and reduces performance.", "update-attribute-only-on-value-change-enabled": "Updates attribute only when the value changes. If the value is unchanged, timestamps are not updated and notifications are not sent.", @@ -1380,7 +1380,7 @@ "alarm-rules-old": "Old", "alarm-rules-actual": "Actual", "severities": "Severities", - "cleared": "Clearing condition", + "cleared": "Clear condition", "delete-title": "Are you sure you want to delete the alarm rule '{{title}}'?", "delete-text": "Be careful, after the confirmation the alarm rule and all related data will become unrecoverable.", "delete-multiple-title": "Are you sure you want to delete { count, plural, =1 {1 alarm rule} other {# alarm rules} }?", @@ -1392,6 +1392,7 @@ "list": "{ count, plural, =1 {One alarm rule} other {List of # alarm rules} }", "selected-fields": "{ count, plural, =1 {1 alarm rule} other {# alarm rules} } selected", "import": "Import alarm rule", + "file": "Alarm rule file", "export": "Export alarm rule", "export-failed-error": "Unable to export alarm rule: {{error}}", "entity-type": "Entity type", @@ -1400,6 +1401,7 @@ "target-entity": "Target entity", "target-entities": "Target entities", "alarm-type": "Alarm type", + "alarm-type-hint": "Unique identifier (e.g., HighTemperatureAlarm) across the scope of the alarm originator (Device, Asset, etc.) to prevent conflicts.", "alarm-type-required": "Alarm type is required.", "alarm-type-pattern": "Alarm type is invalid.", "alarm-type-max-length": "Alarm type should be less than 256 characters.", @@ -1435,8 +1437,7 @@ "add-filter": "Add argument filter", "edit-filter": "Argument filter", "remove-filter": "Remove argument filter", - "no-filter": "No argument filters configured", - "filter-required": "At least one filter must be configured.", + "no-filter": "At least one filter is required.", "conditions": { "simple": "Simple", "duration": "Duration", @@ -1495,9 +1496,9 @@ "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": "At least one creation condition should be configured", - "add-create-alarm-rule": "Add creation condition", - "add-clear-alarm-rule": "Add clearing condition", + "add-create-alarm-rule-prompt": "At least one trigger condition is required.", + "add-create-alarm-rule": "Add trigger condition", + "add-clear-alarm-rule": "Add clear condition", "condition-duration": "Condition duration", "condition-duration-value": "Duration value", "condition-duration-time-unit": "Time unit", @@ -1510,9 +1511,9 @@ "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", - "create-conditions": "Creation conditions", - "clear-condition": "Clearing condition", - "no-clear-alarm-rule": "Clearing condition not configured", + "create-conditions": "Alarm Trigger Conditions", + "clear-condition": "Alarm Clear Condition", + "no-clear-alarm-rule": "No clear condition configured.", "advanced-settings": "Advanced settings", "propagate-alarm": "Propagate alarm to related entities", "alarm-rule-relation-types-list": "Relation types", @@ -4981,7 +4982,8 @@ "additional-info": "Additional info (JSON)", "invalid-additional-info": "Unable to parse additional info json.", "no-relations-text": "No relations found", - "not": "Not" + "not": "Not", + "copy-type": "Copy type" }, "resource": { "add": "Add resource",