Browse Source

UI: Add support in web notification action open dashboard

pull/7911/head
Vladyslav_Prykhodko 4 years ago
parent
commit
902cd73000
  1. 1
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts
  2. 56
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html
  3. 11
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalations.component.ts
  4. 1
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  5. 1
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss
  6. 3
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.html
  7. 20
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts
  8. 31
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts
  9. 48
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html
  10. 38
      ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html
  11. 24
      ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts
  12. 25
      ui-ngx/src/app/shared/components/notification/notification.component.ts
  13. 1
      ui-ngx/src/app/shared/components/string-items-list.component.html
  14. 13
      ui-ngx/src/app/shared/components/string-items-list.component.ts
  15. 35
      ui-ngx/src/app/shared/decorators/coerce-boolean.ts
  16. 15
      ui-ngx/src/app/shared/models/notification.models.ts
  17. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -51,7 +51,6 @@ import { EscalationFormComponent } from '@home/pages/notification-center/rule-ta
import { import {
AlarmSeveritiesListComponent AlarmSeveritiesListComponent
} from '@home/pages/notification-center/rule-table/alarm-severities-list.component'; } from '@home/pages/notification-center/rule-table/alarm-severities-list.component';
import { TemplateConfiguration } from '@home/pages/notification-center/template-table/template-configuration';
@NgModule({ @NgModule({
declarations: [ declarations: [

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

@ -112,15 +112,15 @@
<mat-form-field> <mat-form-field>
<mat-label translate>notification.time</mat-label> <mat-label translate>notification.time</mat-label>
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker> <mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true" timeInterval="1"></mat-datetimepicker>
<input required matInput fxFlex formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker"> <input required matInput fxFlex formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker">
</mat-form-field> </mat-form-field>
</div> </div>
</section> </section>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value && <mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.PUSH.enabled').value" notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.PUSH.enabled').value"
[stepControl]="pushTemplateForm"> [stepControl]="pushTemplateForm">
<ng-template matStepLabel>{{ 'notification.web-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.web-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">
@ -158,7 +158,7 @@
{{ 'notification.action-button' | translate }} {{ 'notification.action-button' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value"> <div *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value">
<div fxLayout="row" fxLayoutGap="12px"> <div fxLayout="row" fxLayoutGap="8px">
<mat-form-field class="mat-block" fxFlex> <mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label> <mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required> <input matInput formControlName="text" required>
@ -169,20 +169,42 @@
<tb-color-input formControlName="color" fxFlex> <tb-color-input formControlName="color" fxFlex>
</tb-color-input> </tb-color-input>
</div> </div>
<mat-form-field class="mat-block"> <div fxLayout="row" fxLayoutGap="8px">
<mat-label translate>notification.link</mat-label> <mat-form-field fxFlex="30">
<input matInput formControlName="link" required> <mat-label translate>notification.action-type</mat-label>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')"> <mat-select formControlName="linkType">
{{ 'notification.link-required' | translate }} <mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
</mat-error> {{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-form-field> </mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35"
[dashboardId]="pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
</div> </div>
</section> </section>
</section> </section>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value && <mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.EMAIL.enabled').value" notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.EMAIL.enabled').value"
[stepControl]="emailTemplateForm"> [stepControl]="emailTemplateForm">
<ng-template matStepLabel>{{ 'notification.email-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.email-settings' | translate }}</ng-template>
<ng-template matStepContent> <ng-template matStepContent>
@ -199,8 +221,8 @@
</form> </form>
</ng-template> </ng-template>
</mat-step> </mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value && <mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SMS.enabled').value" notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SMS.enabled').value"
[stepControl]="smsTemplateForm"> [stepControl]="smsTemplateForm">
<ng-template matStepLabel>{{ 'notification.sms-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.sms-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">
@ -219,8 +241,8 @@
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value && <mat-step *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SLACK.enabled').value" notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm"> [stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">

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

@ -29,10 +29,10 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models'; import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
@Component({ @Component({
selector: 'tb-escalations-component', selector: 'tb-escalations-component',
@ -56,14 +56,9 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
escalationsFormGroup: FormGroup; escalationsFormGroup: FormGroup;
newEscalation = false; newEscalation = false;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input() @Input()
set required(value: boolean) { @coerceBoolean()
this.requiredValue = coerceBooleanProperty(value); required: boolean;
}
@Input() @Input()
disabled: boolean; disabled: boolean;

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

@ -90,6 +90,7 @@
<fieldset class="fields-group tb-margin"> <fieldset class="fields-group tb-margin">
<legend translate>notification.filter</legend> <legend translate>notification.filter</legend>
<tb-string-items-list <tb-string-items-list
editable
label="{{ 'alarm.alarm-type-list' | translate }}" label="{{ 'alarm.alarm-type-list' | translate }}"
placeholder="{{ !alarmTemplateForm.get('triggerConfig.alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}" placeholder="{{ !alarmTemplateForm.get('triggerConfig.alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
floatLabel="always" floatLabel="always"

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

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
@import "../../../../../../theme"; @import "../../../../../../theme";
@import "../../../../../../scss/constants";
:host { :host {
width: 700px; width: 700px;

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

@ -16,7 +16,8 @@
--> -->
<mat-form-field [formGroup]="selectTemplateFormGroup" class="mat-block"> <mat-form-field [formGroup]="selectTemplateFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ 'notification.template' | translate }}" <mat-label translate>notification.template</mat-label>
<input matInput type="text"
#templateInput #templateInput
formControlName="templateName" formControlName="templateName"
(focusin)="onFocus()" (focusin)="onFocus()"

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

@ -21,7 +21,6 @@ import { catchError, debounceTime, distinctUntilChanged, map, share, switchMap,
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { TruncatePipe } from '@shared/pipe/truncate.pipe';
@ -41,6 +40,7 @@ import {
} from '@home/pages/notification-center/template-table/template-notification-dialog.component'; } from '@home/pages/notification-center/template-table/template-notification-dialog.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
@Component({ @Component({
selector: 'tb-template-autocomplete', selector: 'tb-template-autocomplete',
@ -57,23 +57,13 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
selectTemplateFormGroup: FormGroup; selectTemplateFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input() @Input()
set required(value: boolean) { @coerceBoolean()
this.requiredValue = coerceBooleanProperty(value); required: boolean;
}
private allowCreateValue = false;
get allowCreate(): boolean {
return this.allowCreateValue;
}
@Input() @Input()
set allowCreate(value: boolean) { @coerceBoolean()
this.allowCreateValue = coerceBooleanProperty(value); allowCreate: boolean = false;
}
@Input() @Input()

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

@ -16,6 +16,8 @@
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { import {
ActionButtonLinkType,
ActionButtonLinkTypeTranslateMap,
NotificationDeliveryMethod, NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap, NotificationDeliveryMethodTranslateMap,
NotificationTemplate, NotificationTemplate,
@ -46,6 +48,10 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap; notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap;
actionButtonLinkType = ActionButtonLinkType;
actionButtonLinkTypes = Object.keys(ActionButtonLinkType) as ActionButtonLinkType[];
actionButtonLinkTypeTranslateMap = ActionButtonLinkTypeTranslateMap;
tinyMceOptions: Record<string, any> = { tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce', base_url: '/assets/tinymce',
suffix: '.min', suffix: '.min',
@ -97,7 +103,10 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
enabled: [false], enabled: [false],
text: [{value: '', disabled: true}, Validators.required], text: [{value: '', disabled: true}, Validators.required],
color: ['#305680'], color: ['#305680'],
link: [{value: '', disabled: true}, Validators.required] linkType: [ActionButtonLinkType.LINK],
link: [{value: '', disabled: true}, Validators.required],
dashboardId: [{value: null, disabled: true}, Validators.required],
dashboardState: [{value: null, disabled: true}]
}), }),
}) })
}); });
@ -116,11 +125,25 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((value) => { ).subscribe((value) => {
if (value) { if (value) {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').updateValueAndValidity({onlySelf: true});
} else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').enable({emitEvent: false});
}
});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value === ActionButtonLinkType.LINK) {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false});
} else { } else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').enable({emitEvent: false});
} }
}); });
@ -150,7 +173,7 @@ export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<
this.destroy$.complete(); this.destroy$.complete();
} }
private atLeastOne() { atLeastOne() {
return (group: FormGroup): ValidationErrors | null => { return (group: FormGroup): ValidationErrors | null => {
let hasAtLeastOne = true; let hasAtLeastOne = true;
if (group?.controls) { if (group?.controls) {

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

@ -28,7 +28,7 @@
</mat-progress-bar> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <div mat-dialog-content>
<mat-horizontal-stepper [linear]="true" labelPosition="end" #notificationTemplateStepper [orientation]="(stepperOrientation | async)" <mat-horizontal-stepper linear labelPosition="end" #notificationTemplateStepper [orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)"> (selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit"> <ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
@ -84,7 +84,7 @@
</section> </section>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.PUSH.enabled').value" <mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.PUSH.enabled').value"
[stepControl]="pushTemplateForm"> [stepControl]="pushTemplateForm">
<ng-template matStepLabel>{{ 'notification.web-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.web-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">
@ -122,7 +122,7 @@
{{ 'notification.action-button' | translate }} {{ 'notification.action-button' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value"> <div *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value">
<div fxLayout="row" fxLayoutGap="12px"> <div fxLayout="row" fxLayoutGap="8px">
<mat-form-field class="mat-block" fxFlex> <mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label> <mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required> <input matInput formControlName="text" required>
@ -133,19 +133,41 @@
<tb-color-input formControlName="color" fxFlex> <tb-color-input formControlName="color" fxFlex>
</tb-color-input> </tb-color-input>
</div> </div>
<mat-form-field class="mat-block"> <div fxLayout="row" fxLayoutGap="8px">
<mat-label translate>notification.link</mat-label> <mat-form-field fxFlex="30">
<input matInput formControlName="link" required> <mat-label translate>notification.action-type</mat-label>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')"> <mat-select formControlName="linkType">
{{ 'notification.link-required' | translate }} <mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes" [value]="actionButtonLinkType">
</mat-error> {{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-form-field> </mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex
*ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.linkType').value === actionButtonLinkType.LINK; else dashboardSelector">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
<ng-template #dashboardSelector>
<tb-dashboard-autocomplete
fxFlex="35"
required
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete fxFlex="35"
[dashboardId]="pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
</ng-template>
</div>
</div> </div>
</section> </section>
</section> </section>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.EMAIL.enabled').value" <mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.EMAIL.enabled').value"
[stepControl]="emailTemplateForm"> [stepControl]="emailTemplateForm">
<ng-template matStepLabel>{{ 'notification.email-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.email-settings' | translate }}</ng-template>
<ng-template matStepContent> <ng-template matStepContent>
@ -162,7 +184,7 @@
</form> </form>
</ng-template> </ng-template>
</mat-step> </mat-step>
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SMS.enabled').value" <mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SMS.enabled').value"
[stepControl]="smsTemplateForm"> [stepControl]="smsTemplateForm">
<ng-template matStepLabel>{{ 'notification.sms-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.sms-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">
@ -181,7 +203,7 @@
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SLACK.enabled').value" <mat-step *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm"> [stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template>
<div class="tb-hint tb-hint-available-params"> <div class="tb-hint tb-hint-available-params">

38
ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html

@ -15,11 +15,13 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="selectDashboardStateFormGroup" class="mat-block" [floatLabel]="floatLabel"> <mat-form-field [formGroup]="selectDashboardStateFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ placeholder || ('widget-action.target-dashboard-state' | translate) }}" <mat-label>{{ label }}</mat-label>
<input matInput type="text" placeholder="{{ placeholder }}"
#dashboardStateInput #dashboardStateInput
formControlName="dashboardStateId" formControlName="dashboardStateId"
[required]="required" [required]="required"
(focus)="onFocus()"
[matAutocomplete]="dashboardStateAutocomplete"> [matAutocomplete]="dashboardStateAutocomplete">
<button *ngIf="selectDashboardStateFormGroup.get('dashboardStateId').value" <button *ngIf="selectDashboardStateFormGroup.get('dashboardStateId').value"
type="button" type="button"
@ -38,35 +40,3 @@
{{ 'widget-action.target-dashboard-state-required' | translate }} {{ 'widget-action.target-dashboard-state-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<!-- <input matInput type="text" placeholder="{{ placeholder || ('dashboard.dashboard' | translate) }}"-->
<!-- #dashboardInput-->
<!-- formControlName="dashboard"-->
<!-- (focusin)="onFocus()"-->
<!-- [required]="required"-->
<!-- [matAutocomplete]="dashboardAutocomplete">-->
<!-- <button *ngIf="selectDashboardStateFormGroup.get('dashboard').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"-->
<!-- #dashboardAutocomplete="matAutocomplete"-->
<!-- [displayWith]="displayDashboardFn">-->
<!-- <mat-option *ngFor="let dashboard of filteredDashboards | async" [value]="dashboard">-->
<!-- <span [innerHTML]="dashboard.title | highlight:searchText"></span>-->
<!-- </mat-option>-->
<!-- <mat-option *ngIf="!(filteredDashboards | async)?.length" [value]="null">-->
<!-- <span>-->
<!-- {{ translate.get('dashboard.no-dashboards-matching', {entity: searchText}) | async }}-->
<!-- </span>-->
<!-- </mat-option>-->
<!-- </mat-autocomplete>-->
<!-- <mat-error>-->
<!-- <ng-content select="[tb-error]"></ng-content>-->
<!-- </mat-error>-->
<!-- <mat-hint>-->
<!-- <ng-content select="[tb-hint]"></ng-content>-->
<!-- </mat-hint>-->
<!--</mat-form-field>-->

24
ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts

@ -47,12 +47,11 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
@Input() @Input()
placeholder: string; label: string = this.translate.instant('widget-action.target-dashboard-state');
@Input() @Input()
floatLabel: FloatLabelType = 'auto'; placeholder: string;
private requiredValue: boolean; private requiredValue: boolean;
get required(): boolean { get required(): boolean {
@ -66,17 +65,20 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
@Input() @Input()
disabled: boolean; disabled: boolean;
private dashboardIdValue: string; private dashboardIdValue: string = null;
get dashboardId(): string { get dashboardId(): string {
return this.dashboardIdValue; return this.dashboardIdValue;
} }
@Input()
set dashboardId(value: string) { set dashboardId(value: string) {
this.dashboardIdValue = value; if (this.dashboardIdValue !== value) {
this.clearDashboardStateCache(); this.dashboardIdValue = value;
this.searchText = ''; this.clearDashboardStateCache();
this.selectDashboardStateFormGroup.get('dashboardStateId').patchValue('', {emitEvent: false}); this.searchText = '';
this.dirty = true; this.selectDashboardStateFormGroup.get('dashboardStateId').patchValue('', {emitEvent: false});
this.dirty = true;
}
} }
@ViewChild('dashboardStateInput', {static: true}) dashboardStateInput: ElementRef; @ViewChild('dashboardStateInput', {static: true}) dashboardStateInput: ElementRef;
@ -113,7 +115,7 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
debounceTime(150), debounceTime(150),
tap(value => { tap(value => {
let modelValue; let modelValue;
if (!value || !this.latestDashboardStates.includes(value)) { if (!value || !this.latestDashboardStates?.includes(value)) {
modelValue = null; modelValue = null;
} else { } else {
modelValue = value; modelValue = value;
@ -160,7 +162,7 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
onFocus() { onFocus() {
if (this.dirty) { if (this.dirty) {
this.selectDashboardStateFormGroup.get('dashboard').updateValueAndValidity({onlySelf: true}); this.selectDashboardStateFormGroup.get('dashboardStateId').updateValueAndValidity({onlySelf: true});
this.dirty = false; this.dirty = false;
} }
} }

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

@ -16,6 +16,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { import {
ActionButtonLinkType,
AlarmSeverityNotificationColors, AlarmSeverityNotificationColors,
Notification, Notification,
NotificationType, NotificationType,
@ -26,6 +27,8 @@ import { Router } from '@angular/router';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { alarmSeverityTranslations } from '@shared/models/alarm.models'; import { alarmSeverityTranslations } from '@shared/models/alarm.models';
import * as tinycolor_ from 'tinycolor2'; import * as tinycolor_ from 'tinycolor2';
import { StateObject } from '@core/api/widget-api.models';
import { objToBase64URI } from '@core/utils';
@Component({ @Component({
selector: 'tb-notification', selector: 'tb-notification',
@ -91,11 +94,27 @@ export class NotificationComponent implements OnInit {
$event.stopPropagation(); $event.stopPropagation();
} }
if (!this.preview) { if (!this.preview) {
if (this.notification.additionalConfig.actionButtonConfig.link.startsWith('/')) { let link: string;
this.router.navigateByUrl(this.router.parseUrl(this.notification.additionalConfig.actionButtonConfig.link)).then(() => { if (this.notification.additionalConfig.actionButtonConfig.linkType === ActionButtonLinkType.DASHBOARD) {
let state = null;
if (this.notification.additionalConfig.actionButtonConfig.dashboardState) {
const stateObject: StateObject = {};
stateObject.params = {};
stateObject.id = this.notification.additionalConfig.actionButtonConfig.dashboardState;
state = objToBase64URI([ stateObject ]);
}
link = `/dashboards/${this.notification.additionalConfig.actionButtonConfig.dashboardId}`
if (state) {
link += `?state=${state}`;
}
} else {
link = this.notification.additionalConfig.actionButtonConfig.link;
}
if (link.startsWith('/')) {
this.router.navigateByUrl(this.router.parseUrl(link)).then(() => {
}); });
} else { } else {
window.open(this.notification.additionalConfig.actionButtonConfig.link, '_blank'); window.open(link, '_blank');
} }
if (this.onClose) { if (this.onClose) {
this.onClose(); this.onClose();

1
ui-ngx/src/app/shared/components/string-items-list.component.html

@ -22,6 +22,7 @@
<mat-chip *ngFor="let item of stringItemsList" <mat-chip *ngFor="let item of stringItemsList"
[selectable]="!disabled" [selectable]="!disabled"
[removable]="!disabled" [removable]="!disabled"
[contentEditable]="editable && !disabled"
(removed)="removeItems(item)"> (removed)="removeItems(item)">
{{item}} {{item}}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon> <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>

13
ui-ngx/src/app/shared/components/string-items-list.component.ts

@ -16,10 +16,10 @@
import { Component, forwardRef, Input } from '@angular/core'; import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field'; import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
@Component({ @Component({
selector: 'tb-string-items-list', selector: 'tb-string-items-list',
@ -45,15 +45,16 @@ export class StringItemsListComponent implements ControlValueAccessor{
return this.requiredValue; return this.requiredValue;
} }
@Input() @Input()
@coerceBoolean()
set required(value: boolean) { set required(value: boolean) {
const newVal = coerceBooleanProperty(value); if (this.requiredValue !== value) {
if (this.requiredValue !== newVal) { this.requiredValue = value;
this.requiredValue = newVal;
this.updateValidators(); this.updateValidators();
} }
} }
@Input() @Input()
@coerceBoolean()
disabled: boolean; disabled: boolean;
@Input() @Input()
@ -74,6 +75,10 @@ export class StringItemsListComponent implements ControlValueAccessor{
@Input() @Input()
appearance: MatFormFieldAppearance = 'standard'; appearance: MatFormFieldAppearance = 'standard';
@Input()
@coerceBoolean()
editable: boolean = false
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder) { constructor(private fb: FormBuilder) {

35
ui-ngx/src/app/shared/decorators/coerce-boolean.ts

@ -0,0 +1,35 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { coerceBooleanProperty } from '@angular/cdk/coercion';
export function coerceBoolean() {
return function (target: any, key: string): void {
const getter = function () {
return this['__' + key];
};
const setter = function (next: any) {
this['__' + key] = coerceBooleanProperty(next);
};
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}

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

@ -206,7 +206,10 @@ interface PushDeliveryMethodAdditionalConfig {
enabled: boolean; enabled: boolean;
text: string; text: string;
color: string; color: string;
link: string; linkType: ActionButtonLinkType,
link?: string;
dashboardId?: string;
dashboardState?: string;
}; };
} }
@ -301,6 +304,16 @@ export const AlarmSeverityNotificationColors = new Map<AlarmSeverity, string>(
] ]
); );
export enum ActionButtonLinkType {
LINK = 'LINK',
DASHBOARD = 'DASHBOARD'
}
export const ActionButtonLinkTypeTranslateMap = new Map<ActionButtonLinkType, string>([
[ActionButtonLinkType.LINK, 'notification.link-type.link'],
[ActionButtonLinkType.DASHBOARD, 'notification.link-type.dashboard']
]);
interface NotificationTemplateTypeTranslate { interface NotificationTemplateTypeTranslate {
name: string; name: string;
hint?: string; hint?: string;

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

@ -2690,6 +2690,7 @@
}, },
"notification": { "notification": {
"action-button": "Action button", "action-button": "Action button",
"action-type": "Action type",
"add-notification-target": "Add notification recipient", "add-notification-target": "Add notification recipient",
"add-notification-template": "Add notification template", "add-notification-template": "Add notification template",
"add-rule": "Add rule", "add-rule": "Add rule",
@ -2756,6 +2757,10 @@
"inbox": "Inbox", "inbox": "Inbox",
"link": "Link", "link": "Link",
"link-required": "Link is required", "link-required": "Link is required",
"link-type": {
"link": "Open URL link",
"dashboard": "Open dashboard"
},
"management": "Notification management", "management": "Notification management",
"mark-all-as-read": "Mark all as read", "mark-all-as-read": "Mark all as read",
"mark-as-read": "Mark as read", "mark-as-read": "Mark as read",

Loading…
Cancel
Save