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> {
await this._pendingVersionAndStatusUpdate; 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(); 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,90 +197,99 @@ 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!}`))
// 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; .then(() => this.executeToolsTableWorkflow(currentRefreshTimestamp, toolsLoadingErrors))
} .catch(error => console.log(error));
let minVersionCheckFailed = false;
const toolsToAutoInstall: ITool[] = [];
let messages: string[] = [];
this._toolsTable.data = toolRequirements.map(toolRequirement => {
const tool = this.toolsService.getToolByName(toolRequirement.name)!;
// subscribe to onUpdateData event of the tool.
this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
this.updateToolsDisplayTableData(t);
}));
if (tool.isNotInstalled) {
if (tool.autoInstallSupported) {
toolsToAutoInstall.push(tool);
} 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)) {
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 || ''];
});
this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) {
if (messages.length > 1) {
messages = messages.map(message => `${message}`);
}
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in 'Deployments' output channel"));
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: messages.join(EOL)
};
} 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);
let infoText: string[] = [installationNeededHeader];
const informationalMessagesArray = this._tools.reduce<string[]>((returnArray, currentTool) => {
if (currentTool.autoInstallNeeded) {
returnArray.push(...currentTool.dependencyMessages);
}
return returnArray;
}, /* initial Value of return array*/[]);
const informationalMessagesSet: Set<string> = new Set<string>(informationalMessagesArray);
if (informationalMessagesSet.size > 0) {
infoText.push(...informationalMessagesSet.values());
}
// we don't have scenarios that have mixed type of tools - either we don't support auto install: docker, or we support auto install for all required tools
this._dialogObject.message = {
level: azdata.window.MessageLevel.Warning,
text: infoText.join(EOL)
};
}
this._toolsLoadingComponent.loading = false;
});
} }
} }
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[] = 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) => {
this.updateToolsDisplayTableData(t);
}));
if (tool.isNotInstalled) {
if (tool.autoInstallSupported) {
toolsToAutoInstall.push(tool);
}
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)) {
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.installationPathOrAdditionalInformation || ''];
});
this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) {
if (messages.length > 1) {
messages = messages.map(message => `${message}`);
}
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in 'Deployments' output channel"));
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: messages.join(EOL)
};
}
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);
let infoText: string[] = [installationNeededHeader];
const informationalMessagesArray = this._tools.reduce<string[]>((returnArray, currentTool) => {
if (currentTool.autoInstallNeeded) {
returnArray.push(...currentTool.dependencyMessages);
}
return returnArray;
}, /* initial Value of return array*/[]);
const informationalMessagesSet: Set<string> = new Set<string>(informationalMessagesArray);
if (informationalMessagesSet.size > 0) {
infoText.push(...informationalMessagesSet.values());
}
// we don't have scenarios that have mixed type of tools - either we don't support auto install: docker, or we support auto install for all required tools
this._dialogObject.message = {
level: azdata.window.MessageLevel.Warning,
text: infoText.join(EOL)
};
}
this._toolsLoadingComponent.loading = false;
}
private get toolRequirements() {
return this.getCurrentProvider().requiredTools;
}
private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer { private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer {
const checkbox = this._view.modelBuilder.checkBox().withProperties<azdata.CheckBoxProperties>({ const checkbox = this._view.modelBuilder.checkBox().withProperties<azdata.CheckBoxProperties>({
ariaLabel: this.getAgreementDisplayText(agreementInfo) ariaLabel: this.getAgreementDisplayText(agreementInfo)
@@ -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) {