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 isNotInstalled: boolean;
readonly isInstalled: boolean;
readonly installationPath?: string;
readonly installationPathOrAdditionalInformation?: string;
readonly outputChannelName: string;
readonly fullVersion?: string;
readonly onDidUpdateData: vscode.Event<ITool>;

View File

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

View File

@@ -139,8 +139,8 @@ export abstract class ToolBase implements ITool {
return this._statusDescription;
}
public get installationPath(): string | undefined {
return this._installationPath;
public get installationPathOrAdditionalInformation(): string | undefined {
return this._installationPathOrAdditionalInformation;
}
protected get installationCommands(): Command[] | undefined {
@@ -186,6 +186,7 @@ export abstract class ToolBase implements ITool {
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.status = ToolStatus.Error;
this._installationPathOrAdditionalInformation = errorMessage;
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> {
try {
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();
}
/**
* updates the version and status for the tool.
*/
private async updateVersionAndStatus(): Promise<void> {
this._statusDescription = '';
await this.addInstallationSearchPathsToSystemPath();
@@ -292,7 +312,7 @@ export abstract class ToolBase implements ITool {
if (!commandOutput) {
throw new Error(`Install location of tool:'${this.displayName}' could not be discovered`);
} 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 _version?: SemVer;
private _statusDescription?: string;
private _installationPath?: string;
private _installationPathOrAdditionalInformation?: string;
}

View File

@@ -38,7 +38,7 @@ export class ResourceTypePickerDialog extends DialogBase {
this._selectedResourceType = resourceType;
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
this._toDispose.push(this._installToolButton.onClick(() => {
this.installTools();
this.installTools().catch(error => console.log(error));
}));
this._dialogObject.customButtons = [this._installToolButton];
this._installToolButton.hidden = true;
@@ -106,10 +106,10 @@ export class ResourceTypePickerDialog extends DialogBase {
};
const minVersionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"),
width: 90
width: 95
};
const installedPathColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path"),
value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path or Additional Information"),
width: 570
};
this._toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
@@ -197,34 +197,40 @@ export class ResourceTypePickerDialog extends DialogBase {
private updateToolsDisplayTable(): void {
this.toolRefreshTimestamp = new Date().getTime();
const currentRefreshTimestamp = this.toolRefreshTimestamp;
const toolRequirements = this.getCurrentProvider().requiredTools;
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 = {
text: ''
};
this._installToolButton.hidden = true;
if (toolRequirements.length === 0) {
if (this.toolRequirements.length === 0) {
this._dialogObject.okButton.enabled = true;
this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']];
this._tools = [];
} else {
this._tools = toolRequirements.map(toolReq => {
return this.toolsService.getToolByName(toolReq.name)!;
});
this._tools = this.toolRequirements.map(toolReq => this.toolsService.getToolByName(toolReq.name)!);
this._toolsLoadingComponent.loading = true;
this._dialogObject.okButton.enabled = false;
let toolsLoadingErrors: string[] = [];
Promise.all(
this._tools.map(tool => tool.loadInformation())
).then(async () => {
this._tools.map(
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 (this.toolRefreshTimestamp !== currentRefreshTimestamp) {
return;
}
let minVersionCheckFailed = false;
const toolsToAutoInstall: ITool[] = [];
let messages: string[] = [];
this._toolsTable.data = toolRequirements.map(toolRequirement => {
let messages: string[] = toolsLoadingErrors!;
this._toolsTable.data = this.toolRequirements.map(toolRequirement => {
const tool = this.toolsService.getToolByName(toolRequirement.name)!;
// subscribe to onUpdateData event of the tool.
this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
@@ -233,17 +239,17 @@ export class ResourceTypePickerDialog extends DialogBase {
if (tool.isNotInstalled) {
if (tool.autoInstallSupported) {
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));
}
} else if (tool.isInstalled && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.version)) {
}
else if (tool.isInstalled && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.version)) {
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));
}
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPath || ''];
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
});
this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) {
@@ -255,7 +261,8 @@ export class ResourceTypePickerDialog extends DialogBase {
level: azdata.window.MessageLevel.Error,
text: messages.join(EOL)
};
} else if ((toolsToAutoInstall.length !== 0) && !this._installationInProgress) {
}
else if ((toolsToAutoInstall.length !== 0) && !this._installationInProgress) {
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.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;
});
}
private get toolRequirements() {
return this.getCurrentProvider().requiredTools;
}
private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer {
@@ -326,7 +335,7 @@ export class ResourceTypePickerDialog extends DialogBase {
protected updateToolsDisplayTableData(tool: ITool) {
this._toolsTable.data = this._toolsTable.data.map(rowData => {
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 {
return rowData;
}
@@ -347,7 +356,7 @@ export class ResourceTypePickerDialog extends DialogBase {
this._installationInProgress = true;
let tool: ITool;
try {
const toolRequirements = this.getCurrentProvider().requiredTools;
const toolRequirements = this.toolRequirements;
let toolsNotInstalled: ITool[] = [];
for (let i: number = 0; i < toolRequirements.length; 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.
let installationPaths: Set<string> = new Set<string>();
tools.forEach(t => {
if (t.installationPath) {
if (t.installationPathOrAdditionalInformation) {
// construct an env variable name with NoteBookEnvironmentVariablePrefix prefix
// and tool.name as suffix, making sure of using all uppercase characters and only _ as separator
const envVarName = getRuntimeBinaryPathEnvironmentVariableName(t.name);
process.env[envVarName] = t.installationPath;
installationPaths.add(path.dirname(t.installationPath));
process.env[envVarName] = t.installationPathOrAdditionalInformation;
installationPaths.add(path.dirname(t.installationPathOrAdditionalInformation));
}
});
if (installationPaths.size > 0) {