Browse Source

UI: Change notification style and send notification error

pull/8212/head
Vladyslav_Prykhodko 4 years ago
parent
commit
75e0f335b9
  1. 2
      ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts
  2. 21
      ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html
  3. 21
      ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss
  4. 50
      ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts
  5. 10
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts
  6. 29
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts
  7. 1
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts
  8. 42
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts
  9. 40
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.html
  10. 48
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.scss
  11. 57
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.ts
  12. 6
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html
  13. 63
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html
  14. 220
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts
  15. 47
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  16. 31
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts
  17. 6
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts
  18. 6
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html
  19. 5
      ui-ngx/src/app/shared/components/notification/notification.component.html
  20. 11
      ui-ngx/src/app/shared/components/notification/notification.component.ts
  21. 12
      ui-ngx/src/app/shared/models/notification.models.ts
  22. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts

@ -56,7 +56,7 @@ export class ShowNotificationPopoverComponent extends PageComponent implements O
} }
ngOnInit() { ngOnInit() {
this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone); this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone, 6);
this.notifications$ = this.notificationSubscriber.notifications$.pipe( this.notifications$ = this.notificationSubscriber.notifications$.pipe(
share({ share({
connector: () => new ReplaySubject(1) connector: () => new ReplaySubject(1)

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

@ -0,0 +1,21 @@
<!--
Copyright © 2016-2023 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.
-->
<tb-notification
[notification]="notification"
(markAsRead)="markAsRead($event)"
></tb-notification>

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

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2023 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.
*/
:host{
display: block;
max-width: 400px;
width: 350px;
padding: 2px;
}

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

@ -0,0 +1,50 @@
///
/// Copyright © 2016-2023 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, Inject } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Notification } from '@shared/models/notification.models';
export interface InboxNotificationDialogData {
notification: Notification;
}
@Component({
selector: 'tb-inbox-notification-dialog',
templateUrl: './inbox-notification-dialog.component.html',
styleUrls: ['inbox-notification-dialog.component.scss']
})
export class InboxNotificationDialogComponent extends DialogComponent<InboxNotificationDialogComponent, string> {
notification: Notification;
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<InboxNotificationDialogComponent, string>,
@Inject(MAT_DIALOG_DATA) public data: InboxNotificationDialogData) {
super(store, router, dialogRef);
this.notification = data.notification;
}
markAsRead(id: string) {
this.dialogRef.close(id)
}
}

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

@ -49,8 +49,11 @@ import {
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 { import {
AlarmSeveritiesListComponent InboxNotificationDialogComponent
} from '@home/pages/notification-center/rule-table/alarm-severities-list.component'; } from '@home/pages/notification-center/inbox-table/inbox-notification-dialog.component';
import {
NotificationRequestErrorDialogComponent
} from '@home/pages/notification-center/request-table/notification-request-error-dialog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -67,7 +70,8 @@ import {
RuleNotificationDialogComponent, RuleNotificationDialogComponent,
EscalationsComponent, EscalationsComponent,
EscalationFormComponent, EscalationFormComponent,
AlarmSeveritiesListComponent InboxNotificationDialogComponent,
NotificationRequestErrorDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

29
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts

@ -32,14 +32,20 @@ import { NotificationService } from '@core/http/notification.service';
import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component'; import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import {
InboxNotificationDialogComponent, InboxNotificationDialogData
} from '@home/pages/notification-center/inbox-table/inbox-notification-dialog.component';
export class InboxTableConfig extends EntityTableConfig<Notification> { export class InboxTableConfig extends EntityTableConfig<Notification> {
constructor(private notificationService: NotificationService, constructor(private notificationService: NotificationService,
private translate: TranslateService, private translate: TranslateService,
private dialog: MatDialog,
private datePipe: DatePipe) { private datePipe: DatePipe) {
super(); super();
this.entitiesDeleteEnabled = false; this.entitiesDeleteEnabled = false;
this.rowPointer = true;
this.entityTranslations = { this.entityTranslations = {
noEntities: 'notification.no-inbox-notification', noEntities: 'notification.no-inbox-notification',
search: 'notification.search-notification' search: 'notification.search-notification'
@ -50,6 +56,11 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.handleRowClick = ($event, notification) => {
this.showNotification($event, notification)
return true;
}
this.componentsData = { this.componentsData = {
unreadOnly: true unreadOnly: true
}; };
@ -117,4 +128,22 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
} }
}); });
} }
private showNotification($event: Event, notification: Notification) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<InboxNotificationDialogComponent, InboxNotificationDialogData,
string>(InboxNotificationDialogComponent, {
disableClose: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
notification
}
}).afterClosed().subscribe(res => {
if (res) {
this.markAsRead(null, notification);
}
});
}
} }

1
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts

@ -71,6 +71,7 @@ export class NotificationTableComponent implements OnInit {
return new InboxTableConfig( return new InboxTableConfig(
this.notificationService, this.notificationService,
this.translate, this.translate,
this.dialog,
this.datePipe this.datePipe
); );
case EntityType.NOTIFICATION_TEMPLATE: case EntityType.NOTIFICATION_TEMPLATE:

42
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts

@ -24,6 +24,7 @@ import {
NotificationDeliveryMethodTranslateMap, NotificationDeliveryMethodTranslateMap,
NotificationRequest, NotificationRequest,
NotificationRequestInfo, NotificationRequestInfo,
NotificationRequestStats,
NotificationRequestStatus, NotificationRequestStatus,
NotificationRequestStatusTranslateMap, NotificationRequestStatusTranslateMap,
NotificationTemplate NotificationTemplate
@ -40,6 +41,10 @@ import {
RequestNotificationDialogData RequestNotificationDialogData
} from '@home/pages/notification-center/request-table/request-notification-dialog.componet'; } from '@home/pages/notification-center/request-table/request-notification-dialog.componet';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import {
NotificationRequestErrorDialogComponent,
NotificationRequestErrorDialogData
} from '@home/pages/notification-center/request-table/notification-request-error-dialog.component';
export class RequestTableConfig extends EntityTableConfig<NotificationRequest, PageLink, NotificationRequestInfo> { export class RequestTableConfig extends EntityTableConfig<NotificationRequest, PageLink, NotificationRequestInfo> {
@ -66,12 +71,21 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
this.onEntityAction = action => this.onRequestAction(action); this.onEntityAction = action => this.onRequestAction(action);
this.handleRowClick = (event, entity) => {
const path = (event as any).path || (event.composedPath && event.composedPath());
if ((event.target as HTMLElement).getElementsByClassName('stats').length || (event.target as HTMLElement).className === 'stats') {
this.openStatsErrorDialog(event, entity);
}
return true;
};
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.columns.push( this.columns.push(
new DateEntityTableColumn<NotificationRequestInfo>('createdTime', 'notification.created-time', this.datePipe, '170px'), new DateEntityTableColumn<NotificationRequestInfo>('createdTime', 'notification.created-time', this.datePipe, '170px'),
new EntityTableColumn<NotificationRequestInfo>('status', 'notification.status', '15%', new EntityTableColumn<NotificationRequestInfo>('status', 'notification.status', '15%',
request => this.requestStatus(request.status), request => this.requestStatusStyle(request.status)), request => `<span style="display: flex;">${this.requestStatus(request.status)}${this.requestStats(request.stats)}</span>`,
request => this.requestStatusStyle(request.status)),
new EntityTableColumn<NotificationRequest>('deliveryMethods', 'notification.delivery-method', '15%', new EntityTableColumn<NotificationRequest>('deliveryMethods', 'notification.delivery-method', '15%',
(request) => request.deliveryMethods (request) => request.deliveryMethods
.map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '), .map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '),
@ -136,6 +150,18 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
</div>`; </div>`;
} }
private requestStats(stats: NotificationRequestStats): string {
let countError = 0;
Object.keys(stats.errors).forEach(method => countError += Object.keys(stats.errors[method]).length);
if (countError === 0) {
return ''
}
return `<div style="border-radius: 12px; height: 24px; line-height: 24px; padding: 0 10px; width: max-content; background-color: #D12730;
color: #fff; font-weight: 500; margin-left: 8px" class="stats">
${countError} ${this.translate.instant('notification.fails')} >
</div>`;
}
private requestStatusStyle(status: NotificationRequestStatus): object { private requestStatusStyle(status: NotificationRequestStatus): object {
const styleObj = { const styleObj = {
fontSize: '14px', fontSize: '14px',
@ -152,4 +178,18 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
} }
return styleObj; return styleObj;
} }
private openStatsErrorDialog($event: Event, notificationRequest: NotificationRequest) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<NotificationRequestErrorDialogComponent, NotificationRequestErrorDialogData,
void>(NotificationRequestErrorDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
notificationRequest
}
}).afterClosed().subscribe(() => {});
}
} }

40
ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.html

@ -0,0 +1,40 @@
<!--
Copyright © 2016-2023 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.
-->
<mat-toolbar color="primary">
<h2 translate>notification.failed-send</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<div *ngFor="let errorStat of errorStats | keyvalue; last as isLast">
<h6>{{ notificationDeliveryMethodErrorTranslateMap.get(errorStat.key) | translate }}</h6>
<table class="tb-table-list">
<tr *ngFor="let error of errorStat.value | keyvalue">
<td>
<mat-chip>{{ error.key }}</mat-chip>
</td>
<td class="tb-row-error">{{ error.value }}</td>
</tr>
</table>
<mat-divider *ngIf="!isLast"></mat-divider>
</div>
</div>

48
ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.scss

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2023 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.
*/
:host{
display: block;
width: 600px;
max-width: 100%;
h6 {
font-weight: 500;
font-size: 16px;
line-height: 20px;
margin-top: 0;
}
.tb-table-list {
table-layout: fixed;
width: 100%;
border: hidden;
border-collapse: collapse;
td {
border: 8px solid transparent;
border-collapse: collapse;
font-size: 14px;
}
.tb-row-error {
word-wrap: break-word;
}
}
.mat-divider {
margin: 24px 0;
}
}

57
ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.ts

@ -0,0 +1,57 @@
///
/// Copyright © 2016-2023 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, Inject } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { NotificationDeliveryMethod, NotificationRequest } from '@shared/models/notification.models';
export interface NotificationRequestErrorDialogData {
notificationRequest: NotificationRequest;
}
@Component({
selector: 'tb-notification-request-error-dialog',
templateUrl: './notification-request-error-dialog.component.html',
styleUrls: ['notification-request-error-dialog.component.scss']
})
export class NotificationRequestErrorDialogComponent extends DialogComponent<NotificationRequestErrorDialogComponent, void> {
errorStats: { [key in NotificationDeliveryMethod]: {[key in string]: string}};
notificationDeliveryMethodErrorTranslateMap = new Map<NotificationDeliveryMethod, string>([
[NotificationDeliveryMethod.PUSH, 'notification.delivery-method-type.push-failed-sent'],
[NotificationDeliveryMethod.SMS, 'notification.delivery-method-type.sms-failed-sent'],
[NotificationDeliveryMethod.EMAIL, 'notification.delivery-method-type.email-failed-sent'],
[NotificationDeliveryMethod.SLACK, 'notification.delivery-method-type.slack-failed-sent'],
]);
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<NotificationRequestErrorDialogComponent, void>,
@Inject(MAT_DIALOG_DATA) public data: NotificationRequestErrorDialogData) {
super(store, router, dialogRef);
this.errorStats = data.notificationRequest.stats.errors;
}
cancel(): void {
this.dialogRef.close(null);
}
}

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

@ -167,8 +167,6 @@
{{ 'notification.button-text-required' | translate }} {{ 'notification.button-text-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div> </div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column"> <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100"> <mat-form-field fxFlex="30" fxFlex.xs="100">
@ -199,6 +197,10 @@
</tb-dashboard-state-autocomplete> </tb-dashboard-state-autocomplete>
</ng-template> </ng-template>
</div> </div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</div> </div>
</section> </section>
</section> </section>

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

@ -1,63 +0,0 @@
<!--
Copyright © 2016-2023 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-grid #severitiesChipList formControlName="alarmSeverities" [required]="required">
<mat-chip-row *ngFor="let severity of alarmSeverities"
[removable]="!disabled"
(removed)="onSeverityRemoved(severity)">
{{ alarmSeverityTranslationMap.get(severity) | translate }}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip-row>
<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-grid>
<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>

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

@ -1,220 +0,0 @@
///
/// Copyright © 2016-2023 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';
@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 alarmSeveritiesValues = Object.keys(AlarmSeverity) as Array<AlarmSeverity>;
alarmSeverity = AlarmSeverity;
alarmSeverities: Array<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<AlarmSeverity> | null): void {
this.searchText = '';
if (value != null && value.length > 0) {
this.modelValue = [...value];
this.alarmSeverities = [...value];
this.alarmSeveritiesForm.get('alarmSeverities').setValue(value);
} else {
this.alarmSeverities = [];
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: AlarmSeverity) {
if (alarmSeverity) {
if (!this.modelValue || this.modelValue.indexOf(alarmSeverity) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(alarmSeverity);
this.alarmSeverities.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);
this.alarmSeverities.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<AlarmSeverity> {
this.searchText = searchText;
const allowAlarmSeverity = this.alarmSeveritiesValues.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);
}
}
}

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

@ -97,18 +97,23 @@
formControlName="alarmTypes"> formControlName="alarmTypes">
</tb-string-items-list> </tb-string-items-list>
<tb-alarm-severities-list formControlName="alarmSeverities" <mat-form-field fxFlex class="mat-block" floatLabel="always">
flotLabel="always"> <mat-label translate>alarm.alarm-severity-list</mat-label>
</tb-alarm-severities-list> <mat-select formControlName="alarmSeverities" multiple
placeholder="{{ !alarmTemplateForm.get('triggerConfig.alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}">
<mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset> </fieldset>
<fieldset class="fields-group tb-margin-2" formGroupName="clearRule"> <fieldset class="fields-group tb-margin-2" 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" multiple
placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value ? ('alarm.any-status' | translate) : '' }}"> placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value?.length ? ('alarm.any-status' | translate) : '' }}">
<mat-option [value]="null">{{ 'alarm.any-status' | translate }}</mat-option>
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus"> <mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option> </mat-option>
@ -205,7 +210,37 @@
[stepControl]="alarmCommentTemplateForm"> [stepControl]="alarmCommentTemplateForm">
<ng-template matStepLabel>{{ 'notification.alarm-comment-trigger-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.alarm-comment-trigger-settings' | translate }}</ng-template>
<form [formGroup]="alarmCommentTemplateForm"> <form [formGroup]="alarmCommentTemplateForm">
<section formGroupName="triggerConfig">
<fieldset class="fields-group tb-margin">
<legend translate>notification.filter</legend>
<tb-string-items-list
editable
label="{{ 'alarm.alarm-type-list' | translate }}"
placeholder="{{ !alarmCommentTemplateForm.get('triggerConfig.alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
floatLabel="always"
formControlName="alarmTypes">
</tb-string-items-list>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-severity-list</mat-label>
<mat-select formControlName="alarmSeverities" multiple
placeholder="{{ !alarmCommentTemplateForm.get('triggerConfig.alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}">
<mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="alarmStatus" multiple
placeholder="{{ !alarmCommentTemplateForm.get('triggerConfig.alarmStatus').value?.length ? ('alarm.any-status' | translate) : '' }}">
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset>
</section>
</form> </form>
<form [formGroup]="ruleNotificationForm"> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig"> <section formGroupName="additionalConfig">

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

@ -36,7 +36,12 @@ 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 { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models'; import {
AlarmSearchStatus,
alarmSearchStatusTranslations,
AlarmSeverity,
alarmSeverityTranslations
} from '@shared/models/alarm.models';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
TargetNotificationDialogComponent, TargetNotificationDialogComponent,
@ -72,8 +77,16 @@ export class RuleNotificationDialogComponent extends
triggerTypes: TriggerType[] = Object.values(TriggerType); triggerTypes: TriggerType[] = Object.values(TriggerType);
triggerTypeTranslationMap = TriggerTypeTranslationMap; triggerTypeTranslationMap = TriggerTypeTranslationMap;
alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus); alarmSearchStatuses = [
alarmSearchStatusTranslationMap = alarmStatusTranslations; AlarmSearchStatus.ACTIVE,
AlarmSearchStatus.CLEARED,
AlarmSearchStatus.ACK,
AlarmSearchStatus.UNACK
];
alarmSearchStatusTranslationMap = alarmSearchStatusTranslations;
alarmSeverityTranslationMap = alarmSeverityTranslations;
alarmSeverities = Object.keys(AlarmSeverity) as Array<AlarmSeverity>;
entityType = EntityType; entityType = EntityType;
entityTypes: EntityType[] = Object.values(EntityType); entityTypes: EntityType[] = Object.values(EntityType);
@ -136,9 +149,9 @@ export class RuleNotificationDialogComponent extends
this.alarmTemplateForm = this.fb.group({ this.alarmTemplateForm = this.fb.group({
triggerConfig: this.fb.group({ triggerConfig: this.fb.group({
alarmTypes: [null], alarmTypes: [null],
alarmSeverities: [null], alarmSeverities: [[]],
clearRule: this.fb.group({ clearRule: this.fb.group({
alarmStatus: [null] alarmStatus: [[]]
}) })
}) })
}); });
@ -172,7 +185,13 @@ export class RuleNotificationDialogComponent extends
}) })
}); });
this.alarmCommentTemplateForm = this.fb.group({ }); this.alarmCommentTemplateForm = this.fb.group({
triggerConfig: this.fb.group({
alarmTypes: [null],
alarmSeverities: [[]],
alarmStatus: [[]]
})
});
this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([ this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
[TriggerType.ALARM, this.alarmTemplateForm], [TriggerType.ALARM, this.alarmTemplateForm],

6
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts

@ -102,11 +102,11 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
actionButtonConfig: this.fb.group({ actionButtonConfig: this.fb.group({
enabled: [false], enabled: [false],
text: [{value: '', disabled: true}, Validators.required], text: [{value: '', disabled: true}, Validators.required],
color: ['#305680'],
linkType: [ActionButtonLinkType.LINK], linkType: [ActionButtonLinkType.LINK],
link: [{value: '', disabled: true}, Validators.required], link: [{value: '', disabled: true}, Validators.required],
dashboardId: [{value: null, disabled: true}, Validators.required], dashboardId: [{value: null, disabled: true}, Validators.required],
dashboardState: [{value: null, disabled: true}] dashboardState: [{value: null, disabled: true}],
setEntityIdInState: [{value: true, disabled: true}],
}), }),
}) })
}); });
@ -142,10 +142,12 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').disable({emitEvent: false});
} else { } else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').enable({emitEvent: false});
} }
} }
}); });

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

@ -131,8 +131,6 @@
{{ 'notification.button-text-required' | translate }} {{ 'notification.button-text-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div> </div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column"> <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100"> <mat-form-field fxFlex="30" fxFlex.xs="100">
@ -163,6 +161,10 @@
</tb-dashboard-state-autocomplete> </tb-dashboard-state-autocomplete>
</ng-template> </ng-template>
</div> </div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</div> </div>
</section> </section>
</section> </section>

5
ui-ngx/src/app/shared/components/notification/notification.component.html

@ -30,8 +30,7 @@
<div class="content" fxFlex> <div class="content" fxFlex>
<div class="title" [innerHTML]="(notification.subject | safe: 'html' )"></div> <div class="title" [innerHTML]="(notification.subject | safe: 'html' )"></div>
<div class="message" [innerHTML]="(notification.text | safe: 'html' )"></div> <div class="message" [innerHTML]="(notification.text | safe: 'html' )"></div>
<button *ngIf="showButton" mat-stroked-button class="button" <button *ngIf="showButton" mat-stroked-button class="button" color="primary"
[ngStyle]="{color: notification.additionalConfig.actionButtonConfig.color}"
(click)="navigate($event)"> (click)="navigate($event)">
{{ buttonLabel }} {{ buttonLabel }}
</button> </button>
@ -39,7 +38,7 @@
<div class="description" fxLayout="column" fxLayoutAlign="start end"> <div class="description" fxLayout="column" fxLayoutAlign="start end">
<div fxLayout="row" fxLayoutAlign="end center" class="mark-read"> <div fxLayout="row" fxLayoutAlign="end center" class="mark-read">
<span class="time">{{ notification.createdTime ?? currentDate | dateAgo }}</span> <span class="time">{{ notification.createdTime ?? currentDate | dateAgo }}</span>
<button mat-icon-button class="tb-mat-28" (click)="markRead($event)"> <button mat-icon-button class="tb-mat-28" (click)="markRead($event)" *ngIf="!hideMarkAsReadButton">
<mat-icon>check_circle_outline</mat-icon> <mat-icon>check_circle_outline</mat-icon>
</button> </button>
</div> </div>

11
ui-ngx/src/app/shared/components/notification/notification.component.ts

@ -19,6 +19,7 @@ import {
ActionButtonLinkType, ActionButtonLinkType,
AlarmSeverityNotificationColors, AlarmSeverityNotificationColors,
Notification, Notification,
NotificationStatus,
NotificationType, NotificationType,
NotificationTypeIcons NotificationTypeIcons
} from '@shared/models/notification.models'; } from '@shared/models/notification.models';
@ -53,6 +54,7 @@ export class NotificationComponent implements OnInit {
showIcon = false; showIcon = false;
showButton = false; showButton = false;
buttonLabel = ''; buttonLabel = '';
hideMarkAsReadButton = false;
tinycolor = tinycolor_; tinycolor = tinycolor_;
@ -71,6 +73,7 @@ export class NotificationComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.showIcon = this.notification.additionalConfig?.icon?.enabled; this.showIcon = this.notification.additionalConfig?.icon?.enabled;
this.showButton = this.notification.additionalConfig?.actionButtonConfig?.enabled; this.showButton = this.notification.additionalConfig?.actionButtonConfig?.enabled;
this.hideMarkAsReadButton = this.notification.status === NotificationStatus.READ;
if (this.showButton) { if (this.showButton) {
this.buttonLabel = this.utils.customTranslation(this.notification.additionalConfig.actionButtonConfig.text, this.buttonLabel = this.utils.customTranslation(this.notification.additionalConfig.actionButtonConfig.text,
this.notification.additionalConfig.actionButtonConfig.text); this.notification.additionalConfig.actionButtonConfig.text);
@ -96,7 +99,13 @@ export class NotificationComponent implements OnInit {
let state = null; let state = null;
if (this.notification.additionalConfig.actionButtonConfig.dashboardState) { if (this.notification.additionalConfig.actionButtonConfig.dashboardState) {
const stateObject: StateObject = {}; const stateObject: StateObject = {};
stateObject.params = {}; if (this.notification.additionalConfig.actionButtonConfig.setEntityIdInState) {
stateObject.params = {
entityId: this.notification.info.originatorId ?? null
};
} else {
stateObject.params = {};
}
stateObject.id = this.notification.additionalConfig.actionButtonConfig.dashboardState; stateObject.id = this.notification.additionalConfig.actionButtonConfig.dashboardState;
state = objToBase64URI([ stateObject ]); state = objToBase64URI([ stateObject ]);
} }

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

@ -23,7 +23,7 @@ import { NotificationTargetId } from '@shared/models/id/notification-target-id';
import { NotificationTemplateId } from '@shared/models/id/notification-template-id'; import { NotificationTemplateId } from '@shared/models/id/notification-template-id';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { NotificationRuleId } from '@shared/models/id/notification-rule-id'; import { NotificationRuleId } from '@shared/models/id/notification-rule-id';
import { AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; import { AlarmSearchStatus, AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
export interface Notification { export interface Notification {
@ -45,6 +45,7 @@ export interface NotificationInfo {
alarmSeverity?: AlarmSeverity; alarmSeverity?: AlarmSeverity;
alarmStatus?: AlarmStatus; alarmStatus?: AlarmStatus;
alarmType?: string; alarmType?: string;
originatorId?: EntityId;
} }
export interface NotificationRequest extends Omit<BaseData<NotificationRequestId>, 'label'> { export interface NotificationRequest extends Omit<BaseData<NotificationRequestId>, 'label'> {
@ -73,7 +74,7 @@ export interface NotificationRequestPreview {
export interface NotificationRequestStats { export interface NotificationRequestStats {
sent: Map<NotificationDeliveryMethod, any>; sent: Map<NotificationDeliveryMethod, any>;
errors: Map<NotificationDeliveryMethod, Map<string, string>>; errors: { [key in NotificationDeliveryMethod]: {[key in string]: string}};
processedRecipients: Map<NotificationDeliveryMethod, Set<UserId>>; processedRecipients: Map<NotificationDeliveryMethod, Set<UserId>>;
} }
@ -111,7 +112,10 @@ export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'la
export interface NotificationRuleTriggerConfig { export interface NotificationRuleTriggerConfig {
alarmTypes?: Array<string>; alarmTypes?: Array<string>;
alarmSeverities?: Array<AlarmSeverity>; alarmSeverities?: Array<AlarmSeverity>;
clearRule?: AlarmStatus; alarmStatus?: Array<AlarmSearchStatus>;
clearRule?: {
alarmStatus: Array<AlarmSearchStatus>;
};
devices?: Array<string>; devices?: Array<string>;
deviceProfiles?: Array<string>; deviceProfiles?: Array<string>;
entityType?: EntityType; entityType?: EntityType;
@ -205,11 +209,11 @@ interface PushDeliveryMethodAdditionalConfig {
actionButtonConfig: { actionButtonConfig: {
enabled: boolean; enabled: boolean;
text: string; text: string;
color: string;
linkType: ActionButtonLinkType, linkType: ActionButtonLinkType,
link?: string; link?: string;
dashboardId?: string; dashboardId?: string;
dashboardState?: string; dashboardState?: string;
setEntityIdInState?: boolean
}; };
} }

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

@ -2749,12 +2749,16 @@
"delivery-method": "Delivery method", "delivery-method": "Delivery method",
"delivery-method-type": { "delivery-method-type": {
"email": "Email", "email": "Email",
"email-failed-sent": "Email messages failed sent",
"email-preview": "Email notification preview", "email-preview": "Email notification preview",
"push": "Web", "push": "Web",
"push-failed-sent": "Web messages failed sent",
"push-preview": "Web notification preview", "push-preview": "Web notification preview",
"slack": "Slack", "slack": "Slack",
"slack-failed-sent": "Slack messages failed sent",
"slack-preview": "Slack notification preview", "slack-preview": "Slack notification preview",
"sms": "SMS", "sms": "SMS",
"sms-failed-sent": "SMS messages failed sent",
"sms-preview": "SMS notification preview" "sms-preview": "SMS notification preview"
}, },
"delivery-methods": "Delivery methods", "delivery-methods": "Delivery methods",
@ -2773,6 +2777,8 @@
"email-settings": "Email settings", "email-settings": "Email settings",
"entity-action-trigger-settings": "Entity action trigger settings", "entity-action-trigger-settings": "Entity action trigger settings",
"entity-type": "Entity type", "entity-type": "Entity type",
"failed-send": "Failed send",
"fails": "Fails",
"filter": "Filter", "filter": "Filter",
"first-recipient": "First recipient", "first-recipient": "First recipient",
"hierarchy-of-receiving": "Hierarchy of receiving notifications", "hierarchy-of-receiving": "Hierarchy of receiving notifications",
@ -2827,6 +2833,7 @@
"send-notification": "Send notification", "send-notification": "Send notification",
"sent": "Sent", "sent": "Sent",
"settings": "Notification settings", "settings": "Notification settings",
"set-entity-from-notification": "Set entity from notification to dashboard state",
"slack": "Slack", "slack": "Slack",
"slack-chanel-type": "Slack chanel type", "slack-chanel-type": "Slack chanel type",
"slack-chanel-types": { "slack-chanel-types": {

Loading…
Cancel
Save