add handlers for promise rejections (#8735)

This change adds to handlers to unexpected promise rejection scenarios.
This PR fixes #8640


* add handlers for promise rejections
* Displaying all tools load errors
* Update toolBase.ts - setting errorMessage to be displayed in the additional information  field
* disable the select button  when tools not discovered
* PR fixes
This commit is contained in:
Arvind Ranasaria
2020-01-03 14:18:01 -08:00
committed by GitHub
parent 5b34dd2eee
commit ef5ca7bc3a
5 changed files with 114 additions and 84 deletions

View File

@@ -254,7 +254,7 @@ export interface ITool {
readonly autoInstallNeeded: boolean; readonly autoInstallNeeded: boolean;
readonly isNotInstalled: boolean; readonly isNotInstalled: boolean;
readonly isInstalled: boolean; readonly isInstalled: boolean;
readonly installationPath?: string; readonly installationPathOrAdditionalInformation?: string;
readonly outputChannelName: string; readonly outputChannelName: string;
readonly fullVersion?: string; readonly fullVersion?: string;
readonly onDidUpdateData: vscode.Event<ITool>; readonly onDidUpdateData: vscode.Event<ITool>;

View File

@@ -43,6 +43,7 @@ export class DockerTool extends ToolBase {
} }
return version; return version;
} }
protected get versionCommand(): Command { protected get versionCommand(): Command {
return { command: 'docker version --format "{{json .}}"' }; return { command: 'docker version --format "{{json .}}"' };
} }

View File

@@ -139,8 +139,8 @@ export abstract class ToolBase implements ITool {
return this._statusDescription; return this._statusDescription;
} }
public get installationPath(): string | undefined { public get installationPathOrAdditionalInformation(): string | undefined {
return this._installationPath; return this._installationPathOrAdditionalInformation;
} }
protected get installationCommands(): Command[] | undefined { protected get installationCommands(): Command[] | undefined {
@@ -186,6 +186,7 @@ export abstract class ToolBase implements ITool {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
this._statusDescription = localize('toolBase.InstallError', "Error installing tool '{0}' [ {1} ].{2}Error: {3}{2}See output channel '{4}' for more details", this.displayName, this.homePage, EOL, errorMessage, this.outputChannelName); this._statusDescription = localize('toolBase.InstallError', "Error installing tool '{0}' [ {1} ].{2}Error: {3}{2}See output channel '{4}' for more details", this.displayName, this.homePage, EOL, errorMessage, this.outputChannelName);
this.status = ToolStatus.Error; this.status = ToolStatus.Error;
this._installationPathOrAdditionalInformation = errorMessage;
throw error; throw error;
} }
@@ -234,14 +235,33 @@ export abstract class ToolBase implements ITool {
}); });
} }
/**
* Sets the tool with discovered state and version information.
* Upon error the this.status field is set to ToolStatus.Error and this.statusDescription && this.installationPathOrAdditionalInformation is set to the corresponding error message
* and original error encountered is re-thrown so that it gets bubbled up to the caller.
*/
public async loadInformation(): Promise<void> { public async loadInformation(): Promise<void> {
try {
await this._pendingVersionAndStatusUpdate; await this._pendingVersionAndStatusUpdate;
} catch (error) {
this.status = ToolStatus.Error;
this._statusDescription = getErrorMessage(error);
this._installationPathOrAdditionalInformation = this._statusDescription;
throw error;
}
} }
private startVersionAndStatusUpdate() { /**
* Invokes the async method to update version and status for the tool.
*/
private startVersionAndStatusUpdate(): void {
this._statusDescription = '';
this._pendingVersionAndStatusUpdate = this.updateVersionAndStatus(); this._pendingVersionAndStatusUpdate = this.updateVersionAndStatus();
} }
/**
* updates the version and status for the tool.
*/
private async updateVersionAndStatus(): Promise<void> { private async updateVersionAndStatus(): Promise<void> {
this._statusDescription = ''; this._statusDescription = '';
await this.addInstallationSearchPathsToSystemPath(); await this.addInstallationSearchPathsToSystemPath();
@@ -292,7 +312,7 @@ export abstract class ToolBase implements ITool {
if (!commandOutput) { if (!commandOutput) {
throw new Error(`Install location of tool:'${this.displayName}' could not be discovered`); throw new Error(`Install location of tool:'${this.displayName}' could not be discovered`);
} else { } else {
this._installationPath = path.resolve(commandOutput.split(EOL)[0]); this._installationPathOrAdditionalInformation = path.resolve(commandOutput.split(EOL)[0]);
} }
} }
@@ -304,5 +324,5 @@ export abstract class ToolBase implements ITool {
private _status: ToolStatus = ToolStatus.NotInstalled; private _status: ToolStatus = ToolStatus.NotInstalled;
private _version?: SemVer; private _version?: SemVer;
private _statusDescription?: string; private _statusDescription?: string;
private _installationPath?: string; private _installationPathOrAdditionalInformation?: string;
} }

View File

@@ -38,7 +38,7 @@ export class ResourceTypePickerDialog extends DialogBase {
this._selectedResourceType = resourceType; this._selectedResourceType = resourceType;
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools")); this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
this._toDispose.push(this._installToolButton.onClick(() => { this._toDispose.push(this._installToolButton.onClick(() => {
this.installTools(); this.installTools().catch(error => console.log(error));
})); }));
this._dialogObject.customButtons = [this._installToolButton]; this._dialogObject.customButtons = [this._installToolButton];
this._installToolButton.hidden = true; this._installToolButton.hidden = true;
@@ -106,10 +106,10 @@ export class ResourceTypePickerDialog extends DialogBase {
}; };
const minVersionColumn: azdata.TableColumn = { const minVersionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"), value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"),
width: 90 width: 95
}; };
const installedPathColumn: azdata.TableColumn = { const installedPathColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path"), value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path or Additional Information"),
width: 570 width: 570
}; };
this._toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({ this._toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
@@ -197,34 +197,40 @@ export class ResourceTypePickerDialog extends DialogBase {
private updateToolsDisplayTable(): void { private updateToolsDisplayTable(): void {
this.toolRefreshTimestamp = new Date().getTime(); this.toolRefreshTimestamp = new Date().getTime();
const currentRefreshTimestamp = this.toolRefreshTimestamp; const currentRefreshTimestamp = this.toolRefreshTimestamp;
const toolRequirements = this.getCurrentProvider().requiredTools;
const headerRowHeight = 28; const headerRowHeight = 28;
this._toolsTable.height = 25 * Math.max(toolRequirements.length, 1) + headerRowHeight; this._toolsTable.height = 25 * Math.max(this.toolRequirements.length, 1) + headerRowHeight;
this._dialogObject.message = { this._dialogObject.message = {
text: '' text: ''
}; };
this._installToolButton.hidden = true; this._installToolButton.hidden = true;
if (toolRequirements.length === 0) { if (this.toolRequirements.length === 0) {
this._dialogObject.okButton.enabled = true; this._dialogObject.okButton.enabled = true;
this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']]; this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']];
this._tools = []; this._tools = [];
} else { } else {
this._tools = toolRequirements.map(toolReq => { this._tools = this.toolRequirements.map(toolReq => this.toolsService.getToolByName(toolReq.name)!);
return this.toolsService.getToolByName(toolReq.name)!;
});
this._toolsLoadingComponent.loading = true; this._toolsLoadingComponent.loading = true;
this._dialogObject.okButton.enabled = false; this._dialogObject.okButton.enabled = false;
let toolsLoadingErrors: string[] = [];
Promise.all( Promise.all(
this._tools.map(tool => tool.loadInformation()) this._tools.map(
).then(async () => { tool => tool.loadInformation().catch(() => toolsLoadingErrors.push(`${tool.displayName}:${tool.statusDescription!}`))
)
)
.then(() => this.executeToolsTableWorkflow(currentRefreshTimestamp, toolsLoadingErrors))
.catch(error => console.log(error));
}
}
private executeToolsTableWorkflow(currentRefreshTimestamp: number, toolsLoadingErrors: string[]): void {
// If the local timestamp does not match the class level timestamp, it means user has changed options, ignore the results // If the local timestamp does not match the class level timestamp, it means user has changed options, ignore the results
if (this.toolRefreshTimestamp !== currentRefreshTimestamp) { if (this.toolRefreshTimestamp !== currentRefreshTimestamp) {
return; return;
} }
let minVersionCheckFailed = false; let minVersionCheckFailed = false;
const toolsToAutoInstall: ITool[] = []; const toolsToAutoInstall: ITool[] = [];
let messages: string[] = []; let messages: string[] = toolsLoadingErrors!;
this._toolsTable.data = toolRequirements.map(toolRequirement => { this._toolsTable.data = this.toolRequirements.map(toolRequirement => {
const tool = this.toolsService.getToolByName(toolRequirement.name)!; const tool = this.toolsService.getToolByName(toolRequirement.name)!;
// subscribe to onUpdateData event of the tool. // subscribe to onUpdateData event of the tool.
this._toDispose.push(tool.onDidUpdateData((t: ITool) => { this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
@@ -233,17 +239,17 @@ export class ResourceTypePickerDialog extends DialogBase {
if (tool.isNotInstalled) { if (tool.isNotInstalled) {
if (tool.autoInstallSupported) { if (tool.autoInstallSupported) {
toolsToAutoInstall.push(tool); toolsToAutoInstall.push(tool);
} else { }
else {
messages.push(localize('deploymentDialog.ToolInformation', "'{0}' was not discovered and automated installation is not currently supported. Install '{0}' manually or ensure it is started and discoverable. Once done please restart Azure Data Studio. See [{1}] .", tool.displayName, tool.homePage)); messages.push(localize('deploymentDialog.ToolInformation', "'{0}' was not discovered and automated installation is not currently supported. Install '{0}' manually or ensure it is started and discoverable. Once done please restart Azure Data Studio. See [{1}] .", tool.displayName, tool.homePage));
} }
} else if (tool.isInstalled && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.version)) { }
else if (tool.isInstalled && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.version)) {
minVersionCheckFailed = true; minVersionCheckFailed = true;
messages.push(localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.", tool.displayName, tool.homePage)); messages.push(localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.", tool.displayName, tool.homePage));
} }
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPath || ''];
}); });
this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0); this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0); this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) { if (messages.length !== 0) {
@@ -255,7 +261,8 @@ export class ResourceTypePickerDialog extends DialogBase {
level: azdata.window.MessageLevel.Error, level: azdata.window.MessageLevel.Error,
text: messages.join(EOL) text: messages.join(EOL)
}; };
} else if ((toolsToAutoInstall.length !== 0) && !this._installationInProgress) { }
else if ((toolsToAutoInstall.length !== 0) && !this._installationInProgress) {
const installationNeededHeader = toolsToAutoInstall.length === 1 const installationNeededHeader = toolsToAutoInstall.length === 1
? localize('deploymentDialog.InstallToolsHintOne', "Tool: {0} is not installed, you can click the \"{1}\" button to install it.", toolsToAutoInstall[0].displayName, this._installToolButton.label) ? localize('deploymentDialog.InstallToolsHintOne', "Tool: {0} is not installed, you can click the \"{1}\" button to install it.", toolsToAutoInstall[0].displayName, this._installToolButton.label)
: localize('deploymentDialog.InstallToolsHintMany', "Tools: {0} are not installed, you can click the \"{1}\" button to install them.", toolsToAutoInstall.map(t => t.displayName).join(', '), this._installToolButton.label); : localize('deploymentDialog.InstallToolsHintMany', "Tools: {0} are not installed, you can click the \"{1}\" button to install them.", toolsToAutoInstall.map(t => t.displayName).join(', '), this._installToolButton.label);
@@ -277,8 +284,10 @@ export class ResourceTypePickerDialog extends DialogBase {
}; };
} }
this._toolsLoadingComponent.loading = false; this._toolsLoadingComponent.loading = false;
});
} }
private get toolRequirements() {
return this.getCurrentProvider().requiredTools;
} }
private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer { private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer {
@@ -326,7 +335,7 @@ export class ResourceTypePickerDialog extends DialogBase {
protected updateToolsDisplayTableData(tool: ITool) { protected updateToolsDisplayTableData(tool: ITool) {
this._toolsTable.data = this._toolsTable.data.map(rowData => { this._toolsTable.data = this._toolsTable.data.map(rowData => {
if (rowData[0] === tool.displayName) { if (rowData[0] === tool.displayName) {
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', rowData[4]/* required version*/, tool.installationPath || '']; return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', rowData[4]/* required version*/, tool.installationPathOrAdditionalInformation || ''];
} else { } else {
return rowData; return rowData;
} }
@@ -347,7 +356,7 @@ export class ResourceTypePickerDialog extends DialogBase {
this._installationInProgress = true; this._installationInProgress = true;
let tool: ITool; let tool: ITool;
try { try {
const toolRequirements = this.getCurrentProvider().requiredTools; const toolRequirements = this.toolRequirements;
let toolsNotInstalled: ITool[] = []; let toolsNotInstalled: ITool[] = [];
for (let i: number = 0; i < toolRequirements.length; i++) { for (let i: number = 0; i < toolRequirements.length; i++) {
const toolReq = toolRequirements[i]; const toolReq = toolRequirements[i];

View File

@@ -25,13 +25,13 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void {
// Use Set class to make sure the collection only contains unique values. // Use Set class to make sure the collection only contains unique values.
let installationPaths: Set<string> = new Set<string>(); let installationPaths: Set<string> = new Set<string>();
tools.forEach(t => { tools.forEach(t => {
if (t.installationPath) { if (t.installationPathOrAdditionalInformation) {
// construct an env variable name with NoteBookEnvironmentVariablePrefix prefix // construct an env variable name with NoteBookEnvironmentVariablePrefix prefix
// and tool.name as suffix, making sure of using all uppercase characters and only _ as separator // and tool.name as suffix, making sure of using all uppercase characters and only _ as separator
const envVarName = getRuntimeBinaryPathEnvironmentVariableName(t.name); const envVarName = getRuntimeBinaryPathEnvironmentVariableName(t.name);
process.env[envVarName] = t.installationPath; process.env[envVarName] = t.installationPathOrAdditionalInformation;
installationPaths.add(path.dirname(t.installationPath)); installationPaths.add(path.dirname(t.installationPathOrAdditionalInformation));
} }
}); });
if (installationPaths.size > 0) { if (installationPaths.size > 0) {