Browse Source

UI: Refactoring alarm rule

pull/8046/head
Vladyslav_Prykhodko 4 years ago
parent
commit
034832ddbe
  1. 6
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts
  2. 64
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html
  3. 214
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts
  4. 36
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.html
  5. 132
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.ts
  6. 19
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalations.component.ts
  7. 219
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  8. 31
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss
  9. 299
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts
  10. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts
  11. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  12. 4
      ui-ngx/src/app/shared/models/notification.models.ts
  13. 100
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts

@ -48,6 +48,8 @@ import {
} from '@home/pages/notification-center/rule-table/rule-notification-dialog.component'; } from '@home/pages/notification-center/rule-table/rule-notification-dialog.component';
import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component'; import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component';
import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component'; import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component';
import { AlarmTypeListComponent } from '@home/pages/notification-center/rule-table/alarm-type-list.component';
import { AlarmSeveritiesListComponent } from '@home/pages/notification-center/rule-table/alarm-severities-list.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -63,7 +65,9 @@ import { EscalationFormComponent } from '@home/pages/notification-center/rule-ta
RuleTableHeaderComponent, RuleTableHeaderComponent,
RuleNotificationDialogComponent, RuleNotificationDialogComponent,
EscalationsComponent, EscalationsComponent,
EscalationFormComponent EscalationFormComponent,
AlarmTypeListComponent,
AlarmSeveritiesListComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

64
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html

@ -0,0 +1,64 @@
<!--
Copyright © 2016-2022 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.
-->
<section [formGroup]="alarmSeveritiesForm">
<mat-form-field fxFlex class="mat-block" [floatLabel]="flotLabel">
<mat-label translate>alarm.alarm-severity-list</mat-label>
<mat-chip-list #severitiesChipList formControlName="alarmSeverities" [required]="required">
<mat-chip *ngFor="let severity of alarmSeveritiesForm.get('alarmSeverities').value"
[selectable]="!disabled"
[removable]="!disabled"
(removed)="onSeverityRemoved(severity)">
{{ alarmSeverityTranslationMap.get(severity) | translate }}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip>
<input matInput
type="text"
formControlName="alarmSeverity"
placeholder="{{ !alarmSeveritiesForm.get('alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}"
#severityInput
(focusin)="onFocus()"
matAutocompleteOrigin
#origin="matAutocompleteOrigin"
[matAutocompleteConnectedTo]="origin"
[matAutocomplete]="severityAutocomplete"
[matChipInputFor]="severitiesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addSeverityFromChipInput($event)">
</mat-chip-list>
<mat-autocomplete #severityAutocomplete="matAutocomplete"
class="tb-autocomplete"
(optionSelected)="addSeverityFromAutocomplete($event)"
[displayWith]="displaySeverityFn.bind(this)">
<mat-option *ngFor="let severity of filteredDisplaySeverities | async" [value]="severity">
<span [innerHTML]="alarmSeverityTranslationMap.get(alarmSeverity[severity]) | translate | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="(filteredDisplaySeverities | async)?.length === 0" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span translate>notification.no-severity-found</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ 'notification.no-severity-matching' | translate :{severity: truncate.transform(searchText, true, 6, &apos;...&apos;)} }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</section>

214
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts

@ -0,0 +1,214 @@
///
/// Copyright © 2016-2022 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, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
import { TranslateService } from '@ngx-translate/core';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { Observable } from 'rxjs';
import { map, share } from 'rxjs/operators';
import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { FloatLabelType } from '@angular/material/form-field/form-field';
@Component({
selector: 'tb-alarm-severities-list',
templateUrl: './alarm-severities-list.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmSeveritiesListComponent),
multi: true
}
]
})
export class AlarmSeveritiesListComponent implements ControlValueAccessor{
@ViewChild('severityInput', {static: true}) severityInput: ElementRef<HTMLInputElement>;
@ViewChild('severityInput', {read: MatAutocompleteTrigger, static: true}) autocompleteTrigger: MatAutocompleteTrigger;
alarmSeveritiesForm: FormGroup;
alarmSeverityTranslationMap = alarmSeverityTranslations;
private alarmSeverities = Object.keys(AlarmSeverity);
alarmSeverity = AlarmSeverity;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private modelValue: Array<string> | null;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
this.updateValidators();
}
}
@Input()
disabled: boolean;
@Input()
flotLabel: FloatLabelType = 'auto';
searchText = '';
filteredDisplaySeverities: Observable<Array<string>>;
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder,
private translate: TranslateService,
public truncate: TruncatePipe) {
this.alarmSeveritiesForm = this.fb.group({
alarmSeverities: [null, this.required ? [Validators.required] : []],
alarmSeverity: ['']
});
this.filteredDisplaySeverities = this.alarmSeveritiesForm.get('alarmSeverity').valueChanges
.pipe(
map((value) => value ? value : ''),
map(name => this.fetchSeverities(name) ),
share()
);
}
updateValidators() {
this.alarmSeveritiesForm.get('alarmSeverities').setValidators(this.required ? [Validators.required] : []);
this.alarmSeveritiesForm.get('alarmSeverities').updateValueAndValidity();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.alarmSeveritiesForm.disable({emitEvent: false});
} else {
this.alarmSeveritiesForm.enable({emitEvent: false});
}
}
writeValue(value: Array<string> | null): void {
this.searchText = '';
if (value != null && value.length > 0) {
this.modelValue = [...value];
this.alarmSeveritiesForm.get('alarmSeverities').setValue(value);
} else {
this.alarmSeveritiesForm.get('alarmSeverities').setValue(null);
this.modelValue = null;
}
this.dirty = true;
}
addSeverityFromAutocomplete(event: MatAutocompleteSelectedEvent) {
this.addSeverity(event.option.value);
}
addSeverityFromChipInput(event: MatChipInputEvent): void {
const alarmSeverity = event.value || '';
const result = this.fetchSeverities(alarmSeverity.trim());
if (result.length === 1) {
this.addSeverity(result[0]);
}
}
private addSeverity(alarmSeverity: string) {
if (alarmSeverity) {
if (!this.modelValue || this.modelValue.indexOf(alarmSeverity) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(alarmSeverity);
this.alarmSeveritiesForm.get('alarmSeverities').setValue(this.modelValue);
}
this.propagateChange(this.modelValue);
if (!this.fetchSeverities().length) {
this.clear('', true);
this.autocompleteTrigger.closePanel();
} else {
this.clear();
}
}
}
onSeverityRemoved(alarmSeverity: string) {
const index = this.modelValue.indexOf(alarmSeverity);
if (index >= 0) {
this.modelValue.splice(index, 1);
if (!this.modelValue.length) {
this.modelValue = null;
}
this.alarmSeveritiesForm.get('alarmSeverities').setValue(this.modelValue);
this.propagateChange(this.modelValue);
this.clear(this.severityInput.nativeElement.value, true);
this.autocompleteTrigger.closePanel();
}
}
displaySeverityFn(severity?: string): string | undefined {
return severity ? this.translate.instant(alarmSeverityTranslations.get(AlarmSeverity[severity])) : undefined;
}
onFocus() {
if (this.dirty) {
this.alarmSeveritiesForm.get('alarmSeverity').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
private fetchSeverities(searchText?: string): Array<string> {
this.searchText = searchText;
const allowAlarmSeverity = this.alarmSeverities.filter(severity => !this.modelValue?.includes(severity));
if (this.textIsNotEmpty(this.searchText)) {
const search = this.searchText.toUpperCase();
return allowAlarmSeverity.filter(severity => severity.toUpperCase().includes(search));
} else {
return allowAlarmSeverity;
}
}
private clear(value: string = '', ignoreFocus = false) {
this.severityInput.nativeElement.value = value;
this.alarmSeveritiesForm.get('alarmSeverity').patchValue(value);
if (!ignoreFocus) {
setTimeout(() => {
this.severityInput.nativeElement.blur();
this.severityInput.nativeElement.focus();
}, 0);
}
}
}

36
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.html

@ -0,0 +1,36 @@
<!--
Copyright © 2016-2022 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.
-->
<section [formGroup]="alarmTypeForm">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-type-list</mat-label>
<mat-chip-list #alarmTypeChipList formControlName="alarmTypes" [required]="required">
<mat-chip *ngFor="let type of alarmTypeList"
[selectable]="!disabled"
[removable]="!disabled"
(removed)="removeAlarmType(type)">
{{type}}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip>
<input placeholder="{{ !alarmTypeForm.get('alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
[matChipInputFor]="alarmTypeChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addAlarmType($event)">
</mat-chip-list>
</mat-form-field>
</section>

132
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.ts

@ -0,0 +1,132 @@
///
/// Copyright © 2016-2022 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, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
@Component({
selector: 'tb-alarm-type-list',
templateUrl: './alarm-type-list.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmTypeListComponent),
multi: true
}
]
})
export class AlarmTypeListComponent implements ControlValueAccessor{
alarmTypeForm: FormGroup;
private modelValue: Array<string> | null;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
this.updateValidators();
}
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder) {
this.alarmTypeForm = this.fb.group({
alarmTypes: [null, this.required ? [Validators.required] : []]
});
}
updateValidators() {
this.alarmTypeForm.get('alarmTypes').setValidators(this.required ? [Validators.required] : []);
this.alarmTypeForm.get('alarmTypes').updateValueAndValidity();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.alarmTypeForm.disable({emitEvent: false});
} else {
this.alarmTypeForm.enable({emitEvent: false});
}
}
writeValue(value: Array<string> | null): void {
if (value != null && value.length > 0) {
this.modelValue = [...value];
this.alarmTypeForm.get('alarmTypes').setValue(value);
} else {
this.alarmTypeForm.get('alarmTypes').setValue(null);
this.modelValue = null;
}
}
addAlarmType(event: MatChipInputEvent): void {
let alarmType = event.value || '';
const input = event.chipInput.inputElement;
alarmType = alarmType.trim();
if (alarmType) {
if (!this.modelValue || this.modelValue.indexOf(alarmType) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(alarmType);
this.alarmTypeForm.get('alarmTypes').setValue(this.modelValue);
}
this.propagateChange(this.modelValue);
if (input) {
input.value = '';
}
}
}
removeAlarmType(alarmType: string) {
const index = this.modelValue.indexOf(alarmType);
if (index >= 0) {
this.modelValue.splice(index, 1);
if (!this.modelValue.length) {
this.modelValue = null;
}
this.alarmTypeForm.get('alarmTypes').setValue(this.modelValue);
this.propagateChange(this.modelValue);
}
}
get alarmTypeList(): string[] {
return this.alarmTypeForm.get('alarmTypes').value;
}
}

19
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalations.component.ts

@ -118,13 +118,17 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
} }
} }
writeValue(escalations: Array<NonConfirmedNotificationEscalation> | null): void { writeValue(escalations: {[key: string]: Array<string>} | null): void {
if (escalations?.length === this.escalationsFormArray.length) { const escalationParse: Array<NonConfirmedNotificationEscalation> = [];
this.escalationsFormArray.patchValue(escalations, {emitEvent: false}); for (const escalation in escalations) {
escalationParse.push({delayInSec: Number(escalation), targets: escalations[escalation]});
}
if (escalationParse?.length === this.escalationsFormArray.length) {
this.escalationsFormArray.patchValue(escalationParse, {emitEvent: false});
} else { } else {
const escalationsControls: Array<AbstractControl> = []; const escalationsControls: Array<AbstractControl> = [];
if (escalations) { if (escalationParse) {
escalations.forEach((escalation, index) => { escalationParse.forEach((escalation, index) => {
escalationsControls.push(this.fb.control(escalation, [Validators.required])); escalationsControls.push(this.fb.control(escalation, [Validators.required]));
}); });
} else { } else {
@ -166,7 +170,10 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
} }
private updateModel() { private updateModel() {
const escalations = this.escalationsFormGroup.get('escalations').value; const escalations = {};
this.escalationsFormGroup.get('escalations').value.forEach(
escalation => escalations[escalation.delayInSec] = escalation.targets
);
this.propagateChange(escalations); this.propagateChange(escalations);
} }
} }

219
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html

@ -67,94 +67,46 @@
*ngIf="ruleNotificationForm.get('triggerType').value === triggerType.ALARM"> *ngIf="ruleNotificationForm.get('triggerType').value === triggerType.ALARM">
<ng-template matStepLabel>{{ 'notification.alarm-trigger-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.alarm-trigger-settings' | translate }}</ng-template>
<form [formGroup]="alarmTemplateForm"> <form [formGroup]="alarmTemplateForm">
<fieldset class="fields-group"> <section formGroupName="triggerConfig">
<legend translate>notification.filter</legend> <fieldset class="fields-group">
<mat-form-field fxFlex class="mat-block" floatLabel="always"> <legend translate>notification.filter</legend>
<mat-label translate>alarm.alarm-type-list</mat-label> <tb-alarm-type-list
<mat-chip-list #alarmTypeChipList formControlName="alarmTypes"> formControlName="alarmTypes">
<mat-chip *ngFor="let type of alarmTypeList()" [selectable]="true" </tb-alarm-type-list>
[removable]="true" (removed)="removeAlarmType(type)">
{{type}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="{{ !alarmTemplateForm.get('alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
[matChipInputFor]="alarmTypeChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addAlarmType($event)">
</mat-chip-list>
</mat-form-field>
<mat-form-field fxFlex class="mat-block"> <tb-alarm-severities-list formControlName="alarmSeverities"
<mat-label translate>alarm.alarm-severity-list</mat-label> flotLabel="always"
<mat-chip-list #severitiesChipList formControlName="alarmSeverities" required>
required> </tb-alarm-severities-list>
<mat-chip *ngFor="let severity of alarmTemplateForm.get('alarmSeverities').value" </fieldset>
[removable]="true" (removed)="onSeverityRemoved(severity)">
{{ alarmSeverityTranslationMap.get(severity) | translate }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input matInput
type="text"
placeholder="{{ !alarmTemplateForm.get('alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}"
style="max-width: 200px;"
#severityInput
(focusin)="onSeverityInputFocus()"
matAutocompleteOrigin
#origin="matAutocompleteOrigin"
(input)="severityInputChange.next(severityInput.value)"
[matAutocompleteConnectedTo]="origin"
[matAutocomplete]="severityAutocomplete"
[matChipInputFor]="severitiesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addSeverityFromChipInput($event)">
</mat-chip-list>
<mat-autocomplete #severityAutocomplete="matAutocomplete"
class="tb-autocomplete"
(optionSelected)="severitySelected($event)"
[displayWith]="displaySeverityFn.bind(this)">
<mat-option *ngFor="let severity of filteredDisplaySeverities | async" [value]="severity">
<span [innerHTML]="alarmSeverityTranslationMap.get(alarmSeverityEnum[severity]) | translate | highlight:severitySearchText"></span>
</mat-option>
<mat-option *ngIf="(filteredDisplaySeverities | async)?.length === 0" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(severitySearchText); else searchNotEmpty">
<span translate>notification.no-severity-found</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('notification.no-severity-matching',
{severity: truncate.transform(severitySearchText, true, 6, &apos;...&apos;)}) | async }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</fieldset>
<fieldset class="fields-group" formGroupName="clearRule"> <fieldset class="fields-group" formGroupName="clearRule">
<legend translate>notification.clear-rule</legend> <legend translate>notification.clear-rule</legend>
<mat-form-field fxFlex class="mat-block" floatLabel="always"> <mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label> <mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="alarmStatus" <mat-select formControlName="alarmStatus"
placeholder="{{ !alarmTemplateForm.get('clearRule.alarmStatus').value?.length ? ('alarm.any-status' | translate) : '' }}"> placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value ? ('alarm.any-status' | translate) : '' }}">
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus"> <mat-option [value]="null">{{ 'alarm.any-status' | translate }}</mat-option>
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} <mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
</mat-option> {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
</fieldset> </mat-form-field>
</fieldset>
</section>
<fieldset class="fields-group"> <fieldset class="fields-group" formGroupName="recipientsConfig">
<legend translate>notification.hierarchy-of-receiving</legend> <legend translate>notification.hierarchy-of-receiving</legend>
<tb-escalations-component formControlName="escalationTable"></tb-escalations-component> <tb-escalations-component formControlName="escalationTable"></tb-escalations-component>
</fieldset> </fieldset>
</form>
<mat-form-field class="mat-block"> <form [formGroup]="ruleNotificationForm">
<mat-label translate>notification.description</mat-label> <section formGroupName="additionalConfig">
<input matInput formControlName="description"> <mat-form-field class="mat-block">
</mat-form-field> <mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form> </form>
</mat-step> </mat-step>
@ -162,42 +114,51 @@
[stepControl]="deviceInactivityTemplateForm"> [stepControl]="deviceInactivityTemplateForm">
<ng-template matStepLabel>{{ 'notification.device-inactivity-trigger-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.device-inactivity-trigger-settings' | translate }}</ng-template>
<form [formGroup]="deviceInactivityTemplateForm"> <form [formGroup]="deviceInactivityTemplateForm">
<span translate>notification.filter-by</span> <section formGroupName="triggerConfig">
<div fxFlex fxLayoutAlign="center center"> <span translate>notification.filter-by</span>
<mat-button-toggle-group class="tb-notification-unread-toggle-group" <div fxFlex fxLayoutAlign="center center">
style="width: 250px;" <mat-button-toggle-group class="tb-notification-unread-toggle-group"
formControlName="filterByDevice"> style="width: 250px;"
<mat-button-toggle fxFlex [value]=true>{{ 'notification.device' | translate }}</mat-button-toggle> formControlName="filterByDevice">
<mat-button-toggle fxFlex [value]=false>{{ 'notification.device-profile' | translate }}</mat-button-toggle> <mat-button-toggle fxFlex [value]=true>{{ 'notification.device' | translate }}</mat-button-toggle>
</mat-button-toggle-group> <mat-button-toggle fxFlex [value]=false>{{ 'notification.device-profile' | translate }}</mat-button-toggle>
</div> </mat-button-toggle-group>
<tb-entity-list </div>
*ngIf="deviceInactivityTemplateForm.get('filterByDevice').value; else deviceProfile" <ng-container *ngIf="deviceInactivityTemplateForm.get('triggerConfig.filterByDevice').value; else deviceProfile">
required <tb-entity-list
formControlName="devices" formControlName="devices"
labelText="{{ 'notification.devices' | translate }}" labelText="{{ 'notification.devices' | translate }}"
placeholderText="{{ 'notification.device' | translate }}" placeholderText="{{ 'notification.devices' | translate }}"
[entityType]="entityType.DEVICE"> [entityType]="entityType.DEVICE">
</tb-entity-list> </tb-entity-list>
<ng-template #deviceProfile> <div class="tb-hint" translate>notification.device-list-rule-hint</div>
</ng-container>
<ng-template #deviceProfile>
<tb-entity-list
formControlName="deviceProfiles"
labelText="{{ 'notification.device-profiles' | translate }}"
placeholderText="{{ 'notification.device-profiles' | translate }}"
[entityType]="entityType.DEVICE_PROFILE">
</tb-entity-list>
<div class="tb-hint" translate>notification.device-profiles-list-rule-hint</div>
</ng-template>
</section>
<section formGroupName="recipientsConfig">
<tb-entity-list <tb-entity-list
required required
formControlName="deviceProfiles" formControlName="targets"
labelText="{{ 'notification.device-profiles' | translate }}" [entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.device-profile' | translate }}" placeholderText="{{ 'notification.target' | translate }}">
[entityType]="entityType.DEVICE_PROFILE">
</tb-entity-list> </tb-entity-list>
</ng-template> </section>
<tb-entity-list </form>
required <form [formGroup]="ruleNotificationForm">
formControlName="targets" <section formGroupName="additionalConfig">
[entityType]="entityType.NOTIFICATION_TARGET" <mat-form-field class="mat-block">
placeholderText="{{ 'notification.target' | translate }}"> <mat-label translate>notification.description</mat-label>
</tb-entity-list> <input matInput formControlName="description">
<mat-form-field class="mat-block"> </mat-form-field>
<mat-label translate>notification.description</mat-label> </section>
<input matInput formControlName="description">
</mat-form-field>
</form> </form>
</mat-step> </mat-step>
@ -205,7 +166,7 @@
[stepControl]="entityActionTemplateForm"> [stepControl]="entityActionTemplateForm">
<ng-template matStepLabel>{{ 'notification.entity-action-trigger-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.entity-action-trigger-settings' | translate }}</ng-template>
<form [formGroup]="entityActionTemplateForm"> <form [formGroup]="entityActionTemplateForm">
<fieldset class="fields-group"> <fieldset class="fields-group" formGroupName="triggerConfig">
<legend translate>notification.filter</legend> <legend translate>notification.filter</legend>
<tb-entity-type-select required <tb-entity-type-select required
showLabel showLabel
@ -219,16 +180,22 @@
<mat-slide-toggle formControlName="deleted">{{ 'notification.deleted' | translate }}</mat-slide-toggle> <mat-slide-toggle formControlName="deleted">{{ 'notification.deleted' | translate }}</mat-slide-toggle>
</section> </section>
</fieldset> </fieldset>
<tb-entity-list <section formGroupName="recipientsConfig">
required <tb-entity-list
formControlName="targets" required
[entityType]="entityType.NOTIFICATION_TARGET" formControlName="targets"
placeholderText="notification.target"> [entityType]="entityType.NOTIFICATION_TARGET"
</tb-entity-list> placeholderText="notification.target">
<mat-form-field class="mat-block"> </tb-entity-list>
<mat-label translate>notification.description</mat-label> </section>
<input matInput formControlName="description"> </form>
</mat-form-field> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form> </form>
</mat-step> </mat-step>
</mat-horizontal-stepper> </mat-horizontal-stepper>

31
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss

@ -24,6 +24,24 @@
} }
} }
.tb-hint {
margin-top: -1.25em;
padding: 0;
}
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
margin-bottom: 8px;
color: rgba(0, 0, 0, .7);
width: fit-content;
}
}
::ng-deep { ::ng-deep {
.mat-dialog-content { .mat-dialog-content {
@ -119,17 +137,4 @@
} }
} }
} }
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
margin-bottom: 8px;
color: rgba(0, 0, 0, .7);
width: fit-content;
}
}
} }

299
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts

@ -15,7 +15,7 @@
/// ///
import { NotificationRule, TriggerType, TriggerTypeTranslationMap } from '@shared/models/notification.models'; import { NotificationRule, TriggerType, TriggerTypeTranslationMap } from '@shared/models/notification.models';
import { Component, ElementRef, Inject, OnDestroy, ViewChild } from '@angular/core'; import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -25,23 +25,14 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, deepTrim, isDefined } from '@core/utils'; import { deepClone, deepTrim, isDefined } from '@core/utils';
import { Observable, of, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { map, mergeMap, share, startWith, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper'; import { MatStepper } from '@angular/material/stepper';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; import { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import {
AlarmSeverity,
alarmSeverityTranslations,
AlarmStatus,
alarmStatusTranslations
} from '@shared/models/alarm.models';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
export interface RuleNotificationDialogData { export interface RuleNotificationDialogData {
rule?: NotificationRule; rule?: NotificationRule;
@ -59,12 +50,6 @@ export class RuleNotificationDialogComponent extends
@ViewChild('addNotificationRule', {static: true}) addNotificationRule: MatStepper; @ViewChild('addNotificationRule', {static: true}) addNotificationRule: MatStepper;
@ViewChild('severitiesChipList') severitiesChipList: MatChipList;
@ViewChild('severityAutocomplete') severityAutocomplete: MatAutocomplete;
@ViewChild('severityInput') severityInput: ElementRef<HTMLInputElement>;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
stepperOrientation: Observable<StepperOrientation>; stepperOrientation: Observable<StepperOrientation>;
ruleNotificationForm: FormGroup; ruleNotificationForm: FormGroup;
@ -76,10 +61,6 @@ export class RuleNotificationDialogComponent extends
triggerTypes: TriggerType[] = Object.values(TriggerType); triggerTypes: TriggerType[] = Object.values(TriggerType);
triggerTypeTranslationMap = TriggerTypeTranslationMap; triggerTypeTranslationMap = TriggerTypeTranslationMap;
alarmSeverities = Object.keys(AlarmSeverity);
alarmSeverityEnum = AlarmSeverity;
alarmSeverityTranslationMap = alarmSeverityTranslations;
alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus); alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus);
alarmSearchStatusTranslationMap = alarmStatusTranslations; alarmSearchStatusTranslationMap = alarmStatusTranslations;
@ -89,17 +70,14 @@ export class RuleNotificationDialogComponent extends
selectedIndex = 0; selectedIndex = 0;
filteredDisplaySeverities: Observable<Array<string>>;
severitySearchText = '';
dialogTitle = 'notification.edit-rule'; dialogTitle = 'notification.edit-rule';
severityInputChange = new Subject<string>();
private destroy$ = new Subject(); private destroy$ = new Subject();
private readonly ruleNotification: NotificationRule; private readonly ruleNotification: NotificationRule;
private triggerTypeFormsMap: Map<TriggerType, FormGroup>;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
protected dialogRef: MatDialogRef<RuleNotificationDialogComponent, NotificationRule>, protected dialogRef: MatDialogRef<RuleNotificationDialogComponent, NotificationRule>,
@ -107,7 +85,6 @@ export class RuleNotificationDialogComponent extends
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private fb: FormBuilder, private fb: FormBuilder,
public translate: TranslateService, public translate: TranslateService,
public truncate: TruncatePipe,
private notificationService: NotificationService) { private notificationService: NotificationService) {
super(store, router, dialogRef); super(store, router, dialogRef);
@ -122,72 +99,66 @@ export class RuleNotificationDialogComponent extends
name: [null, Validators.required], name: [null, Validators.required],
templateId: [null, Validators.required], templateId: [null, Validators.required],
triggerType: [TriggerType.ALARM, Validators.required], triggerType: [TriggerType.ALARM, Validators.required],
recipientsConfig: this.fb.group({ recipientsConfig: [null],
triggerType: [], triggerConfig: [null],
}),
triggerConfig: this.fb.group({
triggerType: []
}),
additionalConfig: this.fb.group({ additionalConfig: this.fb.group({
description: [] description: ['']
}) })
}); });
this.ruleNotificationForm.get('triggerType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
value => {
this.ruleNotificationForm.get('triggerConfig').patchValue({triggerType: value}, {emitEvent: false});
this.ruleNotificationForm.get('recipientsConfig').patchValue({triggerType: value}, {emitEvent: false});
}
);
this.alarmTemplateForm = this.fb.group({ this.alarmTemplateForm = this.fb.group({
alarmTypes: [null], triggerConfig: this.fb.group({
alarmSeverities: [null, Validators.required], alarmTypes: [null],
clearRule: this.fb.group({ alarmSeverities: [null, Validators.required],
alarmStatus: [] clearRule: this.fb.group({
alarmStatus: [null]
})
}), }),
escalationTable: [], recipientsConfig: this.fb.group({
description: [''] escalationTable: []
})
}); });
this.deviceInactivityTemplateForm = this.fb.group({ this.deviceInactivityTemplateForm = this.fb.group({
filterByDevice: [true], triggerConfig: this.fb.group({
devices: [null, Validators.required], filterByDevice: [true],
deviceProfiles: [{value: null, disabled: true}, Validators.required], devices: [null],
targets: [[], Validators.required], deviceProfiles: [{value: null, disabled: true}]
description: [''] }),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
})
}); });
this.deviceInactivityTemplateForm.get('filterByDevice').valueChanges.pipe( this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice').valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe(value => { ).subscribe(value => {
if (value) { if (value) {
this.deviceInactivityTemplateForm.get('devices').enable({emitEvent: false}); this.deviceInactivityTemplateForm.get('triggerConfig.devices').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('deviceProfiles').disable({emitEvent: false}); this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').disable({emitEvent: false});
} else { } else {
this.deviceInactivityTemplateForm.get('deviceProfiles').enable({emitEvent: false}); this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('devices').disable({emitEvent: false}); this.deviceInactivityTemplateForm.get('triggerConfig.devices').disable({emitEvent: false});
} }
}); });
this.entityActionTemplateForm = this.fb.group({ this.entityActionTemplateForm = this.fb.group({
entityType: [EntityType.DEVICE], triggerConfig: this.fb.group({
created: [false], entityType: [EntityType.DEVICE],
updated: [false], created: [false],
deleted: [false], updated: [false],
targets: [[], Validators.required], deleted: [false]
description: [''] }),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
})
}); });
this.filteredDisplaySeverities = this.severityInputChange this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
.pipe( [TriggerType.ALARM, this.alarmTemplateForm],
startWith(''), [TriggerType.DEVICE_INACTIVITY, this.deviceInactivityTemplateForm],
map((value) => value ? value : ''), [TriggerType.ENTITY_ACTION, this.entityActionTemplateForm]
mergeMap(name => this.fetchSeverities(name) ), ]);
share()
);
if (data.isAdd || data.isCopy) { if (data.isAdd || data.isCopy) {
this.dialogTitle = 'notification.add-rule'; this.dialogTitle = 'notification.add-rule';
@ -200,145 +171,15 @@ export class RuleNotificationDialogComponent extends
} }
this.ruleNotificationForm.reset({}, {emitEvent: false}); this.ruleNotificationForm.reset({}, {emitEvent: false});
this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false}); this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false});
if (this.ruleNotification.triggerType === TriggerType.ALARM) { const currentForm = this.triggerTypeFormsMap.get(this.ruleNotification.triggerType);
const parsedEscalationTable = []; currentForm.patchValue(this.ruleNotification, {emitEvent: false});
const escalationTable = this.ruleNotification.recipientsConfig.escalationTable; if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) {
for (const escalation in escalationTable) { this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice')
parsedEscalationTable.push({delayInSec: escalation, targets: escalationTable[escalation]}); .patchValue(!!this.ruleNotification.triggerConfig.devices, {onlySelf: true});
}
this.alarmTemplateForm.patchValue({
escalationTable: parsedEscalationTable,
description: this.ruleNotification.additionalConfig.description,
...this.ruleNotification.triggerConfig
}, {emitEvent: false});
} else if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) {
this.deviceInactivityTemplateForm.patchValue({
filterByDevice: !!this.ruleNotification.triggerConfig.devices,
deviceProfiles: this.ruleNotification.triggerConfig.deviceProfiles,
devices: this.ruleNotification.triggerConfig.devices,
targets: this.ruleNotification.recipientsConfig.targets,
description: this.ruleNotification.additionalConfig.description
}, {emitEvent: false});
this.deviceInactivityTemplateForm.get('filterByDevice').updateValueAndValidity({onlySelf: true});
} else {
this.entityActionTemplateForm.patchValue({
targets: this.ruleNotification.recipientsConfig.targets,
description: this.ruleNotification.additionalConfig.description,
...this.ruleNotification.triggerConfig
}, {emitEvent: false});
}
}
}
onSeverityRemoved(severity: string): void {
const severities: string[] = this.alarmTemplateForm.get('alarmSeverities').value;
const index = severities.indexOf(severity);
if (index > -1) {
severities.splice(index, 1);
this.alarmTemplateForm.get('alarmSeverities').setValue(severities);
this.alarmTemplateForm.get('alarmSeverities').markAsDirty();
this.severitiesChipList.errorState = !severities.length;
}
}
onSeverityInputFocus() {
this.severityInputChange.next(this.severityInput.nativeElement.value);
}
addSeverityFromChipInput(event: MatChipInputEvent): void {
const value = event.value;
if ((value || '').trim()) {
const severityName = value.trim().toUpperCase();
const existingSeverity = this.alarmSeverities.find(severity => severity.toUpperCase() === severityName);
if (this.addSeverity(existingSeverity)) {
this.clearSeverityInput('');
} }
} }
} }
private addSeverity(existingSeverity: string): boolean {
if (existingSeverity) {
const displaySeverities: string[] = this.alarmTemplateForm.get('alarmSeverities').value;
const index = displaySeverities.indexOf(existingSeverity);
if (index === -1) {
displaySeverities.push(existingSeverity);
this.alarmTemplateForm.get('alarmSeverities').setValue(displaySeverities);
this.alarmTemplateForm.get('alarmSeverities').markAsDirty();
this.severitiesChipList.errorState = false;
return true;
}
}
return false;
}
clearSeverityInput(value: string = '') {
this.severityInput.nativeElement.value = value;
this.severityInputChange.next(null);
setTimeout(() => {
this.severityInput.nativeElement.blur();
this.severityInput.nativeElement.focus();
}, 0);
}
severitySelected(event: MatAutocompleteSelectedEvent): void {
this.addSeverity(event.option.value);
this.clearSeverityInput('');
}
displaySeverityFn(severity?: string): string | undefined {
return severity ? this.translate.instant(this.alarmSeverityTranslationMap.get(this.alarmSeverityEnum[severity])) : undefined;
}
private fetchSeverities(searchText?: string): Observable<Array<string>> {
this.severitySearchText = searchText;
if (this.severitySearchText && this.severitySearchText.length) {
const search = this.severitySearchText.toUpperCase();
return of(this.alarmSeverities.filter(severity => severity.toUpperCase().includes(search)));
} else {
return of(this.alarmSeverities);
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
public alarmTypeList(): string[] {
return this.alarmTemplateForm.get('alarmTypes').value;
}
public removeAlarmType(type: string): void {
const types: string[] = this.alarmTemplateForm.get('alarmTypes').value;
const index = types.indexOf(type);
if (index >= 0) {
types.splice(index, 1);
this.alarmTemplateForm.get('alarmTypes').setValue(types);
this.alarmTemplateForm.get('alarmTypes').markAsDirty();
}
}
public addAlarmType(event: MatChipInputEvent): void {
const input = event.chipInput.inputElement;
let value = event.value || '';
if (value.trim()) {
value = value.trim();
let types: string[] = this.alarmTemplateForm.get('alarmTypes').value;
if (!types || types.indexOf(value) === -1) {
if (!types) {
types = [];
}
types.push(value);
this.alarmTemplateForm.get('alarmTypes').setValue(types);
this.alarmTemplateForm.get('alarmTypes').markAsDirty();
}
}
if (input) {
input.value = '';
}
}
changeStep($event: StepperSelectionEvent) { changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex; this.selectedIndex = $event.selectedIndex;
} }
@ -356,16 +197,7 @@ export class RuleNotificationDialogComponent extends
} }
nextStepLabel(): string { nextStepLabel(): string {
if (this.selectedIndex === 1 && this.selectedIndex < this.maxStepperIndex && this.alarmTemplateForm.pristine) { if (this.selectedIndex !== 0) {
return 'action.skip';
}
if (this.selectedIndex === 2 && this.selectedIndex < this.maxStepperIndex && this.deviceInactivityTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 3 && this.selectedIndex < this.maxStepperIndex && this.entityActionTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex !== 0 && this.selectedIndex >= this.maxStepperIndex) {
return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save'; return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save';
} }
return 'action.next'; return 'action.next';
@ -378,30 +210,15 @@ export class RuleNotificationDialogComponent extends
private add(): void { private add(): void {
if (this.allValid()) { if (this.allValid()) {
let formValue = this.ruleNotificationForm.value; let formValue = this.ruleNotificationForm.value;
const triggerType = this.ruleNotificationForm.get('triggerType').value; const triggerType: TriggerType = this.ruleNotificationForm.get('triggerType').value;
if (triggerType === TriggerType.ALARM) { const currentForm = this.triggerTypeFormsMap.get(triggerType);
Object.assign(formValue.triggerConfig, this.alarmTemplateForm.value); Object.assign(formValue, currentForm.value);
const parsedEscalationTable = {}; if (triggerType === TriggerType.DEVICE_INACTIVITY) {
this.alarmTemplateForm.get('escalationTable').value.forEach(
escalation => parsedEscalationTable[escalation.delayInSec] = escalation.targets
);
formValue.recipientsConfig.escalationTable = parsedEscalationTable;
formValue.additionalConfig.description = this.alarmTemplateForm.get('description').value;
delete formValue.triggerConfig.escalationTable;
} else if (triggerType === TriggerType.DEVICE_INACTIVITY) {
Object.assign(formValue.triggerConfig, this.deviceInactivityTemplateForm.value);
formValue.recipientsConfig.targets = this.deviceInactivityTemplateForm.get('targets').value;
formValue.additionalConfig.description = this.deviceInactivityTemplateForm.get('description').value;
delete formValue.triggerConfig.filterByDevice; delete formValue.triggerConfig.filterByDevice;
} else {
Object.assign(formValue.triggerConfig, this.entityActionTemplateForm.value);
formValue.recipientsConfig.targets = this.entityActionTemplateForm.get('targets').value;
formValue.additionalConfig.description = this.entityActionTemplateForm.get('description').value;
}
if (triggerType === TriggerType.DEVICE_INACTIVITY || triggerType === TriggerType.ENTITY_ACTION) {
delete formValue.triggerConfig.trigger;
} }
if (this.ruleNotification) { formValue.recipientsConfig.triggerType = triggerType;
formValue.triggerConfig.triggerType = triggerType;
if (this.ruleNotification && !this.data.isCopy) {
formValue = {...this.ruleNotification, ...formValue}; formValue = {...this.ruleNotification, ...formValue};
} }
this.notificationService.saveNotificationRule(deepTrim(formValue)).subscribe( this.notificationService.saveNotificationRule(deepTrim(formValue)).subscribe(

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts

@ -215,5 +215,7 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
private reset() { private reset() {
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false}); this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false});
this.updateView(null);
this.dirty = true;
} }
} }

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts

@ -243,7 +243,7 @@ export class TemplateNotificationDialogComponent
delete template.configuration.deliveryMethodsTemplates[method]; delete template.configuration.deliveryMethodsTemplates[method];
} }
}); });
if (this.templateNotification) { if (this.templateNotification && !this.data.isCopy) {
template = {...this.templateNotification, ...template}; template = {...this.templateNotification, ...template};
} }
this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe( this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe(

4
ui-ngx/src/app/shared/models/notification.models.ts

@ -118,12 +118,12 @@ export interface NotificationRuleTriggerConfig {
export interface NotificationRuleRecipientConfig { export interface NotificationRuleRecipientConfig {
targets?: Array<string>; targets?: Array<string>;
escalationTable?: {[key: string]: Array<string>}; escalationTable?: {[key: number]: Array<string>};
} }
export interface NonConfirmedNotificationEscalation { export interface NonConfirmedNotificationEscalation {
delayInSec: number; delayInSec: number;
notificationTargetId: NotificationTargetId; targets: Array<string>;
} }
export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{ export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{

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

@ -2687,29 +2687,34 @@
"action-button": "Action button", "action-button": "Action button",
"add-notification-target": "Add notification recipient", "add-notification-target": "Add notification recipient",
"add-notification-template": "Add notification template", "add-notification-template": "Add notification template",
"add-rule": "Add rule",
"add-target": "Add recipient", "add-target": "Add recipient",
"add-template": "Add template", "add-template": "Add template",
"at-least-one-should-be-selected": "At least one should be selected", "alarm-trigger-settings": "Alarm trigger settings",
"all": "All", "all": "All",
"at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings", "basic-settings": "Basic settings",
"button-text": "Button text", "button-text": "Button text",
"button-text-required": "Button text is required", "button-text-required": "Button text is required",
"clear-rule": "Clear rule",
"compose": "Compose", "compose": "Compose",
"conversation": "Conversation", "conversation": "Conversation",
"conversation-required": "Conversation is required", "conversation-required": "Conversation is required",
"add-rule": "Add rule",
"edit-rule": "Edit rule",
"copy-rule": "Copy rule",
"copy-notification-template": "Copy notification template", "copy-notification-template": "Copy notification template",
"copy-rule": "Copy rule",
"copy-template": "Copy template", "copy-template": "Copy template",
"create-target": "Create recipient", "create-target": "Create recipient",
"created": "Created",
"created-time": "Created time", "created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.", "delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",
"delete-request-title": "Are you sure you want to delete the notification request?", "delete-request-title": "Are you sure you want to delete the notification request?",
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"delete-target-text": "Be careful, after the confirmation the notification recipient will become unrecoverable.", "delete-target-text": "Be careful, after the confirmation the notification recipient will become unrecoverable.",
"delete-target-title": "Are you sure you want to delete the notification recipient '{{targetName}}'?", "delete-target-title": "Are you sure you want to delete the notification recipient '{{targetName}}'?",
"delete-template-text": "Be careful, after the confirmation the notification template will become unrecoverable.", "delete-template-text": "Be careful, after the confirmation the notification template will become unrecoverable.",
"delete-template-title": "Are you sure you want to delete the notification template '{{templateName}}'?", "delete-template-title": "Are you sure you want to delete the notification template '{{templateName}}'?",
"deleted": "Deleted",
"delivery-method": "Delivery method", "delivery-method": "Delivery method",
"delivery-method-type": { "delivery-method-type": {
"email": "Email", "email": "Email",
@ -2717,14 +2722,26 @@
"slack": "Slack", "slack": "Slack",
"sms": "SMS" "sms": "SMS"
}, },
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"delivery-methods": "Delivery methods", "delivery-methods": "Delivery methods",
"description": "Description", "description": "Description",
"device": "Device",
"device-inactivity-trigger-settings": "Device inactive trigger settings",
"device-list-rule-hint": "If the field is empty, the trigger will be applied to all devices",
"device-profile": "Device profile",
"device-profiles": "Device profiles",
"device-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all device profiles",
"devices": "Devices",
"edit-notification-target": "Edit notification recipient", "edit-notification-target": "Edit notification recipient",
"edit-notification-template": "Edit notification template", "edit-notification-template": "Edit notification template",
"edit-rule": "Edit rule",
"edit-template": "Edit template", "edit-template": "Edit template",
"email-settings": "Email settings", "email-settings": "Email settings",
"entity-action-trigger-settings": "Entity action trigger settings",
"entity-type": "Entity type",
"filter": "Filter",
"filter-by": "Filter by",
"first-recipient": "First recipient",
"hierarchy-of-receiving": "Hierarchy of receiving notifications",
"inbox": "Inbox", "inbox": "Inbox",
"link": "Link", "link": "Link",
"link-required": "Link is required", "link-required": "Link is required",
@ -2738,12 +2755,19 @@
"no-notification-request": "No notification request", "no-notification-request": "No notification request",
"no-notification-templates": "Not found notification templates", "no-notification-templates": "Not found notification templates",
"no-notifications-yet": "No notifications yet", "no-notifications-yet": "No notifications yet",
"no-rule": "No rule configured",
"no-rules-notification": "No rules notification",
"no-severity-found": "No severity found",
"no-severity-matching": "'{{severity}}' not found.",
"no-targets-notification": "No recipients notification", "no-targets-notification": "No recipients notification",
"not-found-slack-recipient": "Not found slack recipient", "not-found-slack-recipient": "Not found slack recipient",
"notification": "Notification", "notification": "Notification",
"notification-center": "Notification center", "notification-center": "Notification center",
"notification-target": "Notification recipient", "notification-target": "Notification recipient",
"notify": "notify",
"notify-again": "Notify again", "notify-again": "Notify again",
"platform-users": "Platform users",
"recipient": "Recipient",
"recipient-type": "Recipient type", "recipient-type": "Recipient type",
"recipients-count": "{ count, plural, 1 {1 Recipient} other {# Recipients} }", "recipients-count": "{ count, plural, 1 {1 Recipient} other {# Recipients} }",
"request-status": { "request-status": {
@ -2752,15 +2776,15 @@
"sent": "Sent" "sent": "Sent"
}, },
"review": "Review", "review": "Review",
"no-rules-notification": "No rules notification",
"rule": "Rule", "rule": "Rule",
"rule-name": "Rule name",
"rule-name-required": "Name is required",
"rules": "Rules", "rules": "Rules",
"platform-users": "Platform users",
"scheduler-later": "Schedule for later", "scheduler-later": "Schedule for later",
"search-notification": "Search notification", "search-notification": "Search notification",
"search-rules": "Search rules",
"search-targets": "Search recipients", "search-targets": "Search recipients",
"search-templates": "Search templates", "search-templates": "Search templates",
"search-rules": "Search rules",
"send-notification": "Send notification", "send-notification": "Send notification",
"send-test-notification": "Send test notification", "send-test-notification": "Send test notification",
"sent": "Sent", "sent": "Sent",
@ -2783,64 +2807,42 @@
"target-type": { "target-type": {
"all-users": "All users", "all-users": "All users",
"customer-users": "Customer users", "customer-users": "Customer users",
"originator-entity-owner-users": "Users of originator entity owner",
"user-filters": "User filter", "user-filters": "User filter",
"user-list": "User list", "user-list": "User list"
"originator-entity-owner-users": "Users of originator entity owner"
}, },
"targets": "Recipients", "targets": "Recipients",
"targets-required": "Recipients is required", "targets-required": "Recipients is required",
"recipient": "Recipient",
"template": "Template", "template": "Template",
"template-hint": {
"alarm": "Available params: ${alarmType}, ${alarmSeverity}, ${alarmStatus}, ${alarmOriginatorEntityType}, ${alarmOriginatorId}, ${alarmId}",
"device-inactivity": "Available params: ${deviceName}, ${deviceType}, ${deviceId}",
"entity-action": "Available params: ${actionType}, ${entityType}, ${entityName}, ${entityId}, ${originatorUserName}, ${originatorUserId}",
"general": "Available params: ${recipientEmail}, ${recipientFirstName}, ${recipientLastName}"
},
"template-name": "Template name",
"template-required": "Template is required", "template-required": "Template is required",
"template-type": { "template-type": {
"alarm": "Alarm", "alarm": "Alarm",
"general": "General",
"device-inactivity": "Device inactivity", "device-inactivity": "Device inactivity",
"entity-action": "Entity action" "entity-action": "Entity action",
}, "general": "General"
"template-hint": {
"general": "Available params: ${recipientEmail}, ${recipientFirstName}, ${recipientLastName}",
"alarm": "Available params: ${alarmType}, ${alarmSeverity}, ${alarmStatus}, ${alarmOriginatorEntityType}, ${alarmOriginatorId}, ${alarmId}",
"device-inactivity": "Available params: ${deviceName}, ${deviceType}, ${deviceId}",
"entity-action": "Available params: ${actionType}, ${entityType}, ${entityName}, ${entityId}, ${originatorUserName}, ${originatorUserId}"
}, },
"templates": "Templates", "templates": "Templates",
"text": "Text", "text": "Text",
"time": "Time", "time": "Time",
"type": "Type",
"unread": "Unread",
"view-all": "View all",
"web-settings": "Web settings",
"rule-name": "Rule name",
"rule-name-required": "Name is required",
"template-name": "Template name",
"hierarchy-of-receiving": "Hierarchy of receiving notifications",
"trigger": { "trigger": {
"trigger": "Trigger",
"trigger-required": "Trigger is required",
"alarm": "Alarm", "alarm": "Alarm",
"device-inactivity": "Device inactivity", "device-inactivity": "Device inactivity",
"entity-action": "Entity action" "entity-action": "Entity action",
"trigger": "Trigger",
"trigger-required": "Trigger is required"
}, },
"alarm-trigger-settings": "Alarm trigger settings", "type": "Type",
"device-inactivity-trigger-settings": "Device inactive trigger settings", "unread": "Unread",
"entity-action-trigger-settings": "Entity action trigger settings",
"filter": "Filter",
"clear-rule": "Clear rule",
"no-severity-matching": "'{{severity}}' not found.",
"no-severity-found": "No severity found",
"first-recipient": "First recipient",
"notify": "notify",
"no-rule": "No rule configured",
"device": "Device",
"devices": "Devices",
"device-profile": "Device profile",
"device-profiles": "Device profiles",
"filter-by": "Filter by",
"entity-type": "Entity type",
"created": "Created",
"updated": "Updated", "updated": "Updated",
"deleted": "Deleted" "view-all": "View all",
"web-settings": "Web settings"
}, },
"ota-update": { "ota-update": {
"add": "Add package", "add": "Add package",

Loading…
Cancel
Save