Browse Source

UI: Add rule table

pull/8022/head
Artem Dzhereleiko 4 years ago
parent
commit
1b1f3b4d7b
  1. 6
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html
  2. 6
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts
  3. 7
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts
  4. 120
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/rule-table-config.ts
  5. 132
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  6. 29
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss
  7. 109
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts
  8. 24
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-table-header.component.html
  9. 18
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-table-header.component.scss
  10. 38
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-table-header.component.ts
  11. 3
      ui-ngx/src/app/shared/models/notification.models.ts
  12. 13
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -41,9 +41,9 @@
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.rules' | translate }}">
<ng-template matTabContent>
Content 1 - Loaded
</ng-template>
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_RULE">
</tb-notification-table>
</mat-tab>
<mat-tab disabled>
<ng-template mat-tab-label>

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

@ -42,6 +42,8 @@ import {
TemplateAutocompleteComponent
} from '@home/pages/notification-center/template-table/template-autocomplete.component';
import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component';
import { RuleTableHeaderComponent } from '@home/pages/notification-center/rule-table/rule-table-header.component';
import { RuleNotificationDialogComponent } from '@home/pages/notification-center/rule-table/rule-notification-dialog.component';
@NgModule({
declarations: [
@ -53,7 +55,9 @@ import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox
TemplateTableHeaderComponent,
NotificationTableComponent,
TemplateAutocompleteComponent,
InboxTableHeaderComponent
InboxTableHeaderComponent,
RuleTableHeaderComponent,
RuleNotificationDialogComponent
],
imports: [
CommonModule,

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

@ -26,6 +26,7 @@ import { InboxTableConfig } from '@home/pages/notification-center/notification-t
import { DatePipe } from '@angular/common';
import { TemplateTableConfig } from '@home/pages/notification-center/notification-table/template-table-config';
import { RequestTableConfig } from '@home/pages/notification-center/notification-table/request-table-config';
import { RuleTableConfig } from '@home/pages/notification-center/notification-table/rule-table-config';
@Component({
selector: 'tb-notification-table',
@ -85,6 +86,12 @@ export class NotificationTableComponent implements OnInit {
this.dialog,
this.datePipe
);
case EntityType.NOTIFICATION_RULE:
return new RuleTableConfig(
this.notificationService,
this.translate,
this.dialog
);
}
}
}

120
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/rule-table-config.ts

@ -0,0 +1,120 @@
///
/// 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 {
CellActionDescriptor,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import {
NotificationRule,
NotificationTarget,
NotificationTargetConfigTypeTranslateMap
} from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { TranslateService } from '@ngx-translate/core';
import {
TargetNotificationDialogComponent,
TargetsNotificationDialogData
} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet';
import { MatDialog } from '@angular/material/dialog';
import { EntityAction } from '@home/models/entity/entity-component.models';
import {
TargetTableHeaderComponent
} from '@home/pages/notification-center/targets-table/target-table-header.component';
import { RuleTableHeaderComponent } from '@home/pages/notification-center/rule-table/rule-table-header.component';
import {
RuleNotificationDialogComponent,
RuleNotificationDialogData
} from '@home/pages/notification-center/rule-table/rule-notification-dialog.component';
export class RuleTableConfig extends EntityTableConfig<NotificationRule> {
constructor(private notificationService: NotificationService,
private translate: TranslateService,
private dialog: MatDialog) {
super();
this.loadDataOnInit = false;
this.entityTranslations = {
noEntities: 'notification.no-rules-notification',
search: 'notification.search-rules'
};
this.entityResources = {} as EntityTypeResource<NotificationRule>;
this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationRules(pageLink);
this.deleteEntityTitle = rule => this.translate.instant('notification.delete-rule-title', {ruleName: rule.name});
this.deleteEntityContent = () => this.translate.instant('notification.delete-rule-text');
this.deleteEntity = id => this.notificationService.deleteNotificationRule(id.id);
this.cellActionDescriptors = this.configureCellActions();
this.headerComponent = RuleTableHeaderComponent;
this.onEntityAction = action => this.onTargetAction(action);
this.defaultSortOrder = {property: 'name', direction: Direction.ASC};
this.columns.push(
new EntityTableColumn<NotificationRule>('name', 'notification.rule-name', '30%'),
new EntityTableColumn<NotificationRule>('templateId', 'notification.template', '30%',
(rule) => `${rule.templateId}`,
() => ({}), false),
new EntityTableColumn<NotificationRule>('configuration.description', 'notification.description', '40%',
(rule) => rule.configuration.description || '',
() => ({}), false)
);
}
private configureCellActions(): Array<CellActionDescriptor<NotificationRule>> {
return [{
name: this.translate.instant('device.make-public'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editTarget($event, entity)
}];
}
private editTarget($event: Event, rule: NotificationRule, isAdd = false) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<RuleNotificationDialogComponent, RuleNotificationDialogData,
NotificationRule>(RuleNotificationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd,
rule
}
}).afterClosed()
.subscribe((res) => {
if (res) {
this.updateData();
}
});
}
private onTargetAction(action: EntityAction<NotificationRule>): boolean {
switch (action.action) {
case 'add':
this.editTarget(action.event, action.entity, true);
return true;
}
return false;
}
}

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

@ -0,0 +1,132 @@
<!--
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.
-->
<form [formGroup]="ruleNotificationForm" style="min-width: 560px">
<mat-toolbar color="primary">
<h2>{{ (isAdd ? 'notification.add-notification-target' : 'notification.edit-notification-target') | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<mat-form-field class="mat-block">
<mat-label translate>notification.rule-name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="ruleNotificationForm.get('name').hasError('required')">
{{ 'notification.rule-name-required' | translate }}
</mat-error>
</mat-form-field>
<tb-template-autocomplete
required
formControlName="templateId"
labelText="notification.template-name"
placeholderText="notification.template-name"
requiredText="notification.template-required">
</tb-template-autocomplete>
<section formGroupName="configuration">
<fieldset class="fields-group">
<span class="fields-group-title" translate>notification.hierarchy-of-receiving</span>
<div class="rule-header">
<div fxLayoutAlign="row">
<span fxFlex="30" style="padding-left: 12px" translate>notification.rule</span>
<span fxFlex translate>notification.recipient</span>
</div>
</div>
<div class="rules" style="padding: 16px 0">
<div class="rule" fxFlex fxLayout="row" fxLayoutAlign="start center">
<div fxLayoutAlign="row" fxLayoutAlign="start center" style="display: inline-block; width: 100%; min-height: 64px; height: 100%; background: rgba(0,0,0, 0.04); border-radius: 8px;">
<div fxFlex="30" *ngIf="false">
<span style="padding-left: 12px" >First recipient</span>
<span style="padding-left: 12px" *ngIf="true">After</span>
</div>
<div fxFlex="30" *ngIf="true">
<span style="padding-left: 12px" *ngIf="true">After</span>
<tb-timeinterval
formControlName="interval"
[hideFlag]="true"
predefinedName="aggregation.group-interval">
</tb-timeinterval>
</div>
<tb-entity-list
style="padding: 0 12px"
fxFlex
formControlName="ids"
[required]="true"
[entityType]="entityType.NOTIFICATION_TEMPLATE">
</tb-entity-list>
</div>
<button mat-icon-button>
<mat-icon>close</mat-icon>
</button>
</div>
</div>
<button mat-stroked-button color="primary">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>add</mat-icon>{{ 'notification.add-rule' | translate }}
</div>
</button>
</fieldset>
<!-- <mat-form-field class="mat-block">-->
<!-- <mat-label translate>notification.target-type.type</mat-label>-->
<!-- <mat-select formControlName="type">-->
<!-- <mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type">-->
<!-- {{ notificationTargetConfigTypeTranslateMap.get(type) | translate }}-->
<!-- </mat-option>-->
<!-- </mat-select>-->
<!-- </mat-form-field>-->
<!-- <tb-entity-list-->
<!-- required-->
<!-- formControlName="usersIds"-->
<!-- [entityType]="entityType.USER"-->
<!-- *ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.USER_LIST">-->
<!-- </tb-entity-list>-->
<!-- <section-->
<!-- *ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.CUSTOMER_USERS">-->
<!-- <mat-slide-toggle formControlName="getCustomerIdFromOriginatorEntity">-->
<!-- {{ 'notification.get-customer-id-from-originator' | translate }}-->
<!-- </mat-slide-toggle>-->
<!-- <tb-entity-autocomplete-->
<!-- required-->
<!-- formControlName="customerId"-->
<!-- [entityType]="entityType.CUSTOMER"-->
<!-- *ngIf="!targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').value">-->
<!-- </tb-entity-autocomplete>-->
<!-- </section>-->
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</div>
<mat-divider></mat-divider>
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
<button mat-button
[disabled]="(isLoading$ | async)"
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-raised-button
[disabled]="(isLoading$ | async) || ruleNotificationForm.invalid || !ruleNotificationForm.dirty"
color="primary"
(click)="save()">{{ (isAdd ? 'action.add' : 'action.save') | translate }}</button>
</div>
</form>

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

@ -0,0 +1,29 @@
/**
* 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.
*/
:host {
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
.fields-group-title {
display: inline-block;
font-weight: 500;
padding: 12px 0;
}
}
}

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

@ -0,0 +1,109 @@
///
/// 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 {
NotificationRule,
NotificationTarget,
NotificationTargetConfigType,
NotificationTargetConfigTypeTranslateMap
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { EntityType } from '@shared/models/entity-type.models';
import { deepTrim, isDefined } from '@core/utils';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
export interface RuleNotificationDialogData {
rule?: NotificationRule;
isAdd?: boolean;
}
@Component({
selector: 'tb-rule-notification-dialog',
templateUrl: './rule-notification-dialog.component.html',
styleUrls: ['rule-notification-dialog.component.scss']
})
export class RuleNotificationDialogComponent extends
DialogComponent<RuleNotificationDialogComponent, NotificationRule> implements OnDestroy {
ruleNotificationForm: FormGroup;
// notificationTargetConfigType = NotificationTargetConfigType;
// notificationTargetConfigTypes: NotificationTargetConfigType[] = Object.values(NotificationTargetConfigType);
// notificationTargetConfigTypeTranslateMap = NotificationTargetConfigTypeTranslateMap;
entityType = EntityType;
isAdd = true;
private readonly destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<RuleNotificationDialogComponent, NotificationRule>,
@Inject(MAT_DIALOG_DATA) public data: RuleNotificationDialogData,
private fb: FormBuilder,
private notificationService: NotificationService) {
super(store, router, dialogRef);
if (isDefined(data.isAdd)) {
this.isAdd = data.isAdd;
}
this.ruleNotificationForm = this.fb.group({
name: [null, Validators.required],
templateId: [null, Validators.required],
configuration: this.fb.group({
ids: [],
interval: [],
// type: [NotificationTargetConfigType.ALL_USERS],
description: [null],
// usersIds: [{value: null, disabled: true}, Validators.required],
// customerId: [{value: null, disabled: true}, Validators.required],
// getCustomerIdFromOriginatorEntity: [{value: false, disabled: true}],
})
});
// if (isDefined(data.target)) {
// this.targetNotificationForm.patchValue(data.target, {emitEvent: false});
// this.targetNotificationForm.get('configuration.type').updateValueAndValidity({onlySelf: true});
// }
}
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
cancel(): void {
this.dialogRef.close(null);
}
save() {
let formValue = deepTrim(this.ruleNotificationForm.value);
if (isDefined(this.data.rule)) {
formValue = Object.assign({}, this.data.rule, formValue);
}
this.notificationService.saveNotificationRule(formValue).subscribe(
(rule) => this.dialogRef.close(rule)
);
}
}

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

@ -0,0 +1,24 @@
<!--
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.
-->
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" (click)="createTarget($event)">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>add</mat-icon>{{ 'notification.add-rule' | translate }}
</div>
</button>
</div>

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

@ -0,0 +1,18 @@
/**
* 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.
*/
:host{
width: 100%;
}

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

@ -0,0 +1,38 @@
///
/// 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 } from '@angular/core';
import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { NotificationRule } from '@shared/models/notification.models';
@Component({
selector: 'tb-rule-table-header',
templateUrl: './rule-table-header.component.html',
styleUrls: ['rule-table-header.component.scss']
})
export class RuleTableHeaderComponent extends EntityTableHeaderComponent<NotificationRule> {
constructor(protected store: Store<AppState>) {
super(store);
}
createTarget($event: Event) {
this.entitiesTableConfig.onEntityAction({event: $event, action: 'add', entity: null});
}
}

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

@ -96,13 +96,14 @@ export interface SlackConversation {
export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'label'>{
tenantId: TenantId;
templateId: NotificationTemplateId;
deliveryMethods: Array<NotificationDeliveryMethod>;
// deliveryMethods: Array<NotificationDeliveryMethod>;
configuration: NotificationRuleConfig;
}
export interface NotificationRuleConfig {
initialNotificationTargetId: NotificationTargetId;
escalationConfig: NotificationEscalationConfig;
description?: string;
}
export interface NotificationEscalationConfig {

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

@ -2680,6 +2680,7 @@
"button-text": "Button text",
"button-text-required": "Button text is required",
"compose": "Compose",
"add-rule": "Add rule",
"copy-notification-template": "Copy notification template",
"create-target": "Create target",
"created-time": "Created time",
@ -2697,6 +2698,8 @@
"slack": "Slack",
"sms": "SMS"
},
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"description": "Description",
"edit-notification-target": "Edit notification target",
"edit-notification-template": "Edit notification template",
@ -2726,10 +2729,13 @@
"sent": "Sent"
},
"review": "Review",
"no-rules-notification": "No rules notification",
"rule": "Rule",
"rules": "Rules",
"search-notification": "Search notification",
"search-targets": "Search targets",
"search-templates": "Search templates",
"search-rules": "Search rules",
"send-notification": "Send notification",
"send-test-notification": "Send test notification",
"sent": "Sent",
@ -2750,6 +2756,7 @@
},
"targets": "Targets",
"targets-required": "Targets is required",
"recipient": "Recipient",
"template": "Template",
"template-required": "Template is required",
"template-type": {
@ -2761,7 +2768,11 @@
"type": "Type",
"unread": "Unread",
"view-all": "View al;",
"web-settings": "Web settings"
"web-settings": "Web settings",
"rule-name": "Rule name",
"rule-name-required": "Name is required",
"template-name": "Template name",
"hierarchy-of-receiving": "Hierarchy of receiving notifications"
},
"ota-update": {
"add": "Add package",

Loading…
Cancel
Save