mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 01:25:38 -05:00
Add error messages to BDC dashboard page (#8103)
* Add error messages to BDC dashboard page * Remove testing code * PR fixes
This commit is contained in:
@@ -71,6 +71,7 @@ export namespace cssStyles {
|
||||
export const selectedTabDiv = { 'border-bottom': '2px solid #000' };
|
||||
export const unselectedTabDiv = { 'border-bottom': '1px solid #ccc' };
|
||||
export const lastUpdatedText = { ...text, 'color': '#595959' };
|
||||
export const errorText = { ...text, 'color': 'red' };
|
||||
}
|
||||
|
||||
export type AuthType = 'integrated' | 'basic';
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { BdcDashboardModel, getTroubleshootNotebookUrl } from './bdcDashboardModel';
|
||||
import { BdcDashboardModel, getTroubleshootNotebookUrl, BdcErrorEvent } from './bdcDashboardModel';
|
||||
import { IconPathHelper, cssStyles } from '../constants';
|
||||
import { BdcServiceStatusPage } from './bdcServiceStatusPage';
|
||||
import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage';
|
||||
@@ -34,6 +34,7 @@ export class BdcDashboard {
|
||||
private modelView: azdata.ModelView;
|
||||
private mainAreaContainer: azdata.FlexContainer;
|
||||
private navContainer: azdata.FlexContainer;
|
||||
private overviewPage: BdcDashboardOverviewPage;
|
||||
|
||||
private currentTab: NavTab;
|
||||
private currentPage: azdata.FlexContainer;
|
||||
@@ -44,7 +45,7 @@ export class BdcDashboard {
|
||||
|
||||
constructor(private title: string, private model: BdcDashboardModel) {
|
||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus));
|
||||
this.model.onError(error => this.handleError(error));
|
||||
this.model.onBdcError(errorEvent => this.handleError(errorEvent));
|
||||
}
|
||||
|
||||
public showDashboard(): void {
|
||||
@@ -75,6 +76,7 @@ export class BdcDashboard {
|
||||
}).component();
|
||||
|
||||
this.refreshButton.onDidClick(async () => {
|
||||
this.overviewPage.onRefreshStarted();
|
||||
await this.doRefresh();
|
||||
});
|
||||
|
||||
@@ -130,18 +132,19 @@ export class BdcDashboard {
|
||||
const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', "Big data cluster overview") }).component();
|
||||
overviewNavItemText.updateCssStyles(selectedTabCss);
|
||||
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
|
||||
const overviewPage = new BdcDashboardOverviewPage(this, this.model).create(modelView);
|
||||
this.currentPage = overviewPage;
|
||||
this.overviewPage = new BdcDashboardOverviewPage(this, this.model);
|
||||
const overviewContainer: azdata.FlexContainer = this.overviewPage.create(modelView);
|
||||
this.currentPage = overviewContainer;
|
||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||
|
||||
overviewNavItemDiv.onDidClick(() => {
|
||||
if (this.currentTab) {
|
||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
||||
}
|
||||
this.mainAreaContainer.removeItem(this.currentPage);
|
||||
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||
this.currentPage = overviewPage;
|
||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||
this.currentPage = overviewContainer;
|
||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
||||
});
|
||||
@@ -167,11 +170,14 @@ export class BdcDashboard {
|
||||
this.updateServiceNavTabs(bdcStatus.services);
|
||||
}
|
||||
|
||||
private handleError(error: Error): void {
|
||||
private handleError(errorEvent: BdcErrorEvent): void {
|
||||
if (errorEvent.errorType !== 'general') {
|
||||
return;
|
||||
}
|
||||
// We don't want to show an error for the connection dialog being
|
||||
// canceled since that's a normal case.
|
||||
if (!(error instanceof HdfsDialogCancelledError)) {
|
||||
showErrorMessage(error.message);
|
||||
if (!(errorEvent.error instanceof HdfsDialogCancelledError)) {
|
||||
showErrorMessage(errorEvent.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
|
||||
|
||||
export type BdcDashboardOptions = { url: string, auth: AuthType, username: string, password: string };
|
||||
|
||||
export type BdcErrorType = 'bdcStatus' | 'bdcEndpoints' | 'general';
|
||||
export type BdcErrorEvent = { error: Error, errorType: BdcErrorType };
|
||||
|
||||
export class BdcDashboardModel {
|
||||
|
||||
private _clusterController: ClusterController;
|
||||
@@ -23,10 +26,10 @@ export class BdcDashboardModel {
|
||||
private _endpointsLastUpdated: Date;
|
||||
private readonly _onDidUpdateEndpoints = new vscode.EventEmitter<EndpointModel[]>();
|
||||
private readonly _onDidUpdateBdcStatus = new vscode.EventEmitter<BdcStatusModel>();
|
||||
private readonly _onError = new vscode.EventEmitter<Error>();
|
||||
private readonly _onBdcError = new vscode.EventEmitter<BdcErrorEvent>();
|
||||
public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event;
|
||||
public onDidUpdateBdcStatus = this._onDidUpdateBdcStatus.event;
|
||||
public onError = this._onError.event;
|
||||
public onBdcError = this._onBdcError.event;
|
||||
|
||||
constructor(private _options: BdcDashboardOptions, private _treeDataProvider: ControllerTreeDataProvider, ignoreSslVerification = true) {
|
||||
try {
|
||||
@@ -37,7 +40,7 @@ export class BdcDashboardModel {
|
||||
this.promptReconnect().then(async () => {
|
||||
await this.refresh();
|
||||
}).catch(error => {
|
||||
this._onError.fire(error);
|
||||
this._onBdcError.fire({ error: error, errorType: 'general' });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -70,16 +73,16 @@ export class BdcDashboardModel {
|
||||
this._bdcStatus = response.bdcStatus;
|
||||
this._bdcStatusLastUpdated = new Date();
|
||||
this._onDidUpdateBdcStatus.fire(this.bdcStatus);
|
||||
}),
|
||||
}).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcStatus' })),
|
||||
this._clusterController.getEndPoints(true).then(response => {
|
||||
this._endpoints = response.endPoints || [];
|
||||
fixEndpoints(this._endpoints);
|
||||
this._endpointsLastUpdated = new Date();
|
||||
this._onDidUpdateEndpoints.fire(this.serviceEndpoints);
|
||||
})
|
||||
}).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcEndpoints' }))
|
||||
]);
|
||||
} catch (error) {
|
||||
this._onError.fire(error);
|
||||
this._onBdcError.fire({ error: error, errorType: 'general' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
||||
import { IconPathHelper, cssStyles } from '../constants';
|
||||
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint } from '../utils';
|
||||
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint, getBdcStatusErrorMessage } from '../utils';
|
||||
import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||
import { BdcDashboard } from './bdcDashboard';
|
||||
import { createViewDetailsButton } from './commonControls';
|
||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -31,24 +30,29 @@ const serviceEndpointRowEndpointCellWidth = 350;
|
||||
|
||||
const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
|
||||
|
||||
type ActionItem = (vscode.MessageItem & { execute: () => void; });
|
||||
|
||||
export class BdcDashboardOverviewPage {
|
||||
|
||||
private initialized: boolean = false;
|
||||
private modelBuilder: azdata.ModelBuilder;
|
||||
|
||||
private lastUpdatedLabel: azdata.TextComponent;
|
||||
private propertiesContainer: azdata.DivContainer;
|
||||
private clusterStateLoadingComponent: azdata.LoadingComponent;
|
||||
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
|
||||
|
||||
private serviceStatusRowContainer: azdata.FlexContainer;
|
||||
|
||||
private endpointsRowContainer: azdata.FlexContainer;
|
||||
private endpointsDisplayContainer: azdata.DivContainer;
|
||||
private serviceStatusDisplayContainer: azdata.DivContainer;
|
||||
private propertiesErrorMessage: azdata.TextComponent;
|
||||
private endpointsErrorMessage: azdata.TextComponent;
|
||||
private serviceStatusErrorMessage: azdata.TextComponent;
|
||||
|
||||
constructor(private dashboard: BdcDashboard, private model: BdcDashboardModel) {
|
||||
this.model.onDidUpdateEndpoints(endpoints => this.handleEndpointsUpdate(endpoints));
|
||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus));
|
||||
this.model.onBdcError(error => this.handleBdcError(error));
|
||||
}
|
||||
|
||||
public create(view: azdata.ModelView): azdata.FlexContainer {
|
||||
@@ -69,6 +73,11 @@ export class BdcDashboardOverviewPage {
|
||||
.component();
|
||||
rootContainer.addItem(propertiesLabel, { CSSStyles: { 'margin-top': '15px', 'padding-left': '10px', ...cssStyles.title } });
|
||||
|
||||
this.propertiesErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||
rootContainer.addItem(this.propertiesErrorMessage, { flex: '0 0 auto' });
|
||||
|
||||
this.propertiesContainer = view.modelBuilder.divContainer().component();
|
||||
|
||||
// Row 1
|
||||
const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component();
|
||||
|
||||
@@ -86,7 +95,9 @@ export class BdcDashboardOverviewPage {
|
||||
row1.addItem(healthStatusLabel, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } });
|
||||
row1.addItem(this.clusterHealthStatusLoadingComponent, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px` } });
|
||||
|
||||
rootContainer.addItem(row1, { CSSStyles: { 'padding-left': '10px', 'border-bottom': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
||||
this.propertiesContainer.addItem(row1, { CSSStyles: { 'padding-left': '10px', 'border-bottom': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
||||
|
||||
rootContainer.addItem(this.propertiesContainer, { flex: '0 0 auto' });
|
||||
|
||||
// ############
|
||||
// # OVERVIEW #
|
||||
@@ -125,6 +136,8 @@ export class BdcDashboardOverviewPage {
|
||||
serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${overviewHealthStatusCellWidthPx}px`, 'min-width': `${overviewHealthStatusCellWidthPx}px` } });
|
||||
overviewContainer.addItem(serviceStatusHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
||||
|
||||
this.serviceStatusDisplayContainer = view.modelBuilder.divContainer().component();
|
||||
|
||||
// Service Status row container
|
||||
this.serviceStatusRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
// Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly
|
||||
@@ -135,7 +148,12 @@ export class BdcDashboardOverviewPage {
|
||||
.component();
|
||||
this.serviceStatusRowContainer.addItem(serviceStatusRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||
|
||||
overviewContainer.addItem(this.serviceStatusRowContainer);
|
||||
this.serviceStatusErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
||||
|
||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusRowContainer);
|
||||
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
||||
|
||||
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
|
||||
|
||||
// #####################
|
||||
@@ -147,6 +165,8 @@ export class BdcDashboardOverviewPage {
|
||||
.component();
|
||||
rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
||||
|
||||
this.endpointsErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||
|
||||
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||
|
||||
// Service endpoints header row
|
||||
@@ -157,6 +177,7 @@ export class BdcDashboardOverviewPage {
|
||||
endpointsHeaderRow.addItem(endpointsEndpointHeaderCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px`, ...cssStyles.tableHeader } });
|
||||
endpointsContainer.addItem(endpointsHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
||||
|
||||
this.endpointsDisplayContainer = view.modelBuilder.divContainer().component();
|
||||
this.endpointsRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
// Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly
|
||||
// messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component
|
||||
@@ -166,8 +187,9 @@ export class BdcDashboardOverviewPage {
|
||||
.component();
|
||||
this.endpointsRowContainer.addItem(endpointRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||
|
||||
endpointsContainer.addItem(this.endpointsRowContainer);
|
||||
|
||||
this.endpointsDisplayContainer.addItem(this.endpointsRowContainer);
|
||||
endpointsContainer.addItem(this.endpointsErrorMessage);
|
||||
endpointsContainer.addItem(this.endpointsDisplayContainer);
|
||||
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
|
||||
|
||||
this.initialized = true;
|
||||
@@ -179,10 +201,22 @@ export class BdcDashboardOverviewPage {
|
||||
return rootContainer;
|
||||
}
|
||||
|
||||
public onRefreshStarted(): void {
|
||||
this.propertiesErrorMessage.display = 'none';
|
||||
this.serviceStatusErrorMessage.display = 'none';
|
||||
this.endpointsErrorMessage.display = 'none';
|
||||
|
||||
this.serviceStatusDisplayContainer.display = undefined;
|
||||
this.propertiesContainer.display = undefined;
|
||||
this.endpointsDisplayContainer.display = undefined;
|
||||
|
||||
|
||||
}
|
||||
private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void {
|
||||
if (!this.initialized || !bdcStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastUpdatedLabel.value =
|
||||
localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
|
||||
this.model.bdcStatusLastUpdated ?
|
||||
@@ -225,6 +259,44 @@ export class BdcDashboardOverviewPage {
|
||||
});
|
||||
}
|
||||
|
||||
private handleBdcError(errorEvent: BdcErrorEvent): void {
|
||||
if (errorEvent.errorType === 'bdcEndpoints') {
|
||||
const errorMessage = localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", errorEvent.error.message);
|
||||
this.showEndpointsError(errorMessage);
|
||||
} else if (errorEvent.errorType === 'bdcStatus') {
|
||||
this.showBdcStatusError(getBdcStatusErrorMessage(errorEvent.error));
|
||||
} else {
|
||||
this.handleGeneralError(errorEvent.error);
|
||||
}
|
||||
}
|
||||
|
||||
private showBdcStatusError(errorMessage: string): void {
|
||||
this.serviceStatusDisplayContainer.display = 'none';
|
||||
this.propertiesContainer.display = 'none';
|
||||
this.serviceStatusErrorMessage.value = errorMessage;
|
||||
this.serviceStatusErrorMessage.display = undefined;
|
||||
this.propertiesErrorMessage.value = errorMessage;
|
||||
this.propertiesErrorMessage.display = undefined;
|
||||
}
|
||||
|
||||
private showEndpointsError(errorMessage: string): void {
|
||||
this.endpointsDisplayContainer.display = 'none';
|
||||
this.endpointsErrorMessage.display = undefined;
|
||||
this.endpointsErrorMessage.value = errorMessage;
|
||||
}
|
||||
|
||||
private handleGeneralError(error: Error): void {
|
||||
if (error instanceof HdfsDialogCancelledError) {
|
||||
const errorMessage = localize('bdc.dashboard.noConnection', "The dashboard requires a connection. Please click retry to enter your credentials.");
|
||||
this.showBdcStatusError(errorMessage);
|
||||
this.showEndpointsError(errorMessage);
|
||||
} else {
|
||||
const errorMessage = localize('bdc.dashboard.unexpectedError', "Unexpected error occurred: {0}", error.message);
|
||||
this.showBdcStatusError(errorMessage);
|
||||
this.showEndpointsError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private createServiceStatusRow(container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void {
|
||||
const serviceStatusRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
|
||||
const statusIconCell = this.modelBuilder.text().withProperties({ value: getHealthStatusIcon(serviceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component();
|
||||
|
||||
@@ -269,3 +269,7 @@ export function getControllerEndpoint(serverInfo: azdata.ServerInfo): string | u
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getBdcStatusErrorMessage(error: Error): string {
|
||||
return localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", error.message);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class DivItem {
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" (click)="onClick()" (keyup)="onKey($event)">
|
||||
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" [style.display]="display" (click)="onClick()" (keyup)="onKey($event)">
|
||||
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
|
||||
Reference in New Issue
Block a user