Browse Source

UI: Redesign delivery mehod congiguration in notification

pull/8522/head
Vladyslav_Prykhodko 2 years ago
parent
commit
ba7605cb85
  1. 4
      ui-ngx/src/app/modules/home/pages/notification/notification.module.ts
  2. 3
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts
  3. 2
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html
  4. 4
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts
  5. 348
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html
  6. 2
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss
  7. 15
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts
  8. 4
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts
  9. 2
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html
  10. 4
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts
  11. 398
      ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.html
  12. 66
      ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.scss
  13. 312
      ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.ts
  14. 152
      ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts
  15. 341
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html
  16. 25
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss
  17. 25
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts
  18. 2
      ui-ngx/src/app/shared/components/notification/template-autocomplete.component.html
  19. 4
      ui-ngx/src/app/shared/components/notification/template-autocomplete.component.ts
  20. 7
      ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.ts
  21. 60
      ui-ngx/src/app/shared/models/notification.models.ts
  22. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -39,6 +39,7 @@ import { NotificationSettingsComponent } from '@home/pages/notification/settings
import {
NotificationSettingFormComponent
} from '@home/pages/notification/settings/notification-setting-form.component';
import { NotificationTemplateConfigurationComponent } from '@home/pages/notification/template/notification-template-configuration.component';
@NgModule({
declarations: [
@ -55,7 +56,8 @@ import {
RuleNotificationDialogComponent,
RuleTableHeaderComponent,
NotificationSettingsComponent,
NotificationSettingFormComponent
NotificationSettingFormComponent,
NotificationTemplateConfigurationComponent
],
imports: [
CommonModule,

3
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts

@ -362,6 +362,9 @@ export class RuleNotificationDialogComponent extends
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
if ($event.previouslySelectedIndex > $event.selectedIndex) {
$event.previouslySelectedStep.interacted = false;
}
}
backStep() {

2
ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html

@ -26,7 +26,7 @@
</mat-toolbar>
<div mat-dialog-content>
<div *ngFor="let errorStat of errorStats | keyvalue; last as isLast">
<h6>{{ notificationDeliveryMethodTranslateMap.get(errorStat.key) | translate }}</h6>
<h6>{{ NotificationDeliveryMethodInfoMap.get(errorStat.key).name | translate }}</h6>
<table class="tb-table-list">
<tr *ngFor="let error of errorStat.value | keyvalue">
<td>

4
ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts

@ -22,7 +22,7 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationDeliveryMethodInfoMap,
NotificationRequest
} from '@shared/models/notification.models';
@ -39,7 +39,7 @@ export class SentErrorDialogComponent extends DialogComponent<SentErrorDialogCom
errorStats: { [key in NotificationDeliveryMethod]: {[errorKey in string]: string}};
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
NotificationDeliveryMethodInfoMap = NotificationDeliveryMethodInfoMap;
constructor(protected store: Store<AppState>,
protected router: Router,

348
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html

@ -29,7 +29,7 @@
</mat-progress-bar>
<div mat-dialog-content>
<mat-horizontal-stepper linear #createNotification
labelPosition="bottom"
labelPosition="end"
[orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit">
@ -88,7 +88,7 @@
[matTooltip]="getDeliveryMethodsTooltip(deliveryMethods)"
matTooltipPosition="above">
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}
</mat-slide-toggle>
<mat-icon *ngIf="isInteractDeliveryMethod(deliveryMethods)">
chevron_right
@ -101,7 +101,7 @@
[matTooltipDisabled]="getDeliveryMethodsTemplatesControl(deliveryMethods).enabled"
matTooltipPosition="above">
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}
</mat-slide-toggle>
</section>
</ng-template>
@ -145,338 +145,16 @@
</section>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.WEB.enabled').value"
[stepControl]="webTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.web' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(notificationType.GENERAL).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="webTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="webTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="webTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
[color]="webTemplateForm.get('additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="actionButtonConfig">
<mat-expansion-panel class="tb-settings" [expanded]="webTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
{length: webTemplateForm.get('additionalConfig.actionButtonConfig.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</section>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.EMAIL.enabled').value"
[stepControl]="emailTemplateForm" #emailStep="matStep">
<ng-template matStepLabel>{{ 'notification.delivery-method.email' | translate }}</ng-template>
<ng-template matStepContent>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(notificationType.GENERAL).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="emailTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="emailTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-label class="tb-title tb-required"
[ngClass]="{'tb-error': (emailStep.interacted || emailTemplateForm.get('body').dirty) && emailTemplateForm.get('body').hasError('required')}"
translate>notification.message</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
<mat-error class="tb-mat-error"
*ngIf="(emailStep.interacted || emailTemplateForm.get('body').dirty) && emailTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</form>
</ng-template>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SMS.enabled').value"
[stepControl]="smsTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.sms' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(notificationType.GENERAL).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="smsTemplateForm">
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="smsTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="smsTemplateForm.get('body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: smsTemplateForm.get('body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.slack' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(notificationType.GENERAL).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="slackTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="slackTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.MICROSOFT_TEAMS.enabled').value"
[stepControl]="microsoftTeamsTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.microsoft-teams' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="microsoftTeamsTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between">
<div translate>notification.theme-color</div>
<tb-color-input asBoxInput formControlName="themeColor"></tb-color-input>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="button">
<mat-expansion-panel class="tb-settings" [expanded]="microsoftTeamsTemplateForm.get('button.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
{length: microsoftTeamsTemplateForm.get('button.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="microsoftTeamsTemplateForm.get('button.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="microsoftTeamsTemplateForm.get('button.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="microsoftTeamsTemplateForm.get('button.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.MOBILE_APP.enabled').value"
[stepControl]="mobileTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.mobile-app' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(notificationType.GENERAL).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="mobileTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="mobileTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="mobileTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value"
[stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="templateNotificationForm.get('notificationType').value"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</mat-step>
<mat-step>

2
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss

@ -16,7 +16,7 @@
@import "../../../../../../scss/constants";
:host {
width: 930px;
width: 775px;
height: 100%;
max-width: 100%;
max-height: 100vh;

15
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts

@ -24,7 +24,6 @@ import {
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
@ -47,6 +46,7 @@ import { Authority } from '@shared/models/authority.enum';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
export interface RequestNotificationDialogData {
request?: NotificationRequest;
@ -98,7 +98,7 @@ export class SentNotificationDialogComponent extends
}
});
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm'])
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
this.notificationRequestForm = this.fb.group({
@ -150,11 +150,9 @@ export class SentNotificationDialogComponent extends
let useTemplate = true;
if (isDefinedAndNotNull(this.data.request.template)) {
useTemplate = false;
// eslint-disable-next-line guard-for-in
for (const method in this.data.request.template.configuration.deliveryMethodsTemplates) {
this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method])
.patchValue(this.data.request.template.configuration.deliveryMethodsTemplates[method]);
}
this.notificationTemplateConfigurationForm.patchValue({
deliveryMethodsTemplates: this.data.request.template.configuration.deliveryMethodsTemplates
}, {emitEvent: false});
}
this.notificationRequestForm.get('useTemplate').setValue(useTemplate, {onlySelf : true});
}
@ -178,6 +176,9 @@ export class SentNotificationDialogComponent extends
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
if ($event.previouslySelectedIndex > $event.selectedIndex) {
$event.previouslySelectedStep.interacted = false;
}
if (this.selectedIndex === this.maxStepperIndex) {
this.getPreview();
}

4
ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts

@ -21,7 +21,7 @@ import {
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import {
NotificationDeliveryMethodTranslateMap,
NotificationDeliveryMethodInfoMap,
NotificationRequest,
NotificationRequestInfo,
NotificationRequestStats,
@ -94,7 +94,7 @@ export class SentTableConfigResolver implements Resolve<EntityTableConfig<Notifi
request => this.requestStatusStyle(request.status)),
new EntityTableColumn<NotificationRequest>('deliveryMethods', 'notification.delivery-method.delivery-method', '15%',
(request) => request.deliveryMethods
.map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '),
.map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodInfoMap.get(deliveryMethod).name)).join(', '),
() => ({}), false),
new EntityTableColumn<NotificationRequest>('templateName', 'notification.template', '70%')
);

2
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html

@ -54,7 +54,7 @@
(change)="changeInstanceTypeCheckBox($event.checked, deliveryMethods)"
[indeterminate]="getIndeterminate(deliveryMethods)"
(click)="$event.stopPropagation()">
<mat-label>{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}</mat-label>
<mat-label>{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}</mat-label>
</mat-checkbox>
</div>
</div>

4
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts

@ -25,7 +25,7 @@ import { ActivatedRoute } from '@angular/router';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationDeliveryMethodInfoMap,
NotificationUserSettings
} from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
@ -41,7 +41,7 @@ export class NotificationSettingsComponent extends PageComponent implements OnIn
notificationSettings: UntypedFormGroup;
notificationDeliveryMethods: NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
notificationDeliveryMethodInfoMap = NotificationDeliveryMethodInfoMap;
private deliveryMethods = new Set([
NotificationDeliveryMethod.SLACK,

398
ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.html

@ -0,0 +1,398 @@
<!--
Copyright © 2016-2024 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 class="tb-template-header">
<div class="tb-form-panel-title" translate>notification.customize-messages</div>
<div class="tb-form-panel-hint tb-hint-available-params">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ NotificationTemplateTypeTranslateMap.get(notificationType).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px;font-size:12px;"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
</section>
<section [formGroup]="templateConfigurationForm" class="tb-form-panel no-border no-padding">
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.WEB"
*ngIf="templateConfigurationForm.get('WEB.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('WEB.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('WEB.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('WEB.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="actionButtonConfig">
<mat-expansion-panel class="tb-settings"
[expanded]="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error
*ngIf="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error
*ngIf="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
{length: templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes"
[value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error
*ngIf="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="templateConfigurationForm.get('WEB.additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</section>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.MOBILE_APP"
*ngIf="templateConfigurationForm.get('MOBILE_APP.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="additionalConfig">
<mat-expansion-panel class="tb-settings"
formGroupName="onClick"
[expanded]="templateConfigurationForm.get('MOBILE_APP.additionalConfig.onClick.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.open-dashboard-on-click-notification' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<tb-dashboard-autocomplete
fxFlex fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex fxFlex.xs="100"
[dashboardId]="templateConfigurationForm.get('MICROSOFT_TEAMS.button.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.SMS"
*ngIf="templateConfigurationForm.get('SMS.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-hint></mat-hint>
<mat-error *ngIf="templateConfigurationForm.get('SMS.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('SMS.body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('SMS.body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.MICROSOFT_TEAMS"
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between">
<div translate>notification.theme-color</div>
<tb-color-input asBoxInput formControlName="themeColor"></tb-color-input>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="button">
<mat-expansion-panel class="tb-settings"
[expanded]="templateConfigurationForm.get('MICROSOFT_TEAMS.button.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.button.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.button.text').hasError('maxlength')">
{{'notification.button-text-max-length' | translate :
{length: templateConfigurationForm.get('MICROSOFT_TEAMS.button.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes"
[value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.button.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.button.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="templateConfigurationForm.get('MICROSOFT_TEAMS.button.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.button.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.SLACK"
*ngIf="templateConfigurationForm.get('SLACK.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('SLACK.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.EMAIL"
*ngIf="templateConfigurationForm.get('EMAIL.enabled').value">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('EMAIL.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-label class="tb-title tb-required"
[class.tb-error]="(interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')"
translate>notification.message
</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
<mat-error class="tb-mat-error"
*ngIf="(interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</ng-template>
</mat-expansion-panel>
</section>
</section>

66
ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.scss

@ -0,0 +1,66 @@
/**
* Copyright © 2016-2024 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 {
.tb-template-header {
margin-bottom: 12px;
.tb-hint-available-params {
color: rgba(0,0,0,0.54);
letter-spacing: 0.4px;
.content {
vertical-align: middle;
}
}
}
.template-tittle {
gap: 12px;
font-weight: normal;
tb-icon {
color: rgba(0, 0, 0, .38);
}
}
.tb-mat-error {
font-size: 13px;
}
.tb-title {
font-size: 16px;
line-height: 24px;
&.tb-required::after {
font-size: initial;
content: "*";
}
&.tb-error {
color: var(--mdc-theme-error, #f44336);
&.tb-required::after {
color: var(--mdc-theme-error, #f44336);
}
}
}
}
:host ::ng-deep {
.tb-form-panel .mat-expansion-panel.tb-settings {
& > .mat-expansion-panel-content > .mat-expansion-panel-body {
gap: 0;
}
}
}

312
ui-ngx/src/app/modules/home/pages/notification/template/notification-template-configuration.component.ts

@ -0,0 +1,312 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import {
ActionButtonLinkType,
ActionButtonLinkTypeTranslateMap,
DeliveryMethodsTemplates,
NotificationDeliveryMethod,
NotificationDeliveryMethodInfoMap,
NotificationTemplateTypeTranslateMap,
NotificationType
} from '@shared/models/notification.models';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-template-configuration',
templateUrl: './notification-template-configuration.component.html',
styleUrls: ['./notification-template-configuration.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NotificationTemplateConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => NotificationTemplateConfigurationComponent),
multi: true,
}
]
})
export class NotificationTemplateConfigurationComponent implements OnDestroy, ControlValueAccessor, Validator {
templateConfigurationForm: FormGroup;
NotificationDeliveryMethodInfoMap = NotificationDeliveryMethodInfoMap;
@Input()
set predefinedDeliveryMethodsTemplate(value: Partial<DeliveryMethodsTemplates>) {
if (isDefinedAndNotNull(value)) {
this.templateConfigurationForm.patchValue(value, {emitEvent: false});
this.updateDisabledForms();
this.templateConfigurationForm.updateValueAndValidity();
}
}
@Input()
notificationType: NotificationType;
@Input()
@coerceBoolean()
interacted: boolean;
readonly NotificationDeliveryMethod = NotificationDeliveryMethod;
readonly NotificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap;
readonly actionButtonLinkType = ActionButtonLinkType;
readonly actionButtonLinkTypes = Object.keys(ActionButtonLinkType) as ActionButtonLinkType[];
readonly actionButtonLinkTypeTranslateMap = ActionButtonLinkTypeTranslateMap;
tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
toolbar_mode: 'sliding',
height: 400,
autofocus: false,
branding: false
};
private propagateChange = (v: any) => { };
private readonly destroy$ = new Subject<void>();
constructor(private fb: FormBuilder) {
this.templateConfigurationForm = this.buildForm();
this.templateConfigurationForm.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
this.propagateChange(value);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
writeValue(value: any) {
this.templateConfigurationForm.patchValue(value, {emitEvent: false});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.templateConfigurationForm.disable({emitEvent: false});
} else {
this.updateDisabledForms();
}
}
validate(): ValidationErrors {
return this.templateConfigurationForm.valid ? null : {
templateConfiguration: {
valid: false,
},
};
}
private updateDisabledForms(){
Object.values(NotificationDeliveryMethod).forEach((method) => {
const form = this.templateConfigurationForm.get(method);
if (!form.get('enabled').value) {
form.disable({emitEvent: false});
} else {
form.enable({emitEvent: false});
switch (method) {
case NotificationDeliveryMethod.WEB:
form.get('additionalConfig.icon.enabled').updateValueAndValidity({onlySelf: true});
form.get('additionalConfig.actionButtonConfig.enabled').updateValueAndValidity({onlySelf: true});
break;
case NotificationDeliveryMethod.MICROSOFT_TEAMS:
form.get('button.enabled').updateValueAndValidity({onlySelf: true});
break;
case NotificationDeliveryMethod.MOBILE_APP:
form.get('additionalConfig.onClick.enabled').updateValueAndValidity({onlySelf: true});
}
}
});
}
private buildForm(): FormGroup {
const form = this.fb.group({});
Object.values(NotificationDeliveryMethod).forEach((method) => {
form.addControl(method, this.buildDeliveryMethodControl(method), {emitEvent: false});
});
return form;
}
private buildDeliveryMethodControl(deliveryMethod: NotificationDeliveryMethod): FormGroup {
let deliveryMethodForm: FormGroup;
switch (deliveryMethod) {
case NotificationDeliveryMethod.WEB:
deliveryMethodForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required],
additionalConfig: this.fb.group({
icon: this.fb.group({
enabled: [false],
icon: [{value: 'notifications', disabled: true}, Validators.required],
color: [{value: '#757575', disabled: true}]
}),
actionButtonConfig: this.createButtonConfigForm()
})
});
deliveryMethodForm.get('additionalConfig.icon.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
deliveryMethodForm.get('additionalConfig.icon.icon').enable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.icon.color').enable({emitEvent: false});
} else {
deliveryMethodForm.get('additionalConfig.icon.icon').disable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.icon.color').disable({emitEvent: false});
}
});
break;
case NotificationDeliveryMethod.EMAIL:
deliveryMethodForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required]
});
break;
case NotificationDeliveryMethod.SMS:
deliveryMethodForm = this.fb.group({
body: ['', [Validators.required, Validators.maxLength(320)]]
});
break;
case NotificationDeliveryMethod.SLACK:
deliveryMethodForm = this.fb.group({
body: ['', Validators.required]
});
break;
case NotificationDeliveryMethod.MOBILE_APP:
deliveryMethodForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required],
additionalConfig: this.fb.group({
onClick: this.fb.group({
enabled: [false],
linkType: [{value: ActionButtonLinkType.DASHBOARD, disabled: true}],
dashboardId: [{value: null, disabled: true}, Validators.required],
dashboardState: [{value: null, disabled: true}],
setEntityIdInState: [{value: true, disabled: true}]
})
})
});
deliveryMethodForm.get('additionalConfig.onClick.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
deliveryMethodForm.get('additionalConfig.onClick.linkType').enable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.dashboardId').enable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.dashboardState').enable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.setEntityIdInState').enable({emitEvent: false});
} else {
deliveryMethodForm.get('additionalConfig.onClick.linkType').disable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.dashboardId').disable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.dashboardState').disable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.onClick.setEntityIdInState').disable({emitEvent: false});
}
});
break;
case NotificationDeliveryMethod.MICROSOFT_TEAMS:
deliveryMethodForm = this.fb.group({
subject: [''],
body: ['', Validators.required],
themeColor: [''],
button: this.createButtonConfigForm()
});
break;
default:
throw new Error(`Not configured templated for notification delivery method: ${deliveryMethod}`);
}
deliveryMethodForm.addControl('enabled', this.fb.control(false), {emitEvent: false});
deliveryMethodForm.addControl('method', this.fb.control(deliveryMethod), {emitEvent: false});
return deliveryMethodForm;
}
private createButtonConfigForm(): FormGroup {
const form = this.fb.group({
enabled: [false],
text: [{value: '', disabled: true}, [Validators.required, Validators.maxLength(50)]],
linkType: [ActionButtonLinkType.LINK],
link: [{value: '', disabled: true}, Validators.required],
dashboardId: [{value: null, disabled: true}, Validators.required],
dashboardState: [{value: null, disabled: true}],
setEntityIdInState: [{value: true, disabled: true}]
});
form.get('enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
form.enable({emitEvent: false});
form.get('linkType').updateValueAndValidity({onlySelf: true});
} else {
form.disable({emitEvent: false});
form.get('enabled').enable({emitEvent: false});
}
});
form.get('linkType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
const isEnabled = form.get('enabled').value;
if (isEnabled) {
if (value === ActionButtonLinkType.LINK) {
form.get('link').enable({emitEvent: false});
form.get('dashboardId').disable({emitEvent: false});
form.get('dashboardState').disable({emitEvent: false});
form.get('setEntityIdInState').disable({emitEvent: false});
} else {
form.get('link').disable({emitEvent: false});
form.get('dashboardId').enable({emitEvent: false});
form.get('dashboardState').enable({emitEvent: false});
form.get('setEntityIdInState').enable({emitEvent: false});
}
}
});
return form;
}
}

152
ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts

@ -16,10 +16,9 @@
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import {
ActionButtonLinkType,
ActionButtonLinkTypeTranslateMap,
DeliveryMethodsTemplates,
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationDeliveryMethodInfoMap,
NotificationTemplate,
NotificationTemplateTypeTranslateMap,
NotificationType
@ -27,47 +26,25 @@ import {
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { Directive, OnDestroy } from '@angular/core';
import { deepClone, deepTrim } from '@core/utils';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MatDialogRef } from '@angular/material/dialog';
import { deepClone, deepTrim } from '@core/utils';
import tinymce from 'tinymce';
@Directive()
// tslint:disable-next-line:directive-class-suffix
export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<T, R> implements OnDestroy{
templateNotificationForm: FormGroup;
webTemplateForm: FormGroup;
emailTemplateForm: FormGroup;
smsTemplateForm: FormGroup;
slackTemplateForm: FormGroup;
microsoftTeamsTemplateForm: FormGroup;
mobileTemplateForm: FormGroup;
notificationTemplateConfigurationForm: FormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
notificationDeliveryMethodInfoMap = NotificationDeliveryMethodInfoMap;
notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap;
actionButtonLinkType = ActionButtonLinkType;
actionButtonLinkTypes = Object.keys(ActionButtonLinkType) as ActionButtonLinkType[];
actionButtonLinkTypeTranslateMap = ActionButtonLinkTypeTranslateMap;
tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
toolbar_mode: 'sliding',
height: 400,
autofocus: false,
branding: false
};
deliveryConfiguration: Partial<DeliveryMethodsTemplates>;
protected readonly destroy$ = new Subject<void>();
@ -87,69 +64,22 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
})
});
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.WEB}), {emitEvent: false});
});
this.webTemplateForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required],
additionalConfig: this.fb.group({
icon: this.fb.group({
enabled: [false],
icon: [{value: 'notifications', disabled: true}, Validators.required],
color: [{value: '#757575', disabled: true}]
}),
actionButtonConfig: this.createButtonConfigForm()
})
});
this.webTemplateForm.get('additionalConfig.icon.enabled').valueChanges.pipe(
this.templateNotificationForm.get('configuration.deliveryMethodsTemplates').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
this.webTemplateForm.get('additionalConfig.icon.icon').enable({emitEvent: false});
this.webTemplateForm.get('additionalConfig.icon.color').enable({emitEvent: false});
} else {
this.webTemplateForm.get('additionalConfig.icon.icon').disable({emitEvent: false});
this.webTemplateForm.get('additionalConfig.icon.color').disable({emitEvent: false});
}
this.deliveryConfiguration = value;
});
this.emailTemplateForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required]
this.notificationTemplateConfigurationForm = this.fb.group({
deliveryMethodsTemplates: null
});
this.smsTemplateForm = this.fb.group({
body: ['', [Validators.required, Validators.maxLength(320)]]
});
this.slackTemplateForm = this.fb.group({
body: ['', Validators.required]
});
this.mobileTemplateForm = this.fb.group({
subject: ['', Validators.required],
body: ['', Validators.required]
});
this.microsoftTeamsTemplateForm = this.fb.group({
subject: [''],
body: ['', Validators.required],
themeColor: [''],
button: this.createButtonConfigForm()
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.WEB}), {emitEvent: false});
});
this.deliveryMethodFormsMap = new Map<NotificationDeliveryMethod, FormGroup>([
[NotificationDeliveryMethod.WEB, this.webTemplateForm],
[NotificationDeliveryMethod.EMAIL, this.emailTemplateForm],
[NotificationDeliveryMethod.SMS, this.smsTemplateForm],
[NotificationDeliveryMethod.SLACK, this.slackTemplateForm],
[NotificationDeliveryMethod.MICROSOFT_TEAMS, this.microsoftTeamsTemplateForm],
[NotificationDeliveryMethod.MOBILE_APP, this.mobileTemplateForm]
]);
this.deliveryConfiguration = this.templateNotificationForm.get('configuration.deliveryMethodsTemplates').value;
}
ngOnDestroy() {
@ -169,58 +99,8 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
}
protected getNotificationTemplateValue(): NotificationTemplate {
const template: NotificationTemplate = deepClone(this.templateNotificationForm.value);
this.notificationDeliveryMethods.forEach(method => {
if (template.configuration.deliveryMethodsTemplates[method]?.enabled) {
Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method});
} else {
delete template.configuration.deliveryMethodsTemplates[method];
}
});
const template = deepClone(this.templateNotificationForm.value);
template.configuration = deepClone(this.notificationTemplateConfigurationForm.value);
return deepTrim(template);
}
private createButtonConfigForm(): FormGroup {
const form = this.fb.group({
enabled: [false],
text: [{value: '', disabled: true}, [Validators.required, Validators.maxLength(50)]],
linkType: [ActionButtonLinkType.LINK],
link: [{value: '', disabled: true}, Validators.required],
dashboardId: [{value: null, disabled: true}, Validators.required],
dashboardState: [{value: null, disabled: true}],
setEntityIdInState: [{value: true, disabled: true}],
});
form.get('enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
form.enable({emitEvent: false});
form.get('linkType').updateValueAndValidity({onlySelf: true});
} else {
form.disable({emitEvent: false});
form.get('enabled').enable({emitEvent: false});
}
});
form.get('linkType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
const isEnabled = form.get('enabled').value;
if (isEnabled) {
if (value === ActionButtonLinkType.LINK) {
form.get('link').enable({emitEvent: false});
form.get('dashboardId').disable({emitEvent: false});
form.get('dashboardState').disable({emitEvent: false});
form.get('setEntityIdInState').disable({emitEvent: false});
} else {
form.get('link').disable({emitEvent: false});
form.get('dashboardId').enable({emitEvent: false});
form.get('dashboardState').enable({emitEvent: false});
form.get('setEntityIdInState').enable({emitEvent: false});
}
}
});
return form;
}
}

341
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html

@ -29,14 +29,14 @@
</mat-progress-bar>
<div mat-dialog-content>
<mat-horizontal-stepper linear #notificationTemplateStepper
labelPosition="bottom"
labelPosition="end"
[orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon>
</ng-template>
<mat-step [stepControl]="templateNotificationForm">
<ng-template matStepLabel>{{ 'notification.basic-settings' | translate }}</ng-template>
<ng-template matStepLabel>{{ 'notification.setup' | translate }}</ng-template>
<form [formGroup]="templateNotificationForm" style="padding-bottom: 16px;">
<mat-form-field class="mat-block">
<mat-label translate>notification.name</mat-label>
@ -62,339 +62,22 @@
class="delivery-method-container"
[formGroupName]="deliveryMethods">
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}
</mat-slide-toggle>
</section>
</section>
</section>
</form>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.WEB.enabled').value"
[stepControl]="webTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.web' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="webTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="webTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="webTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
[color]="webTemplateForm.get('additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="actionButtonConfig">
<mat-expansion-panel class="tb-settings" [expanded]="webTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
{length: webTemplateForm.get('additionalConfig.actionButtonConfig.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</section>
</form>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.EMAIL.enabled').value"
[stepControl]="emailTemplateForm" #emailStep="matStep">
<ng-template matStepLabel>{{ 'notification.delivery-method.email' | translate }}</ng-template>
<ng-template matStepContent>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="emailTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="emailTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-label class="tb-title tb-required"
[ngClass]="{'tb-error': (emailStep.interacted || emailTemplateForm.get('body').dirty) && emailTemplateForm.get('body').hasError('required')}"
translate>notification.message</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
<mat-error class="tb-mat-error"
*ngIf="(emailStep.interacted || emailTemplateForm.get('body').dirty) && emailTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</form>
</ng-template>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SMS.enabled').value"
[stepControl]="smsTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.sms' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="smsTemplateForm">
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="smsTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="smsTemplateForm.get('body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: smsTemplateForm.get('body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</form>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.slack' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="slackTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="slackTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
</form>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.MICROSOFT_TEAMS.enabled').value"
[stepControl]="microsoftTeamsTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.microsoft-teams' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="microsoftTeamsTemplateForm">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between">
<div translate>notification.theme-color</div>
<tb-color-input asBoxInput formControlName="themeColor"></tb-color-input>
</div>
<div class="tb-form-panel tb-slide-toggle stroked" formGroupName="button">
<mat-expansion-panel class="tb-settings" [expanded]="microsoftTeamsTemplateForm.get('button.enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
{length: microsoftTeamsTemplateForm.get('button.text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="30" fxFlex.xs="100">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="microsoftTeamsTemplateForm.get('button.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="microsoftTeamsTemplateForm.get('button.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35" fxFlex.xs="100"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35" fxFlex.xs="100"
[dashboardId]="microsoftTeamsTemplateForm.get('button.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="microsoftTeamsTemplateForm.get('button.linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</form>
</mat-step>
<mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.MOBILE_APP.enabled').value"
[stepControl]="mobileTemplateForm">
<ng-template matStepLabel>{{ 'notification.delivery-method.mobile-app' | translate }}</ng-template>
<div class="tb-hint-available-params mat-body-2">
<span class="content">{{ 'notification.input-field-support-templatization' | translate}}</span>
<span tb-help-popup="{{ notificationTemplateTypeTranslateMap.get(templateNotificationForm.get('notificationType').value).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '800px'}"
trigger-text="{{ 'notification.see-documentation' | translate }}"></span>
</div>
<form [formGroup]="mobileTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="mobileTemplateForm.get('subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="mobileTemplateForm.get('body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<mat-step [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="templateNotificationForm.get('notificationType').value"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</mat-step>
</mat-horizontal-stepper>

25
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss

@ -17,7 +17,7 @@
@import "../../../../../../theme";
:host {
width: 840px;
width: 775px;
height: 100%;
max-width: 100%;
max-height: 100vh;
@ -62,25 +62,10 @@
}
}
.tb-mat-error {
font-size: 13px;
}
.tb-hint {
padding: 0 0 8px;
}
.tb-hint-available-params {
border-radius: 6px;
background-color: rgba(48, 86, 128, 0.04);
margin-bottom: 8px;
padding: 8px 16px;
.content {
vertical-align: middle;
}
}
.delivery-methods-container {
margin-bottom: 20px;
display: flex;
@ -125,13 +110,5 @@
}
}
}
.tb-form-panel .mat-expansion-panel.tb-settings {
padding: 11px 16px;
& > .mat-expansion-panel-content > .mat-expansion-panel-body {
gap: 0;
}
}
}
}

25
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts

@ -14,13 +14,13 @@
/// limitations under the License.
///
import { NotificationDeliveryMethod, NotificationTemplate, NotificationType } from '@shared/models/notification.models';
import { NotificationTemplate, NotificationType } from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
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 { FormBuilder } from '@angular/forms';
import { FormBuilder, FormGroup } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { Observable } from 'rxjs';
@ -31,8 +31,7 @@ import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
import { TemplateConfiguration } from '@home/pages/notification/template/template-configuration';
import { AuthState } from '@core/auth/auth.models';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { AuthUser } from '@shared/models/user.model';
import { Authority } from '@shared/models/authority.enum';
@ -62,9 +61,10 @@ export class TemplateNotificationDialogComponent
selectedIndex = 0;
hideSelectType = false;
notificationTemplateConfigurationForm: FormGroup;
private readonly templateNotification: NotificationTemplate;
private authState: AuthState = getCurrentAuthState(this.store);
private authUser: AuthUser = this.authState.authUser;
private authUser: AuthUser = getCurrentAuthUser(this.store);
constructor(protected store: Store<AppState>,
protected router: Router,
@ -78,7 +78,7 @@ export class TemplateNotificationDialogComponent
this.notificationTypes = this.allowNotificationType();
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm'])
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
if (isDefinedAndNotNull(this.data?.predefinedType)) {
@ -99,11 +99,9 @@ export class TemplateNotificationDialogComponent
}
this.templateNotificationForm.reset({}, {emitEvent: false});
this.templateNotificationForm.patchValue(this.templateNotification, {emitEvent: false});
// eslint-disable-next-line guard-for-in
for (const method in this.templateNotification.configuration.deliveryMethodsTemplates) {
this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method])
.patchValue(this.templateNotification.configuration.deliveryMethodsTemplates[method]);
}
this.notificationTemplateConfigurationForm.patchValue({
deliveryMethodsTemplates: this.templateNotification.configuration.deliveryMethodsTemplates
}, {emitEvent: false});
}
}
@ -119,6 +117,9 @@ export class TemplateNotificationDialogComponent
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
if ($event.previouslySelectedIndex > $event.selectedIndex) {
$event.previouslySelectedStep.interacted = false;
}
}
backStep() {

2
ui-ngx/src/app/shared/components/notification/template-autocomplete.component.html

@ -51,7 +51,7 @@
<span class="template-option-name" [innerHTML]="template.name | highlight:searchText"></span>
<mat-chip-set>
<mat-chip disabled *ngFor="let method of template.configuration.deliveryMethodsTemplates | keyvalue">
{{ notificationDeliveryMethodTranslateMap.get(method.key) | translate }}
{{ notificationDeliveryMethodInfoMap.get(method.key).name | translate }}
</mat-chip>
</mat-chip-set>
</mat-option>

4
ui-ngx/src/app/shared/components/notification/template-autocomplete.component.ts

@ -28,7 +28,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { emptyPageData } from '@shared/models/page/page-data';
import {
NotificationDeliveryMethodTranslateMap,
NotificationDeliveryMethodInfoMap,
NotificationTemplate,
NotificationType
} from '@shared/models/notification.models';
@ -55,7 +55,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
})
export class TemplateAutocompleteComponent implements ControlValueAccessor, OnInit {
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
notificationDeliveryMethodInfoMap = NotificationDeliveryMethodInfoMap;
selectTemplateFormGroup: FormGroup;
@Input()

7
ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.ts

@ -24,11 +24,7 @@ import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import {
NotificationDeliveryMethodTranslateMap,
SlackChanelType,
SlackConversation
} from '@shared/models/notification.models';
import { SlackChanelType, SlackConversation } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { isEqual } from '@core/utils';
@ -44,7 +40,6 @@ import { isEqual } from '@core/utils';
})
export class SlackConversationAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges {
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
conversationSlackFormGroup: FormGroup;
@Input()

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

@ -311,9 +311,11 @@ export interface NotificationTemplate extends Omit<BaseData<NotificationTemplate
}
interface NotificationTemplateConfig {
deliveryMethodsTemplates: {
[key in NotificationDeliveryMethod]: DeliveryMethodNotificationTemplate
};
deliveryMethodsTemplates: DeliveryMethodsTemplates;
}
export type DeliveryMethodsTemplates = {
[key in NotificationDeliveryMethod]: DeliveryMethodNotificationTemplate
}
export interface DeliveryMethodNotificationTemplate extends
@ -376,20 +378,52 @@ export enum NotificationStatus {
export enum NotificationDeliveryMethod {
WEB = 'WEB',
MOBILE_APP = 'MOBILE_APP',
SMS = 'SMS',
EMAIL = 'EMAIL',
MICROSOFT_TEAMS = 'MICROSOFT_TEAMS',
SLACK = 'SLACK',
MOBILE_APP = 'MOBILE_APP',
MICROSOFT_TEAMS = 'MICROSOFT_TEAMS'
EMAIL = 'EMAIL',
}
export interface NotificationDeliveryMethodInfo {
name: string;
icon: string;
}
export const NotificationDeliveryMethodTranslateMap = new Map<NotificationDeliveryMethod, string>([
[NotificationDeliveryMethod.WEB, 'notification.delivery-method.web'],
[NotificationDeliveryMethod.SMS, 'notification.delivery-method.sms'],
[NotificationDeliveryMethod.EMAIL, 'notification.delivery-method.email'],
[NotificationDeliveryMethod.SLACK, 'notification.delivery-method.slack'],
[NotificationDeliveryMethod.MOBILE_APP, 'notification.delivery-method.mobile-app'],
[NotificationDeliveryMethod.MICROSOFT_TEAMS, 'notification.delivery-method.microsoft-teams']
export const NotificationDeliveryMethodInfoMap = new Map<NotificationDeliveryMethod, NotificationDeliveryMethodInfo>([
[NotificationDeliveryMethod.WEB,
{
name: 'notification.delivery-method.web',
icon: 'mdi:bell-badge'
}
],
[NotificationDeliveryMethod.SMS,
{
name: 'notification.delivery-method.sms',
icon: 'mdi:message-processing'
}
],
[NotificationDeliveryMethod.EMAIL,
{
name: 'notification.delivery-method.email',
icon: 'mdi:email'
}],
[NotificationDeliveryMethod.SLACK,
{
name: 'notification.delivery-method.slack',
icon: 'mdi:slack'
}
],
[NotificationDeliveryMethod.MOBILE_APP,
{
name: 'notification.delivery-method.mobile-app',
icon: 'mdi:cellphone-text'
}],
[NotificationDeliveryMethod.MICROSOFT_TEAMS,
{
name: 'notification.delivery-method.microsoft-teams',
icon: 'mdi:microsoft-teams'
}]
]);
export enum NotificationRequestStatus {

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

@ -3233,7 +3233,6 @@
"new-platform-version-trigger-settings": "New platform version trigger settings",
"rate-limits-trigger-settings": "Exceeded rate limits trigger settings",
"at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings",
"button-text": "Button text",
"button-text-required": "Button text is required",
"button-text-max-length": "Button text should be less than or equal to {{ length }} characters",
@ -3245,6 +3244,7 @@
"copy-template": "Copy template",
"create-new": "Create new",
"created": "Created",
"customize-messages": "Customize messages",
"delete-notification-text": "Be careful, after the confirmation the notification will become unrecoverable.",
"delete-notification-title": "Are you sure you want to delete the notification?",
"delete-notifications-text": "Be careful, after the confirmation notifications will become unrecoverable.",
@ -3353,6 +3353,7 @@
"notify-only-user-comments": "Notify only user comments",
"only-rule-chain-lifecycle-failures": "Only rule chain lifecycle failures",
"only-rule-node-lifecycle-failures": "Only rule node lifecycle failures",
"open-dashboard-on-click-notification": "Open dashboard on click notification",
"platform-users": "Platform users",
"rate-limits": "Rate limits",
"rate-limits-hint": "If the field is empty, the trigger will be applied to all rate limits",
@ -3405,6 +3406,7 @@
"selected-template": "{ count, plural, =1 {1 template} other {# templates} } selected",
"send-notification": "Send notification",
"sent": "Sent",
"setup": "Setup",
"notification-sent": "Notifications / Sent",
"set-entity-from-notification": "Set entity from notification to dashboard state",
"slack-chanel-type": "Slack channel type",

Loading…
Cancel
Save