diff --git a/ui-ngx/src/app/core/ws/notification-websocket.service.ts b/ui-ngx/src/app/core/ws/notification-websocket.service.ts index 020bdf4615..5cced2a371 100644 --- a/ui-ngx/src/app/core/ws/notification-websocket.service.ts +++ b/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(); diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 7cfcf269de..4619a4dcf7 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/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, diff --git a/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.html new file mode 100644 index 0000000000..21093f74d8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.html @@ -0,0 +1,53 @@ + + + {{ labelText }} + + + + + + + +
+
+ notification.not-found-slack-recipient +
+ + Not slack recipient matches {{ truncate.transform(slackSearchText, true, 6, '...') }} + +
+
+
+ + {{ requiredText }} + +
diff --git a/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.scss b/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.scss new file mode 100644 index 0000000000..251a6b5466 --- /dev/null +++ b/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; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/notification/slack-conversation-autocomplete.component.ts new file mode 100644 index 0000000000..087dcb2674 --- /dev/null +++ b/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>; + + slackSearchText = ''; + + private modelValue: string | null; + private dirty = false; + private latestSearchConversetionResult: Array = null; + private slackConversetionFetchObservable$: Observable> = null; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + 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> { + 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>; + 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); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html index bd7925d7a1..529fa91afd 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html @@ -16,47 +16,92 @@ -->
-
-
- - - - - - - - - - - - - - - - - - - - Content 1 - Loaded - - - - -
- -
-
-
-
+ +
+
+ + + + + + + + + + + + + + + + + + + + Content 1 - Loaded + + + + +
+ +
+
+
+
+
-
+ +
+
+ +
Notification settings
+
+
+
+ Current user allow delivery method +
+
+
+ + {{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }} + +
+
+ + Slack bot token + + + {{ 'notification.link-required' | translate }} + + +
+
+
+
+ +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss index e3726e4c02..dae717255b 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss +++ b/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 { diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts index 64635cb470..6eceb003c6 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts +++ b/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) { + protected store: Store, + 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(); + }); + } } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts index 482100b168..247206ff68 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts +++ b/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 { constructor(private notificationService: NotificationService, private translate: TranslateService, - private datePipe: DatePipe) { + private datePipe: DatePipe, + private toggleSettings: EventEmitter) { super(); this.entitiesDeleteEnabled = false; this.entityTranslations = { @@ -58,12 +60,20 @@ export class InboxTableConfig extends EntityTableConfig { 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('createdTime', 'notification.created-time', this.datePipe, '150px'), @@ -97,6 +107,13 @@ export class InboxTableConfig extends EntityTableConfig { }); } + private togleNotificationSettings($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.toggleSettings.emit(); + } + private markAsRead($event, entity){ if ($event) { $event.stopPropagation(); diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts index a07b5dbb46..810b27481e 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts +++ b/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 = new EventEmitter(); + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; entityTableConfig: EntityTableConfig; @@ -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( diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts index 31333546c0..ee9efcb0be 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts +++ b/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> { @@ -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(() => { diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html index 1325edb0c8..089243d242 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html @@ -162,22 +162,23 @@ {{ 'notification.slack-settings' | translate }} -
+ notification.message - - Conversation type - - - - Conversation id - - - {{ 'notification.subject-required' | translate }} - - + + + +
+
{{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}
+
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.scss index 491796c817..4974d6f540 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.scss +++ b/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); + } } } } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts index ca20facc34..b451ddd811 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts +++ b/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; diff --git a/ui-ngx/src/app/shared/models/notification-ws.models.ts b/ui-ngx/src/app/shared/models/notification-ws.models.ts index ef077097d6..29581eddad 100644 --- a/ui-ngx/src/app/shared/models/notification-ws.models.ts +++ b/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; } } diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 83aa994c3c..ab6ae3a6c6 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -80,14 +80,18 @@ export interface NotificationRequestConfig { } export interface NotificationSettings { - deliveryMethodsConfigs: Map; + deliveryMethodsConfigs: { [key in NotificationDeliveryMethod]: NotificationDeliveryMethodConfig }; } -export interface NotificationDeliveryMethodConfig { +export interface NotificationDeliveryMethodConfig extends Partial{ 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.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', diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d3c415f278..bbc70552c6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/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",