Browse Source

UI: Add settings for slack delivery method in notification template

pull/8046/head
Vladyslav_Prykhodko 4 years ago
parent
commit
9e35f4bd0f
  1. 1
      ui-ngx/src/app/core/ws/notification-websocket.service.ts
  2. 9
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  3. 53
      ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.html
  4. 26
      ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.scss
  5. 249
      ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.ts
  6. 129
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html
  7. 71
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss
  8. 65
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts
  9. 31
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts
  10. 8
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts
  11. 16
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts
  12. 25
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html
  13. 5
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.scss
  14. 5
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  15. 7
      ui-ngx/src/app/shared/models/notification-ws.models.ts
  16. 14
      ui-ngx/src/app/shared/models/notification.models.ts
  17. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
ui-ngx/src/app/core/ws/notification-websocket.service.ts

@ -161,7 +161,6 @@ export class NotificationWebsocketService implements NotificationWsService {
private publishCommands() {
while (this.isOpened && this.cmdsWrapper.hasCommands()) {
this.dataStream.next(this.cmdsWrapper.preparePublishCommands());
this.cmdsWrapper.clear();
this.checkToClose();
}
this.tryOpenSocket();

9
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -173,6 +173,9 @@ import { RateLimitsDetailsDialogComponent } from '@home/components/profile/tenan
import { AssetProfileComponent } from '@home/components/profile/asset-profile.component';
import { AssetProfileDialogComponent } from '@home/components/profile/asset-profile-dialog.component';
import { AssetProfileAutocompleteComponent } from '@home/components/profile/asset-profile-autocomplete.component';
import {
SlackConversationAutocompleteComponent
} from '@home/components/notification/slack-conversation-autocomplete.component';
@NgModule({
declarations:
@ -316,7 +319,8 @@ import { AssetProfileAutocompleteComponent } from '@home/components/profile/asse
RateLimitsListComponent,
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent
RateLimitsDetailsDialogComponent,
SlackConversationAutocompleteComponent
],
imports: [
CommonModule,
@ -453,7 +457,8 @@ import { AssetProfileAutocompleteComponent } from '@home/components/profile/asse
RateLimitsListComponent,
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent
RateLimitsDetailsDialogComponent,
SlackConversationAutocompleteComponent
],
providers: [
WidgetComponentService,

53
ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.html

@ -0,0 +1,53 @@
<!--
Copyright © 2016-2022 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="conversationSlackFormGroup" class="mat-block">
<mat-label>{{ labelText }}</mat-label>
<input matInput type="text"
#slackInput
formControlName="conversation"
(focusin)="onFocus()"
[required]="required"
[matAutocomplete]="slackAutocomplete">
<button *ngIf="conversationSlackFormGroup.get('conversation').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete"
#slackAutocomplete="matAutocomplete"
[displayWith]="displayTemplateFn"
>
<mat-option *ngFor="let template of slackConversation$ | async" [value]="template" class="template-option">
<span [innerHTML]="template.name | highlight:slackSearchText"></span>
</mat-option>
<mat-option *ngIf="!(slackConversation$ | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(slackSearchText); else searchNotEmpty">
<span translate>notification.not-found-slack-recipient</span>
</div>
<ng-template #searchNotEmpty>
<span>Not slack recipient matches {{ truncate.transform(slackSearchText, true, 6, &apos;...&apos;) }}</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="conversationSlackFormGroup.get('conversation').hasError('required')">
{{ requiredText }}
</mat-error>
</mat-form-field>

26
ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.scss

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
::ng-deep {
.slack-option {
.mat-option-text {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 4px;
}
}
}

249
ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.ts

@ -0,0 +1,249 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Observable, of } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
map,
publishReplay,
refCount,
share,
switchMap,
tap
} from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import {
NotificationDeliveryMethodTranslateMap,
SlackChanelType,
SlackConversation
} from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { isEqual } from '@core/utils';
@Component({
selector: 'tb-slack-conversation-autocomplete',
templateUrl: './slack-conversation-autocomplete.component.html',
styleUrls: ['./slack-conversation-autocomplete.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SlackConversationAutocompleteComponent),
multi: true
}]
})
export class SlackConversationAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges {
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
conversationSlackFormGroup: FormGroup;
@Input()
labelText = this.translate.instant('notification.conversation');
@Input()
requiredText = this.translate.instant('notification.conversation-required');
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@Input()
slackChanelType: SlackChanelType;
@ViewChild('slackInput', {static: true}) slackInput: ElementRef;
slackConversation$: Observable<Array<SlackConversation>>;
slackSearchText = '';
private modelValue: string | null;
private dirty = false;
private latestSearchConversetionResult: Array<SlackConversation> = null;
private slackConversetionFetchObservable$: Observable<Array<SlackConversation>> = null;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
public truncate: TruncatePipe,
private entityService: EntityService,
private notificationService: NotificationService,
private fb: FormBuilder) {
this.conversationSlackFormGroup = this.fb.group({
conversation: ['']
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
if (this.required) {
this.conversationSlackFormGroup.get('conversation').addValidators(Validators.required);
this.conversationSlackFormGroup.get('conversation').updateValueAndValidity({emitEvent: false});
}
this.slackConversation$ = this.conversationSlackFormGroup.get('conversation').valueChanges
.pipe(
debounceTime(150),
tap(value => {
let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = value.id;
}
this.updateView(modelValue);
if (value === null) {
this.clear();
}
}),
map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
switchMap(name => this.fetchSlackConversation(name)),
share()
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'slackChanelType') {
this.clearSlackCache();
this.conversationSlackFormGroup.get('conversation').patchValue('');
}
}
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.conversationSlackFormGroup.disable({emitEvent: false});
} else {
this.conversationSlackFormGroup.enable({emitEvent: false});
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
writeValue(value: EntityId | null): void {
this.slackSearchText = '';
if (value != null) {
// this.notificationService.getNotificationTemplateById(value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe(
// (entity) => {
// this.modelValue = entity.id;
// this.conversationSlackFormGroup.get('conversation').patchValue(entity, {emitEvent: false});
// },
// () => {
// this.modelValue = null;
// this.conversationSlackFormGroup.get('conversation').patchValue('', {emitEvent: false});
// if (value !== null) {
// this.propagateChange(this.modelValue);
// }
// }
// );
} else {
this.modelValue = null;
this.conversationSlackFormGroup.get('conversation').patchValue('', {emitEvent: false});
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.conversationSlackFormGroup.get('conversation').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
updateView(value: string | null) {
if (!isEqual(this.modelValue, value)) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayTemplateFn(slackConversation?: SlackConversation): string | undefined {
return slackConversation ? slackConversation.name : undefined;
}
private fetchSlackConversation(searchText?: string): Observable<Array<SlackConversation>> {
if (this.slackSearchText !== searchText || this.latestSearchConversetionResult === null) {
this.slackSearchText = searchText;
const slackConversationFilter = this.createSlackConversationFilter(this.slackSearchText);
return this.getSlackConversationByType().pipe(
map(name => name.filter(slackConversationFilter)),
tap(res => this.latestSearchConversetionResult = res)
);
}
return of(this.latestSearchConversetionResult);
}
private getSlackConversationByType() {
if (this.slackConversetionFetchObservable$ === null) {
let fetchObservable: Observable<Array<SlackConversation>>;
if (this.slackChanelType) {
fetchObservable = this.notificationService.listSlackConversations(this.slackChanelType, {ignoreLoading: true});
} else {
fetchObservable = of([]);
}
this.slackConversetionFetchObservable$ = fetchObservable.pipe(
publishReplay(1),
refCount()
);
}
return this.slackConversetionFetchObservable$;
}
private createSlackConversationFilter(query: string): (key: SlackConversation) => boolean {
const lowercaseQuery = query.toLowerCase();
return key => key.name.toLowerCase().includes(lowercaseQuery);
}
private clearSlackCache(): void {
this.latestSearchConversetionResult = null;
this.slackConversetionFetchObservable$ = null;
}
clear() {
this.conversationSlackFormGroup.get('conversation').patchValue('', {emitEvent: true});
setTimeout(() => {
this.slackInput.nativeElement.blur();
this.slackInput.nativeElement.focus();
}, 0);
}
}

129
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html

@ -16,47 +16,92 @@
-->
<div class="tb-notification-center">
<div class="tb-notification-center-content">
<div class="tabs-container">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab label="{{ 'notification.inbox' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
<tb-notification-table
#notificationRequest
[notificationType]="entityType.NOTIFICATION_REQUEST">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.templates' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TEMPLATE">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.targets' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TARGET">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.rules' | translate }}">
<ng-template matTabContent>
Content 1 - Loaded
</ng-template>
</mat-tab>
<mat-tab disabled>
<ng-template mat-tab-label>
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<button mat-flat-button color="primary" (click)="sendNotification($event)">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }}
</div>
</button>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
<mat-drawer-container class="tb-absolute-fill tb-notification-draw-container">
<div class="tb-notification-center-content">
<div class="tabs-container">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab label="{{ 'notification.inbox' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION"
(toggleSettings)="toggleNotificationSettings()">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
<tb-notification-table
#notificationRequest
[notificationType]="entityType.NOTIFICATION_REQUEST">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.templates' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TEMPLATE">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.targets' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TARGET">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.rules' | translate }}">
<ng-template matTabContent>
Content 1 - Loaded
</ng-template>
</mat-tab>
<mat-tab disabled>
<ng-template mat-tab-label>
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<button mat-flat-button color="primary" (click)="sendNotification($event)">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }}
</div>
</button>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
<mat-drawer mode="over"
#notificationSettings
position="end"
class="notification-settings">
<div>
<div fxLayout="row" fxLayoutAlign="start center">
<button mat-icon-button (click)="toggleNotificationSettings()">
<mat-icon>chevron_left</mat-icon>
</button>
<h6 class="title">Notification settings</h6>
</div>
<div style="max-width: 450px">
<fieldset class="fields-group">
<legend class="group-title">Current user allow delivery method</legend>
<form [formGroup]="notificationSettingsForm">
<section formGroupName="deliveryMethodsConfigs">
<section *ngFor="let deliveryMethods of notificationDeliveryMethods" [formGroupName]="deliveryMethods">
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
</mat-slide-toggle>
</section>
<section formGroupName="SLACK">
<mat-form-field class="mat-block">
<mat-label>Slack bot token</mat-label>
<input matInput formControlName="botToken" required>
<mat-error *ngIf="notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
</section>
</section>
</form>
</fieldset>
<button mat-raised-button
[disabled]="notificationSettingsForm.pristine || notificationSettingsForm.invalid"
(click)="saveNotificationSettigs($event)"
color="primary">
Save
</button>
</div>
</div>
</mat-drawer>
</mat-drawer-container>
</div>

71
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss

@ -20,11 +20,14 @@
height: 100%;
display: block;
.tb-notification-center {
padding: 8px;
width: 100%;
height: 100%;
display: block;
.tb-notification-draw-container {
margin: 8px;
}
.tb-notification-center-content {
width: 100%;
height: 100%;
@ -55,52 +58,34 @@
flex-direction: column;
flex: 1 1 100%;
}
}
.notification-settings {
width: 100%;
padding: 24px;
.title {
margin: 0;
line-height: 1.4rem;
}
//.mat-toolbar-tools{
// min-height: auto;
//}
//
//.title-container{
// overflow: hidden;
//}
//
//.tb-entity-table-title {
// padding-right: 20px;
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
//}
//
//.table-container {
// overflow: auto;
//}
//
//.tb-entity-table-info{
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
//}
//
//.button-widget-action{
// margin-left: auto;
// overflow: hidden;
// text-overflow: ellipsis;
//}
.fields-group {
padding: 8px 16px 0;
margin: 10px 0;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
color: rgba(0, 0, 0, .7);
}
.delivery-method {
margin-bottom: 8px;
}
}
}
}
//@media #{$mat-xs} {
// .mat-toolbar {
// height: auto;
// min-height: 100px;
//
// .tb-entity-table-title{
// padding-bottom: 5px;
// width: 100%;
// }
// }
//}
@media #{$mat-xs} {
.tb-notification-center {
.tb-notification-center-content {

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

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { AfterViewInit, Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -23,16 +23,30 @@ import {
NotificationTableComponent
} from '@home/pages/notification-center/notification-table/notification-table.component';
import { EntityType } from '@shared/models/entity-type.models';
import { MatDrawer } from '@angular/material/sidenav';
import { NotificationService } from '@core/http/notification.service';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationTemplateTypeTranslateMap
} from '@shared/models/notification.models';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { deepTrim } from '@core/utils';
@Component({
selector: 'tb-notification-center',
templateUrl: './notification-center.component.html',
styleUrls: ['notification-center.component.scss']
})
export class NotificationCenterComponent extends PageComponent {
export class NotificationCenterComponent extends PageComponent implements AfterViewInit {
entityType = EntityType;
notificationSettingsForm: FormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
@ViewChild('notificationSettings', {static: true}) notificationSettings!: MatDrawer;
@ViewChild('matTabGroup', {static: true}) matTabs: MatTabGroup;
@ViewChild('requestTab', {static: true}) requestTab: MatTab;
@ViewChild('notificationRequest', {static: true}) notificationRequestTable: NotificationTableComponent;
@ -40,8 +54,38 @@ export class NotificationCenterComponent extends PageComponent {
constructor(
protected store: Store<AppState>) {
protected store: Store<AppState>,
private notificationService: NotificationService,
private fb: FormBuilder) {
super(store);
this.notificationSettingsForm = this.fb.group({
deliveryMethodsConfigs: this.fb.group({})
});
this.notificationDeliveryMethods.forEach(method => {
(this.notificationSettingsForm.get('deliveryMethodsConfigs') as FormGroup)
.addControl(method, this.fb.group({method, enabled: method !== NotificationDeliveryMethod.SLACK}), {emitEvent: false});
});
(this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK') as FormGroup)
.addControl('botToken', this.fb.control({value: '', disabled: true}, Validators.required), {emitEvent: false});
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.enabled').valueChanges.subscribe(value => {
if (value) {
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').enable({emitEvent: true});
} else {
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').disable({emitEvent: true});
}
});
}
ngAfterViewInit() {
this.notificationSettings.openedStart.subscribe(() => {
this.notificationService.getNotificationSettings().subscribe(
value => {
this.notificationSettingsForm.patchValue(value, {emitEvent: false});
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.enabled').updateValueAndValidity({onlySelf: true});
}
);
});
}
updateData() {
@ -59,4 +103,19 @@ export class NotificationCenterComponent extends PageComponent {
sendNotification($event: Event) {
this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: this.requestTab.isActive ? 'add' : 'add-without-update', entity: null});
}
toggleNotificationSettings() {
this.notificationSettings.toggle().then(() => {});
}
saveNotificationSettigs($event: Event) {
if ($event) {
$event.stopPropagation();
}
const formValue = deepTrim(this.notificationSettingsForm.getRawValue());
this.notificationService.saveNotificationSettings(formValue).subscribe(value => {
this.notificationSettingsForm.patchValue(value, {emitEvent: false});
this.notificationSettingsForm.markAsPristine();
});
}
}

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

@ -32,12 +32,14 @@ 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 { EventEmitter } from '@angular/core';
export class InboxTableConfig extends EntityTableConfig<Notification> {
constructor(private notificationService: NotificationService,
private translate: TranslateService,
private datePipe: DatePipe) {
private datePipe: DatePipe,
private toggleSettings: EventEmitter<void>) {
super();
this.entitiesDeleteEnabled = false;
this.entityTranslations = {
@ -58,12 +60,20 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
this.headerComponent = InboxTableHeaderComponent;
this.headerActionDescriptors = [{
name: this.translate.instant('notification.mark-all-as-read'),
icon: 'done_all',
isEnabled: () => true,
onAction: $event => this.markAllRead($event)
}];
this.headerActionDescriptors = [
{
name: 'Notification setting',
icon: 'settings',
isEnabled: () => true,
onAction: $event => this.togleNotificationSettings($event)
},
{
name: this.translate.instant('notification.mark-all-as-read'),
icon: 'done_all',
isEnabled: () => true,
onAction: $event => this.markAllRead($event)
}
];
this.columns.push(
new DateEntityTableColumn<Notification>('createdTime', 'notification.created-time', this.datePipe, '150px'),
@ -97,6 +107,13 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
});
}
private togleNotificationSettings($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.toggleSettings.emit();
}
private markAsRead($event, entity){
if ($event) {
$event.stopPropagation();

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

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { NotificationService } from '@core/http/notification.service';
import { TargetsTableConfig } from '@home/pages/notification-center/notification-table/targets-table-config';
@ -36,6 +36,9 @@ export class NotificationTableComponent implements OnInit {
@Input()
notificationType = EntityType.NOTIFICATION;
@Output()
toggleSettings: EventEmitter<void> = new EventEmitter<void>();
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
entityTableConfig: EntityTableConfig<any>;
@ -70,7 +73,8 @@ export class NotificationTableComponent implements OnInit {
return new InboxTableConfig(
this.notificationService,
this.translate,
this.datePipe
this.datePipe,
this.toggleSettings
);
case EntityType.NOTIFICATION_TEMPLATE:
return new TemplateTableConfig(

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

@ -173,10 +173,6 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
}
}
reset() {
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false});
}
updateView(value: EntityId | null) {
if (!isEqual(this.modelValue, value)) {
this.modelValue = value;
@ -184,8 +180,8 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
}
}
displayTemplateFn(queue?: NotificationTemplate): string | undefined {
return queue ? queue.name : undefined;
displayTemplateFn(template?: NotificationTemplate): string | undefined {
return template ? template.name : undefined;
}
fetchTemplate(searchText?: string): Observable<Array<NotificationTemplate>> {
@ -202,14 +198,6 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
);
}
getDescription(value) {
return value.additionalInfo?.description ? value.additionalInfo.description :
this.translate.instant(
'queue.alt-description',
{submitStrategy: value.submitStrategy.type, processingStrategy: value.processingStrategy.type}
);
}
clear() {
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: true});
setTimeout(() => {

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

@ -162,22 +162,23 @@
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template>
<form [formGroup]="slackTemplateForm">
<form [formGroup]="slackTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.message</mat-label>
<input matInput formControlName="body" [placeholder]="templateNotificationForm.get('configuration.defaultTextTemplate').value">
</mat-form-field>
<mat-form-field class="mat-block" floatLabel="always">
<mat-label>Conversation type</mat-label>
<input matInput formControlName="conversationType">
</mat-form-field>
<mat-form-field class="mat-block" floatLabel="always">
<mat-label>Conversation id</mat-label>
<input matInput formControlName="conversationId" required>
<mat-error *ngIf="slackTemplateForm.get('conversationId').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<label>{{ 'Recipient type' | translate }}</label>
<mat-radio-group formControlName="conversationType">
<mat-radio-button *ngFor="let slackChanelType of slackChanelTypes" [value]="slackChanelType">
<div fxLayout="column">
<div>{{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}</div>
</div>
</mat-radio-button>
</mat-radio-group>
<tb-slack-conversation-autocomplete
formControlName="conversationId" required
[slackChanelType]="slackTemplateForm.get('conversationType').value">
</tb-slack-conversation-autocomplete>
</form>
</mat-step>
</mat-horizontal-stepper>

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

@ -14,6 +14,7 @@
* limitations under the License.
*/
@import "../../../../../../scss/constants";
@import "../../../../../../theme";
:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
width: 720px;
@ -110,6 +111,10 @@
&:not(:last-child){
margin-right: 8px;
}
&.mat-radio-checked {
border-color: map_get($tb-accent, 500);
}
}
}
}

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

@ -20,7 +20,8 @@ import {
NotificationTemplate,
NotificationTemplateTypeTranslateMap,
NotificationType,
SlackChanelType
SlackChanelType,
SlackChanelTypesTranslateMap
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
@ -65,9 +66,11 @@ export class TemplateNotificationDialogComponent
saveButtonLabel = 'action.save';
notificationTypes = Object.keys(NotificationType) as NotificationType[];
slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[];
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap;
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
slackChanelTypesTranslateMap = SlackChanelTypesTranslateMap;
selectedIndex = 0;

7
ui-ngx/src/app/shared/models/notification-ws.models.ts

@ -236,13 +236,14 @@ export class NotificationPluginCmdsWrapper {
unsubCmd: UnsubscribeCmd;
markAsReadCmd: MarkAsReadCmd;
markAllAsReadCmd: MarkAllAsReadCmd;
#hasSend = false;
public hasCommands(): boolean {
return isDefinedAndNotNull(this.unreadCountSubCmd) ||
return !this.#hasSend && (isDefinedAndNotNull(this.unreadCountSubCmd) ||
isDefinedAndNotNull(this.unreadSubCmd) ||
isDefinedAndNotNull(this.unsubCmd) ||
isDefinedAndNotNull(this.markAsReadCmd) ||
isDefinedAndNotNull(this.markAllAsReadCmd);
isDefinedAndNotNull(this.markAllAsReadCmd));
}
public clear() {
@ -251,6 +252,7 @@ export class NotificationPluginCmdsWrapper {
this.unsubCmd = null;
this.markAsReadCmd = null;
this.markAllAsReadCmd = null;
this.#hasSend = false;
}
public preparePublishCommands(): NotificationPluginCmdsWrapper {
@ -260,6 +262,7 @@ export class NotificationPluginCmdsWrapper {
preparedWrapper.unsubCmd = this.unsubCmd || undefined;
preparedWrapper.markAsReadCmd = this.markAsReadCmd || undefined;
preparedWrapper.markAllAsReadCmd = this.markAllAsReadCmd || undefined;
this.#hasSend = true;
return preparedWrapper;
}
}

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

@ -80,14 +80,18 @@ export interface NotificationRequestConfig {
}
export interface NotificationSettings {
deliveryMethodsConfigs: Map<NotificationDeliveryMethod, NotificationDeliveryMethodConfig>;
deliveryMethodsConfigs: { [key in NotificationDeliveryMethod]: NotificationDeliveryMethodConfig };
}
export interface NotificationDeliveryMethodConfig {
export interface NotificationDeliveryMethodConfig extends Partial<SlackNotificationDeliveryMethodConfig>{
enabled: boolean;
method: NotificationDeliveryMethod;
}
interface SlackNotificationDeliveryMethodConfig {
botToken: string;
}
export interface SlackConversation {
id: string;
name: string;
@ -226,6 +230,12 @@ export enum SlackChanelType {
PRIVATE_CHANNEL = 'PRIVATE_CHANNEL'
}
export const SlackChanelTypesTranslateMap = new Map<SlackChanelType, string>([
[SlackChanelType.DIRECT, 'notification.slack-chanel-types.direct'],
[SlackChanelType.PUBLIC_CHANNEL, 'notification.slack-chanel-types.public-channel'],
[SlackChanelType.PRIVATE_CHANNEL, 'notification.slack-chanel-types.private-channel']
]);
export enum NotificationTargetConfigType {
USER_LIST = 'USER_LIST',
CUSTOMER_USERS = 'CUSTOMER_USERS',

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

@ -2680,6 +2680,8 @@
"button-text": "Button text",
"button-text-required": "Button text is required",
"compose": "Compose",
"conversation": "Conversation",
"conversation-required": "Conversation is required",
"copy-notification-template": "Copy notification template",
"create-target": "Create recipient",
"created-time": "Created time",
@ -2716,6 +2718,7 @@
"no-notification-templates": "No notification templates",
"no-notifications-yet": "No notifications yet",
"no-targets-notification": "No recipients notification",
"not-found-slack-recipient": "Not found slack recipient",
"notification": "Notification",
"notification-center": "Notification center",
"notification-target": "Notification recipient",
@ -2734,6 +2737,12 @@
"send-test-notification": "Send test notification",
"sent": "Sent",
"settings": "Notification settings",
"slack-chanel-type": "Slack chanel type",
"slack-chanel-types": {
"direct": "Direct message",
"private-channel": "Private Channel",
"public-channel": "Public Channel"
},
"slack-settings": "Slack settings",
"sms-settings": "SMS settings",
"status": "Status",

Loading…
Cancel
Save