Browse Source

Merge pull request #14625 from ArtemDzhereleiko/AD/bug-fix/alarm-rule/fix

Enhancement for Alarm rules
master
Vladyslav Prykhodko 7 hours ago
committed by GitHub
parent
commit
757420d546
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html
  2. 54
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts
  3. 4
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.ts
  4. 39
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts
  5. 2
      ui-ngx/src/app/shared/models/alarm-rule.models.ts
  6. 3
      ui-ngx/src/app/shared/models/constants.ts

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

@ -19,7 +19,9 @@
<mat-toolbar color="primary">
<h2>{{ 'alarm-rule.alarm-rule' | translate}}</h2>
<span class="flex-1"></span>
<div tb-help="alarmRules"></div>
<button mat-icon-button
[disabled]="isLoading"
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
@ -49,7 +51,7 @@
<tb-entity-debug-settings-button
formControlName="debugSettings"
entityLabel="alarm-rule.alarm-rule"
class="pt-[10px]"
style="padding-top: 10px;"
[class.mb-5]="fieldFormGroup.get('name').errors && fieldFormGroup.get('name').touched"
[entityType]="EntityType.CALCULATED_FIELD"
[additionalActionConfig]="additionalDebugActionConfig"
@ -77,31 +79,31 @@
[placeholder]="'action.set' | translate"
[required]="true"
[entityType]="fieldFormGroup.get('entityId.entityType').value"
(entityChanged)="changeEntity($event)"
/>
}
</div>
}
</div>
<ng-container formGroupName="configuration">
<div class="tb-form-panel">
<div class="tb-form-panel-title tb-required" [class.tb-disabled]="!fieldFormGroup.get('entityId.id').value || !fieldFormGroup.get('name').value">{{ 'calculated-fields.arguments' | translate }}</div>
<div class="tb-form-panel" [class.disabled]="disabledArguments">
<div class="tb-form-panel-title tb-required">{{ 'calculated-fields.arguments' | translate }}</div>
<tb-calculated-field-arguments-table formControlName="arguments"
[entityId]="data.entityId || fieldFormGroup.get('entityId').value"
[tenantId]="data.tenantId"
[ownerId]="data.ownerId"
[watchKeyChange]="true"
[disable]="!fieldFormGroup.get('entityId.id').value || !fieldFormGroup.get('name').value"
[entityName]="entityName"/>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" [class.tb-disabled]="!disabledClearRuleButton">{{ 'alarm-rule.create-conditions' | translate }}</div>
<div class="tb-form-panel" [class.disabled]="disabledClearRuleButton">
<div class="tb-form-panel-title">{{ 'alarm-rule.create-conditions' | translate }}</div>
<div class="flex flex-1 flex-col">
<tb-create-cf-alarm-rules formControlName="createRules" [arguments]="arguments" [testScript]="onTestScript.bind(this)">
</tb-create-cf-alarm-rules>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" [class.tb-disabled]="!disabledClearRuleButton">{{ 'alarm-rule.clear-condition' | translate }}</div>
<div class="tb-form-panel" [class.disabled]="disabledClearRuleButton">
<div class="tb-form-panel-title">{{ 'alarm-rule.clear-condition' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2 pb-2"
[class.!hidden]="!configFormGroup.get('clearRule').value">
<div class="clear-alarm-rule flex flex-1 flex-row">
@ -122,7 +124,7 @@
</div>
<div [class.!hidden]="configFormGroup.get('clearRule').value">
<button mat-stroked-button color="primary"
[disabled]="!disabledClearRuleButton"
[disabled]="disabledClearRuleButton"
type="button"
(click)="addClearAlarmRule()">
{{ 'alarm-rule.add-clear-alarm-rule' | translate }}
@ -131,7 +133,7 @@
</div>
<div class="tb-form-panel no-gap">
<mat-expansion-panel class="tb-settings" [expanded]="false">
<mat-expansion-panel-header [class.tb-disabled]="!disabledClearRuleButton">
<mat-expansion-panel-header [class.tb-disabled]="disabledClearRuleButton">
{{ 'alarm-rule.advanced-settings' | translate }}
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
@ -171,13 +173,14 @@
<div mat-dialog-actions class="justify-end">
<button mat-button color="primary"
type="button"
[disabled]="isLoading"
cdkFocusInitial
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
(click)="add()"
[disabled]="isLoading$ | async">
[disabled]="isLoading">
{{ data.buttonTitle | translate }}
</button>
</div>

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

@ -30,21 +30,22 @@ import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { EntityId } from '@shared/models/id/entity-id';
import { AdditionalDebugActionConfig } from '@home/components/entity/debug/entity-debug-settings.model';
import { COMMA, ENTER, SEMICOLON } from "@angular/cdk/keycodes";
import { MatChipInputEvent } from "@angular/material/chips";
import {
AlarmRule,
AlarmRuleConditionType,
alarmRuleEntityTypeList,
AlarmRuleExpressionType,
AlarmRuleTestScriptFn
} from "@shared/models/alarm-rule.models";
import { deepTrim } from "@core/utils";
import { Observable } from "rxjs";
import { switchMap } from "rxjs/operators";
import { combineLatest, Observable } from "rxjs";
import { debounceTime, startWith, 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";
import { BaseData } from "@shared/models/base-data";
export interface AlarmRuleDialogData {
value?: CalculatedField;
@ -92,7 +93,7 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
readonly EntityType = EntityType;
readonly entityTypeTranslations = entityTypeTranslations;
readonly alarmRuleEntityTypeList = [EntityType.DEVICE, EntityType.ASSET, EntityType.CUSTOMER, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE];
readonly alarmRuleEntityTypeList = alarmRuleEntityTypeList;
readonly CalculatedFieldType = CalculatedFieldType;
readonly ScriptLanguage = ScriptLanguage;
@ -101,6 +102,8 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
entityName = this.data.entityName;
disabledClearRuleButton = false;
disabledArguments = false;
isLoading = false;
@ViewChild('entityTypeSelect') entityTypeSelect: EntityTypeSelectComponent;
@ViewChild('entityAutocompleteComponent') entityAutocompleteComponent: EntityAutocompleteComponent;
@ -117,23 +120,30 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
this.applyDialogData();
this.updateRulesValidators();
if (this.data.isDirty) {
this.fieldFormGroup.markAsDirty();
}
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;
}
)
if (!this.data.entityId) {
combineLatest([
this.fieldFormGroup.get('entityId.id')!.valueChanges.pipe(startWith(this.fieldFormGroup.get('entityId.id')!.value)),
this.fieldFormGroup.get('name')!.valueChanges.pipe(startWith(this.fieldFormGroup.get('name')!.value))
]).pipe(
debounceTime(50),
takeUntilDestroyed()
).subscribe(([entityId, name]) => {
this.disabledArguments = !entityId || !name?.length;
const argsControl = this.fieldFormGroup.get('configuration.arguments')!;
if (this.disabledArguments) {
argsControl.disable({ emitEvent: false });
} else {
argsControl.enable({ emitEvent: false });
}
});
}
@ -174,12 +184,16 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
add(): void {
if (this.fieldFormGroup.valid && Object.keys(this.arguments ?? {}).length > 0) {
this.isLoading = true;
const alarmRule = { entityId: this.data.entityId, ...(this.data.value ?? {}), ...this.fromGroupValue};
alarmRule.configuration.type = CalculatedFieldType.ALARM;
this.calculatedFieldsService.saveCalculatedField(alarmRule)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(calculatedField => this.dialogRef.close(calculatedField));
.subscribe({
next: calculatedField => this.dialogRef.close(calculatedField),
error: () => this.isLoading = false
});
} else {
this.fieldFormGroup.get('name').markAsTouched();
this.entityTypeSelect?.markAsTouched();
@ -215,7 +229,7 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
this.fieldFormGroup.get('configuration.propagateToOwner').enable({emitEvent: false});
this.fieldFormGroup.get('configuration.propagateToTenant').enable({emitEvent: false});
this.fieldFormGroup.get('configuration.propagateRelationTypes').enable({emitEvent: false});
this.disabledClearRuleButton = true;
this.disabledClearRuleButton = false;
} else {
this.fieldFormGroup.get('configuration.createRules').disable({emitEvent: false});
this.fieldFormGroup.get('configuration.clearRule').disable({emitEvent: false});
@ -223,7 +237,7 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
this.fieldFormGroup.get('configuration.propagateToOwner').disable({emitEvent: false});
this.fieldFormGroup.get('configuration.propagateToTenant').disable({emitEvent: false});
this.fieldFormGroup.get('configuration.propagateRelationTypes').disable({emitEvent: false});
this.disabledClearRuleButton = false;
this.disabledClearRuleButton = true;
}
}
get predefinedTypeValues(): StringItemsOption[] {
@ -233,4 +247,8 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
}));
}
changeEntity(entity: BaseData<EntityId>): void {
this.entityName = entity.name;
}
}

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

@ -39,7 +39,7 @@ import { fromEvent, Subscription } from 'rxjs';
import { POSITION_MAP } from '@shared/models/overlay.models';
import { UtilsService } from '@core/services/utils.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AlarmRuleFilterConfig } from "@shared/models/alarm-rule.models";
import { alarmRuleEntityTypeList, AlarmRuleFilterConfig } from "@shared/models/alarm-rule.models";
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmRuleFilterConfigData');
@ -100,7 +100,7 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
entityType = EntityType;
listEntityTypes = [EntityType.DEVICE, EntityType.ASSET, EntityType.CUSTOMER, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE];
listEntityTypes = alarmRuleEntityTypeList;
entityTypeTranslations = entityTypeTranslations;
private alarmRuleFilterConfig: AlarmRuleFilterConfig;

39
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts

@ -95,6 +95,13 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
super();
if (this.pageMode) {
this.headerComponent = AlarmRuleTableHeaderComponent;
this.rowPointer = true;
this.handleRowClick = ($event, model) => {
this.editCalculatedField($event, model);
return true;
};
}
this.tableTitle = this.pageMode ? '' : this.translate.instant('alarm-rule.alarm-rules');
this.detailsPanelEnabled = false;
@ -153,7 +160,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
name: this.translate.instant('alarm-rule.copy'),
icon: 'content_copy',
isEnabled: () => true,
onAction: ($event, entity) => this.copyCalculatedField(entity)
onAction: ($event, entity) => this.copyCalculatedField($event, entity)
}
);
this.cellActionDescriptors.push(
@ -161,13 +168,13 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
name: this.translate.instant('action.export'),
icon: 'file_download',
isEnabled: () => true,
onAction: (event$, entity) => this.exportAlarmRule(event$, entity),
onAction: ($event, entity) => this.exportAlarmRule($event, entity),
},
{
name: this.translate.instant('entity-view.events'),
icon: 'mdi:clipboard-text-clock',
isEnabled: () => true,
onAction: (_, entity) => this.openDebugEventsDialog(entity),
onAction: ($event, entity) => this.openDebugEventsDialog($event, entity),
},
{
name: '',
@ -181,7 +188,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
name: this.translate.instant('action.edit'),
icon: 'edit',
isEnabled: () => true,
onAction: (_, entity) => this.editCalculatedField(entity),
onAction: ($event, entity) => this.editCalculatedField($event, entity),
}
);
}
@ -193,14 +200,12 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
}
onOpenDebugConfig($event: Event, calculatedField: CalculatedField): void {
$event?.stopPropagation();
const { debugSettings = {}, id } = calculatedField;
const additionalActionConfig = {
...this.additionalDebugActionConfig,
action: () => this.openDebugEventsDialog(calculatedField)
action: () => this.openDebugEventsDialog($event, calculatedField)
};
if ($event) {
$event.stopPropagation();
}
const { viewContainerRef, renderer } = this.entityDebugSettingsService;
if (!viewContainerRef || !renderer) {
@ -219,7 +224,8 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
}, $event.target as Element);
}
private editCalculatedField(calculatedField: CalculatedField, isDirty = false): void {
private editCalculatedField($event: Event, calculatedField: CalculatedField, isDirty = false): void {
$event?.stopPropagation();
this.getCalculatedAlarmDialog(calculatedField, 'action.apply', isDirty)
.subscribe((res) => {
if (res) {
@ -228,7 +234,8 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
});
}
private copyCalculatedField(calculatedField: CalculatedField, isDirty = false): void {
private copyCalculatedField($event: Event, calculatedField: CalculatedField, isDirty = false): void {
$event?.stopPropagation();
const copyCalculatedAlarmRule = deepClone(calculatedField);
if (this.pageMode) {
copyCalculatedAlarmRule.entityId = null;
@ -243,13 +250,14 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
}
private getCalculatedAlarmDialog(value?: CalculatedField, buttonTitle = 'action.add', isDirty = false): Observable<CalculatedField> {
const entityId = this.entityId || value?.entityId;
return this.dialog.open<AlarmRuleDialogComponent, AlarmRuleDialogData, CalculatedField>(AlarmRuleDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value,
buttonTitle,
entityId: this.entityId,
entityId,
tenantId: this.tenantId,
entityName: this.entityName,
ownerId: this.ownerId ?? {entityType: EntityType.TENANT, id: this.tenantId},
@ -263,7 +271,8 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
.pipe(filter(Boolean));
}
private openDebugEventsDialog(calculatedField: CalculatedField): void {
private openDebugEventsDialog($event: Event, calculatedField: CalculatedField): void {
$event?.stopPropagation();
this.dialog.open<EventsDialogComponent, EventsDialogData, null>(EventsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
@ -282,9 +291,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
}
private exportAlarmRule($event: Event, calculatedField: CalculatedField): void {
if ($event) {
$event.stopPropagation();
}
$event?.stopPropagation();
this.importExportService.exportCalculatedField(calculatedField.id.id);
}
@ -361,7 +368,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
filter(Boolean),
tap(expression => {
if (openCalculatedFieldEdit) {
this.editCalculatedField({
this.editCalculatedField(null, {
entityId: this.entityId, ...calculatedField,
configuration: {...calculatedField.configuration, expression} as any
}, true)

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

@ -23,6 +23,8 @@ import { EntityType } from "@shared/models/entity-type.models";
import { Observable } from "rxjs";
import { CalculatedField, CalculatedFieldArgument } from "@shared/models/calculated-field.models";
export const alarmRuleEntityTypeList = [EntityType.DEVICE, EntityType.ASSET, EntityType.CUSTOMER, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE];
export enum AlarmRuleScheduleType {
ANY_TIME = 'ANY_TIME',
SPECIFIC_TIME = 'SPECIFIC_TIME',

3
ui-ngx/src/app/shared/models/constants.ts

@ -219,7 +219,8 @@ export const HelpLinks = {
aiModels: `${helpBaseUrl}/docs${docPlatformPrefix}/samples/analytics/ai-models/`,
apiKeys: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/api-keys`,
timewindowSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/dashboards/#time-window`,
trendzSettings: `${helpBaseUrl}/docs/trendz/`
trendzSettings: `${helpBaseUrl}/docs/trendz/`,
alarmRules: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/alarm-rules/`,
}
};
/* eslint-enable max-len */

Loading…
Cancel
Save