Browse Source

UI: Improve custom dialog error diagnostics. Improve widget error message formatting.

pull/8658/head
Igor Kulikov 3 years ago
parent
commit
dbb5a8cc8b
  1. 18
      ui-ngx/src/app/core/services/dialog.service.ts
  2. 4
      ui-ngx/src/app/core/services/utils.service.ts
  3. 18
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts
  4. 2
      ui-ngx/src/app/modules/home/components/widget/widget.component.html
  5. 3
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  6. 2
      ui-ngx/src/app/shared/components/dialog/alert-dialog.component.html
  7. 30
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.html
  8. 37
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.scss
  9. 49
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts
  10. 2
      ui-ngx/src/app/shared/components/snack-bar-component.scss
  11. 3
      ui-ngx/src/app/shared/shared.module.ts
  12. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json

18
ui-ngx/src/app/core/services/dialog.service.ts

@ -29,6 +29,7 @@ import {
} from '@shared/components/dialog/material-icons-dialog.component'; } from '@shared/components/dialog/material-icons-dialog.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component';
@Injectable( @Injectable(
@ -78,6 +79,23 @@ export class DialogService {
return dialogRef.afterClosed(); return dialogRef.afterClosed();
} }
errorAlert(title: string, message: string, error: any, ok: string = null, fullscreen: boolean = false): Observable<any> {
const dialogConfig: MatDialogConfig = {
disableClose: true,
data: {
title,
message,
error,
ok: ok || this.translate.instant('action.ok')
}
};
if (fullscreen) {
dialogConfig.panelClass = ['tb-fullscreen-dialog'];
}
const dialogRef = this.dialog.open(ErrorAlertDialogComponent, dialogConfig);
return dialogRef.afterClosed();
}
colorPicker(color: string): Observable<string> { colorPicker(color: string): Observable<string> {
return this.dialog.open<ColorPickerDialogComponent, ColorPickerDialogData, string>(ColorPickerDialogComponent, return this.dialog.open<ColorPickerDialogComponent, ColorPickerDialogData, string>(ColorPickerDialogComponent,
{ {

4
ui-ngx/src/app/core/services/utils.service.ts

@ -188,6 +188,10 @@ export class UtilsService {
public processWidgetException(exception: any): ExceptionData { public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -6); const data = this.parseException(exception, -6);
if (data.message?.startsWith('NG0')) {
data.message = `${this.translate.instant('widget.widget-template-error')}<br/>
<br/><i>${this.translate.instant('dialog.error-message-title')}</i><br/><br/>${data.message}`;
}
if (this.widgetEditMode) { if (this.widgetEditMode) {
const message: WindowMessage = { const message: WindowMessage = {
type: 'widgetException', type: 'widgetException',

18
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts

@ -33,6 +33,8 @@ import {
CustomDialogComponent, CustomDialogComponent,
CustomDialogData CustomDialogData
} from '@home/components/widget/dialog/custom-dialog.component'; } from '@home/components/widget/dialog/custom-dialog.component';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
export interface CustomDialogContainerData { export interface CustomDialogContainerData {
controller: (instance: CustomDialogComponent) => void; controller: (instance: CustomDialogComponent) => void;
@ -54,6 +56,8 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
protected router: Router, protected router: Router,
public viewContainerRef: ViewContainerRef, public viewContainerRef: ViewContainerRef,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>, public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
private dialogService: DialogService,
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) { @Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
super(store, router, dialogRef); super(store, router, dialogRef);
let customDialogData: CustomDialogData = { let customDialogData: CustomDialogData = {
@ -72,7 +76,19 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
useValue: dialogRef useValue: dialogRef
}] }]
}); });
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector); try {
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
} catch (e: any) {
let message;
if (e.message?.startsWith('NG0')) {
message = this.translate.instant('widget-action.custom-pretty-template-error');
} else {
message = this.translate.instant('widget-action.custom-pretty-controller-error');
}
dialogRef.close();
console.error(e);
this.dialogService.errorAlert(this.translate.instant('widget-action.custom-pretty-error-title'), message, e);
}
} }
ngOnDestroy(): void { ngOnDestroy(): void {

2
ui-ngx/src/app/modules/home/components/widget/widget.component.html

@ -34,7 +34,7 @@
</tb-legend> </tb-legend>
</div> </div>
<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData"> <div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">
<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span> <span [innerHtml]="('Widget Error:<br/><br/>' + widgetErrorData.message) | safe:'html'"></span>
</div> </div>
<div class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData"> <div class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData">
<span fxLayoutAlign="center center" <span fxLayoutAlign="center center"

3
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -489,9 +489,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
if (!this.gotError) { if (!this.gotError) {
this.gotError = true; this.gotError = true;
let errorInfo = 'Error:'; let errorInfo = 'Error:';
if (details.name) {
errorInfo += ' ' + details.name + ':';
}
if (details.message) { if (details.message) {
errorInfo += ' ' + details.message; errorInfo += ' ' + details.message;
} }

2
ui-ngx/src/app/shared/components/dialog/alert-dialog.component.html

@ -16,7 +16,7 @@
--> -->
<h2 mat-dialog-title>{{data.title}}</h2> <h2 mat-dialog-title>{{data.title}}</h2>
<div mat-dialog-content [innerHTML]="data.message"></div> <div mat-dialog-content [innerHTML]="data.message | safe:'html'"></div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button> <button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
</div> </div>

30
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.html

@ -0,0 +1,30 @@
<!--
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.
-->
<h2 mat-dialog-title>{{title}}</h2>
<div mat-dialog-content class="tb-error-alert-dialog-content">
<div>{{ message }}</div>
<div class="error-message-title" translate>dialog.error-message-title</div>
<div class="error-message-content"> {{ errorMessage }} </div>
<mat-expansion-panel *ngIf="errorDetails">
<mat-expansion-panel-header>{{ 'dialog.error-details-title' | translate }}</mat-expansion-panel-header>
<small class="error-details-content" [innerHTML]="errorDetails"></small>
</mat-expansion-panel>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
</div>

37
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.scss

@ -0,0 +1,37 @@
/**
* 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.
*/
:host {
.mat-mdc-dialog-content {
padding: 0 24px 24px;
}
.tb-error-alert-dialog-content {
.error-message-title {
font-style: italic;
}
.error-message-content {
color: red;
}
.error-details-content {
display: block;
border: solid 1px #d3d3d3;
padding: 8px;
border-radius: 4px;
}
& > *:not(:last-child) {
padding-bottom: 16px;
}
}
}

49
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts

@ -0,0 +1,49 @@
///
/// 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 { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
export interface ErrorAlertDialogData {
title: string;
message: string;
error: any;
ok: string;
}
@Component({
selector: 'tb-error-alert-dialog',
templateUrl: './error-alert-dialog.component.html',
styleUrls: ['./error-alert-dialog.component.scss']
})
export class ErrorAlertDialogComponent {
title: string;
message: string;
errorMessage: string;
errorDetails?: string;
constructor(public dialogRef: MatDialogRef<ErrorAlertDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ErrorAlertDialogData) {
this.title = this.data.title;
this.message = this.data.message;
this.errorMessage = this.data.error.message ? this.data.error.message : JSON.stringify(this.data.error);
if (this.data.error.stack) {
this.errorDetails = this.data.error.stack.replaceAll('\n', '<br/>');
}
}
}

2
ui-ngx/src/app/shared/components/snack-bar-component.scss

@ -51,7 +51,7 @@
padding: 0 18px; padding: 0 18px;
margin: 8px; margin: 8px;
.toast-text { .toast-text {
padding: 0 6px; padding: 8px;
width: 100%; width: 100%;
} }
button { button {

3
ui-ngx/src/app/shared/shared.module.ts

@ -123,6 +123,7 @@ import { JsFuncComponent } from '@shared/components/js-func.component';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component'; import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component';
import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component'; import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component';
import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component'; import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component';
@ -305,6 +306,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
WidgetsBundleSelectComponent, WidgetsBundleSelectComponent,
ConfirmDialogComponent, ConfirmDialogComponent,
AlertDialogComponent, AlertDialogComponent,
ErrorAlertDialogComponent,
TodoDialogComponent, TodoDialogComponent,
ColorPickerDialogComponent, ColorPickerDialogComponent,
MaterialIconsDialogComponent, MaterialIconsDialogComponent,
@ -528,6 +530,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MarkdownModule, MarkdownModule,
ConfirmDialogComponent, ConfirmDialogComponent,
AlertDialogComponent, AlertDialogComponent,
ErrorAlertDialogComponent,
TodoDialogComponent, TodoDialogComponent,
ColorPickerDialogComponent, ColorPickerDialogComponent,
MaterialIconsDialogComponent, MaterialIconsDialogComponent,

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

@ -1789,7 +1789,9 @@
} }
}, },
"dialog": { "dialog": {
"close": "Close dialog" "close": "Close dialog",
"error-message-title": "Error message:",
"error-details-title": "Error details"
}, },
"direction": { "direction": {
"column": "Column", "column": "Column",
@ -3991,7 +3993,8 @@
"alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities", "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities",
"search": "Search widget", "search": "Search widget",
"filter": "Widget filter type", "filter": "Widget filter type",
"loading-widgets": "Loading widgets..." "loading-widgets": "Loading widgets...",
"widget-template-error": "Invalid widget HTML template."
}, },
"widget-action": { "widget-action": {
"header-button": "Widget header button", "header-button": "Widget header button",
@ -4000,6 +4003,9 @@
"open-dashboard": "Navigate to other dashboard", "open-dashboard": "Navigate to other dashboard",
"custom": "Custom action", "custom": "Custom action",
"custom-pretty": "Custom action (with HTML template)", "custom-pretty": "Custom action (with HTML template)",
"custom-pretty-error-title": "Custom dialog error",
"custom-pretty-template-error": "Invalid custom dialog template.",
"custom-pretty-controller-error": "Error occurred while evaluating custom dialog function.",
"mobile-action": "Mobile action", "mobile-action": "Mobile action",
"target-dashboard-state": "Target dashboard state", "target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required", "target-dashboard-state-required": "Target dashboard state is required",

Loading…
Cancel
Save