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() {
this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone);
this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone, 6);
this.notifications$ = this.notificationSubscriber.notifications$.pipe(
share({
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 { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component';
import {
AlarmSeveritiesListComponent
} from '@home/pages/notification-center/rule-table/alarm-severities-list.component';
InboxNotificationDialogComponent
} 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({
declarations: [
@ -67,7 +70,8 @@ import {
RuleNotificationDialogComponent,
EscalationsComponent,
EscalationFormComponent,
AlarmSeveritiesListComponent
InboxNotificationDialogComponent,
NotificationRequestErrorDialogComponent
],
imports: [
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 { TranslateService } from '@ngx-translate/core';
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> {
constructor(private notificationService: NotificationService,
private translate: TranslateService,
private dialog: MatDialog,
private datePipe: DatePipe) {
super();
this.entitiesDeleteEnabled = false;
this.rowPointer = true;
this.entityTranslations = {
noEntities: 'notification.no-inbox-notification',
search: 'notification.search-notification'
@ -50,6 +56,11 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.handleRowClick = ($event, notification) => {
this.showNotification($event, notification)
return true;
}
this.componentsData = {
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(
this.notificationService,
this.translate,
this.dialog,
this.datePipe
);
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,
NotificationRequest,
NotificationRequestInfo,
NotificationRequestStats,
NotificationRequestStatus,
NotificationRequestStatusTranslateMap,
NotificationTemplate
@ -40,6 +41,10 @@ import {
RequestNotificationDialogData
} from '@home/pages/notification-center/request-table/request-notification-dialog.componet';
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> {
@ -66,12 +71,21 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
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.columns.push(
new DateEntityTableColumn<NotificationRequestInfo>('createdTime', 'notification.created-time', this.datePipe, '170px'),
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%',
(request) => request.deliveryMethods
.map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '),
@ -136,6 +150,18 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
</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 {
const styleObj = {
fontSize: '14px',
@ -152,4 +178,18 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
}
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 }}
</mat-error>
</mat-form-field>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
@ -199,6 +197,10 @@
</tb-dashboard-state-autocomplete>
</ng-template>
</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>
</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">
</tb-string-items-list>
<tb-alarm-severities-list formControlName="alarmSeverities"
flotLabel="always">
</tb-alarm-severities-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="{{ !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 class="fields-group tb-margin-2" formGroupName="clearRule">
<legend translate>notification.clear-rule</legend>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="alarmStatus"
placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value ? ('alarm.any-status' | translate) : '' }}">
<mat-option [value]="null">{{ 'alarm.any-status' | translate }}</mat-option>
<mat-select formControlName="alarmStatus" multiple
placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value?.length ? ('alarm.any-status' | translate) : '' }}">
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option>
@ -205,7 +210,37 @@
[stepControl]="alarmCommentTemplateForm">
<ng-template matStepLabel>{{ 'notification.alarm-comment-trigger-settings' | translate }}</ng-template>
<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 [formGroup]="ruleNotificationForm">
<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 { MediaBreakpoints } from '@shared/models/constants';
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 {
TargetNotificationDialogComponent,
@ -72,8 +77,16 @@ export class RuleNotificationDialogComponent extends
triggerTypes: TriggerType[] = Object.values(TriggerType);
triggerTypeTranslationMap = TriggerTypeTranslationMap;
alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus);
alarmSearchStatusTranslationMap = alarmStatusTranslations;
alarmSearchStatuses = [
AlarmSearchStatus.ACTIVE,
AlarmSearchStatus.CLEARED,
AlarmSearchStatus.ACK,
AlarmSearchStatus.UNACK
];
alarmSearchStatusTranslationMap = alarmSearchStatusTranslations;
alarmSeverityTranslationMap = alarmSeverityTranslations;
alarmSeverities = Object.keys(AlarmSeverity) as Array<AlarmSeverity>;
entityType = EntityType;
entityTypes: EntityType[] = Object.values(EntityType);
@ -136,9 +149,9 @@ export class RuleNotificationDialogComponent extends
this.alarmTemplateForm = this.fb.group({
triggerConfig: this.fb.group({
alarmTypes: [null],
alarmSeverities: [null],
alarmSeverities: [[]],
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>([
[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({
enabled: [false],
text: [{value: '', disabled: true}, Validators.required],
color: ['#305680'],
linkType: [ActionButtonLinkType.LINK],
link: [{value: '', 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.dashboardId').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').disable({emitEvent: false});
} else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({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.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 }}
</mat-error>
</mat-form-field>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
@ -163,6 +161,10 @@
</tb-dashboard-state-autocomplete>
</ng-template>
</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>
</section>
</section>

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

@ -30,8 +30,7 @@
<div class="content" fxFlex>
<div class="title" [innerHTML]="(notification.subject | safe: 'html' )"></div>
<div class="message" [innerHTML]="(notification.text | safe: 'html' )"></div>
<button *ngIf="showButton" mat-stroked-button class="button"
[ngStyle]="{color: notification.additionalConfig.actionButtonConfig.color}"
<button *ngIf="showButton" mat-stroked-button class="button" color="primary"
(click)="navigate($event)">
{{ buttonLabel }}
</button>
@ -39,7 +38,7 @@
<div class="description" fxLayout="column" fxLayoutAlign="start end">
<div fxLayout="row" fxLayoutAlign="end center" class="mark-read">
<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>
</button>
</div>

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

@ -19,6 +19,7 @@ import {
ActionButtonLinkType,
AlarmSeverityNotificationColors,
Notification,
NotificationStatus,
NotificationType,
NotificationTypeIcons
} from '@shared/models/notification.models';
@ -53,6 +54,7 @@ export class NotificationComponent implements OnInit {
showIcon = false;
showButton = false;
buttonLabel = '';
hideMarkAsReadButton = false;
tinycolor = tinycolor_;
@ -71,6 +73,7 @@ export class NotificationComponent implements OnInit {
ngOnInit() {
this.showIcon = this.notification.additionalConfig?.icon?.enabled;
this.showButton = this.notification.additionalConfig?.actionButtonConfig?.enabled;
this.hideMarkAsReadButton = this.notification.status === NotificationStatus.READ;
if (this.showButton) {
this.buttonLabel = this.utils.customTranslation(this.notification.additionalConfig.actionButtonConfig.text,
this.notification.additionalConfig.actionButtonConfig.text);
@ -96,7 +99,13 @@ export class NotificationComponent implements OnInit {
let state = null;
if (this.notification.additionalConfig.actionButtonConfig.dashboardState) {
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;
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 { EntityId } from '@shared/models/id/entity-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';
export interface Notification {
@ -45,6 +45,7 @@ export interface NotificationInfo {
alarmSeverity?: AlarmSeverity;
alarmStatus?: AlarmStatus;
alarmType?: string;
originatorId?: EntityId;
}
export interface NotificationRequest extends Omit<BaseData<NotificationRequestId>, 'label'> {
@ -73,7 +74,7 @@ export interface NotificationRequestPreview {
export interface NotificationRequestStats {
sent: Map<NotificationDeliveryMethod, any>;
errors: Map<NotificationDeliveryMethod, Map<string, string>>;
errors: { [key in NotificationDeliveryMethod]: {[key in string]: string}};
processedRecipients: Map<NotificationDeliveryMethod, Set<UserId>>;
}
@ -111,7 +112,10 @@ export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'la
export interface NotificationRuleTriggerConfig {
alarmTypes?: Array<string>;
alarmSeverities?: Array<AlarmSeverity>;
clearRule?: AlarmStatus;
alarmStatus?: Array<AlarmSearchStatus>;
clearRule?: {
alarmStatus: Array<AlarmSearchStatus>;
};
devices?: Array<string>;
deviceProfiles?: Array<string>;
entityType?: EntityType;
@ -205,11 +209,11 @@ interface PushDeliveryMethodAdditionalConfig {
actionButtonConfig: {
enabled: boolean;
text: string;
color: string;
linkType: ActionButtonLinkType,
link?: string;
dashboardId?: 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-type": {
"email": "Email",
"email-failed-sent": "Email messages failed sent",
"email-preview": "Email notification preview",
"push": "Web",
"push-failed-sent": "Web messages failed sent",
"push-preview": "Web notification preview",
"slack": "Slack",
"slack-failed-sent": "Slack messages failed sent",
"slack-preview": "Slack notification preview",
"sms": "SMS",
"sms-failed-sent": "SMS messages failed sent",
"sms-preview": "SMS notification preview"
},
"delivery-methods": "Delivery methods",
@ -2773,6 +2777,8 @@
"email-settings": "Email settings",
"entity-action-trigger-settings": "Entity action trigger settings",
"entity-type": "Entity type",
"failed-send": "Failed send",
"fails": "Fails",
"filter": "Filter",
"first-recipient": "First recipient",
"hierarchy-of-receiving": "Hierarchy of receiving notifications",
@ -2827,6 +2833,7 @@
"send-notification": "Send notification",
"sent": "Sent",
"settings": "Notification settings",
"set-entity-from-notification": "Set entity from notification to dashboard state",
"slack": "Slack",
"slack-chanel-type": "Slack chanel type",
"slack-chanel-types": {

Loading…
Cancel
Save