Feat/tool install master merge back to master (#7819)

* add install tools button (#7454)

* add install tools button

* address comments

* remove description for install tools hint message

* First working version of AutoDeployment of tools (#7647)

First working version of AutoDeployment of tools.

This pull request adds feature to install the tools needed for doing BDC/TINA deployments.

This has been tested so far only on win32 and testing on other platforms is in progress.

* removing TODO and redundant code

* Not localizing azuredatastudio product name

* convert methods returning Promises to async-await

* changing from null to undefined

* Localize all the command labels

* using existing sudo-prompt typings

* progres/error status in ModalDialogue && PR fixes

* review feedback to change warning to information

* revert settings.json changes

* fix resource-Deployment Extension Unit Test

* ensuring platform service's working directory

* incorporate review feedback

* review feedback

* addressing PR feedback

* PR fixes

* PR Feedback

* remove debug logs

* disable UI deployment containers when installing

* addding data type to stdout/stderr messaging

* remove commented code

* revert accidental change

* addressing review feedback

* fix failed install with zero exit code

* fixing bug due to typo

* fixes for linux

* Misc fixes during mac testing

* PR fixes
This commit is contained in:
Arvind Ranasaria
2019-10-18 23:17:21 -07:00
committed by GitHub
parent a2f105a913
commit 4dd6db57ee
18 changed files with 893 additions and 141 deletions

View File

@@ -2,14 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { EOL } from 'os';
import * as nls from 'vscode-nls';
import { DialogBase } from './dialogBase';
import { ResourceType, AgreementInfo, DeploymentProvider } from '../interfaces';
import { AgreementInfo, DeploymentProvider, ITool, ResourceType } from '../interfaces';
import { IResourceTypeService } from '../services/resourceTypeService';
import { IToolsService } from '../services/toolsService';
import { EOL } from 'os';
import { getErrorMessage } from '../utils';
import { DialogBase } from './dialogBase';
import { createFlexContainer } from './modelViewUtils';
const localize = nls.loadMessageBundle();
@@ -27,6 +27,9 @@ export class ResourceTypePickerDialog extends DialogBase {
private _toolsLoadingComponent!: azdata.LoadingComponent;
private _agreementContainer!: azdata.DivContainer;
private _agreementCheckboxChecked: boolean = false;
private _installToolButton: azdata.window.Button;
private _tools: ITool[] = [];
private _cardsContainer!: azdata.FlexContainer;
constructor(
private toolsService: IToolsService,
@@ -34,7 +37,14 @@ export class ResourceTypePickerDialog extends DialogBase {
resourceType: ResourceType) {
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
this._selectedResourceType = resourceType;
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Select');
this._dialogObject.okButton.onClick(() => this.onComplete());
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
this._toDispose.push(this._installToolButton.onClick(() => {
this.installTools();
}));
this._dialogObject.customButtons = [this._installToolButton];
this._installToolButton.hidden = true;
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
}
initialize() {
@@ -55,24 +65,24 @@ export class ResourceTypePickerDialog extends DialogBase {
this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => {
return (a.displayIndex || Number.MAX_VALUE) - (b.displayIndex || Number.MAX_VALUE);
}).forEach(resourceType => this.addCard(resourceType));
const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component();
this._cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component();
this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component();
this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this._agreementContainer = view.modelBuilder.divContainer().component();
const toolColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolNameColumnHeader', 'Tool'),
value: localize('deploymentDialog.toolNameColumnHeader', "Tool"),
width: 150
};
const descriptionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolDescriptionColumnHeader', 'Description'),
value: localize('deploymentDialog.toolDescriptionColumnHeader', "Description"),
width: 650
};
const installStatusColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolStatusColumnHeader', 'Installed'),
value: localize('deploymentDialog.toolStatusColumnHeader', "Status"),
width: 100
};
const versionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolVersionColumnHeader', 'Version'),
value: localize('deploymentDialog.toolVersionColumnHeader', "Version"),
width: 100
};
@@ -88,7 +98,7 @@ export class ResourceTypePickerDialog extends DialogBase {
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: cardsContainer,
component: this._cardsContainer,
title: ''
}, {
component: this._resourceDescriptionLabel,
@@ -99,10 +109,10 @@ export class ResourceTypePickerDialog extends DialogBase {
},
{
component: this._optionsContainer,
title: localize('deploymentDialog.OptionsTitle', 'Options')
title: localize('deploymentDialog.OptionsTitle', "Options")
}, {
component: this._toolsLoadingComponent,
title: localize('deploymentDialog.RequiredToolsTitle', 'Required tools')
title: localize('deploymentDialog.RequiredToolsTitle', "Required tools")
}
],
{
@@ -178,15 +188,15 @@ export class ResourceTypePickerDialog extends DialogBase {
width: '300px'
}).component();
this._toDispose.push(optionSelectBox.onValueChanged(() => { this.updateTools(); }));
this._toDispose.push(optionSelectBox.onValueChanged(() => { this.updateToolsDisplayTable(); }));
this._optionDropDownMap.set(option.name, optionSelectBox);
const row = this._view.modelBuilder.flexContainer().withItems([optionLabel, optionSelectBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
this._optionsContainer.addItem(row);
});
this.updateTools();
this.updateToolsDisplayTable();
}
private updateTools(): void {
private updateToolsDisplayTable(): void {
this.toolRefreshTimestamp = new Date().getTime();
const currentRefreshTimestamp = this.toolRefreshTimestamp;
const toolRequirements = this.getCurrentProvider().requiredTools;
@@ -195,40 +205,58 @@ export class ResourceTypePickerDialog extends DialogBase {
this._dialogObject.message = {
text: ''
};
this._installToolButton.hidden = true;
if (toolRequirements.length === 0) {
this._dialogObject.okButton.enabled = true;
this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']];
this._tools = [];
} else {
const tools = toolRequirements.map(toolReq => {
this._tools = toolRequirements.map(toolReq => {
return this.toolsService.getToolByName(toolReq.name)!;
});
this._toolsLoadingComponent.loading = true;
this._dialogObject.okButton.enabled = false;
Promise.all(tools.map(tool => tool.loadInformation())).then(() => {
Promise.all(this._tools.map(tool => tool.loadInformation())).then(async () => {
// 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 autoInstallRequired = false;
const messages: string[] = [];
this._toolsTable.data = toolRequirements.map(toolRef => {
const tool = this.toolsService.getToolByName(toolRef.name)!;
if (!tool.isInstalled) {
messages.push(localize('deploymentDialog.ToolInformation', "{0}: {1}", tool.displayName, tool.homePage));
this._toolsTable.data = toolRequirements.map(toolReq => {
const tool = this.toolsService.getToolByName(toolReq.name)!;
// subscribe to onUpdateData event of the tool.
this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
this.updateToolsDisplayTableData(t);
}));
if (tool.isNotInstalled && !tool.autoInstallSupported) {
messages.push(localize('deploymentDialog.ToolInformation', "'{0}' [ {1} ]", tool.displayName, tool.homePage));
if (tool.statusDescription !== undefined) {
console.warn(localize('deploymentDialog.DetailToolStatusDescription', "Additional status information for tool: {0}. {1}", tool.name, tool.statusDescription));
console.warn(localize('deploymentDialog.DetailToolStatusDescription', "Additional status information for tool: '{0}' [ {1} ]. {2}", tool.name, tool.homePage, tool.statusDescription));
}
}
return [tool.displayName, tool.description, tool.isInstalled ? localize('deploymentDialog.YesText', "Yes") : localize('deploymentDialog.NoText', "No"), tool.version ? tool.version.version : ''];
autoInstallRequired = tool.autoInstallRequired;
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || ''];
});
this._dialogObject.okButton.enabled = messages.length === 0;
this._installToolButton.hidden = !autoInstallRequired;
this._dialogObject.okButton.enabled = messages.length === 0 && !autoInstallRequired;
if (messages.length !== 0) {
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in the debug console."));
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: localize('deploymentDialog.ToolCheckFailed', "Some required tools are not installed or do not meet the minimum version requirement."),
text: localize('deploymentDialog.ToolCheckFailed', "Some required tools are not installed."),
description: messages.join(EOL)
};
} else if (autoInstallRequired) {
// 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.Information,
text: localize('deploymentDialog.InstallToolsHint', "Some required tools are not installed, you can click the \"{0}\" button to install them.", this._installToolButton.label)
};
}
this._toolsLoadingComponent.loading = false;
});
@@ -263,4 +291,56 @@ export class ResourceTypePickerDialog extends DialogBase {
protected onComplete(): void {
this.resourceTypeService.startDeployment(this.getCurrentProvider());
}
public 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 || ''];
} else {
return rowData;
}
});
this.enableUiControlsWhenNotInstalling(!tool.isInstalling); // if installing the disableContainers else enable them
}
private enableUiControlsWhenNotInstalling(enabled: boolean): void {
this._cardsContainer.enabled = enabled;
this._agreementContainer.enabled = enabled;
this._optionsContainer.enabled = enabled;
this._dialogObject.cancelButton.enabled = enabled;
// select and install tools button are controlled separately
}
private async installTools(): Promise<void> {
this._installToolButton.enabled = false;
let i: number = 0;
try {
for (; i < this._tools.length; i++) {
if (this._tools[i].needsInstallation) {
// Update the informational message
this._dialogObject.message = {
level: azdata.window.MessageLevel.Information,
text: localize('deploymentDialog.InstallingTool', "Required tool '{0}' [ {1} ] is being installed now.", this._tools[i].displayName, this._tools[i].homePage)
};
await this._tools[i].install();
}
}
// Update the informational message
this._dialogObject.message = {
level: azdata.window.MessageLevel.Information,
text: localize('deploymentDialog.InstalledTools', "All required tools are installed now.")
};
this._dialogObject.okButton.enabled = true;
} catch (error) {
const errorMessage = this._tools[i].statusDescription || getErrorMessage(error);
if (errorMessage) {
// Let the tooltip status show the errorMessage just shown so that last status is visible even after showError dialogue has been dismissed.
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: errorMessage
};
}
this._tools[i].showOutputChannel(/*preserverFocus*/false);
}
}
}

View File

@@ -68,19 +68,19 @@ export abstract class WizardBase<T, M extends Model> {
}
private dispose() {
let errorOccured = false;
let errorOccurred = false;
this.toDispose.forEach((disposable: vscode.Disposable) => {
try {
disposable.dispose();
}
catch (error) {
errorOccured = true;
errorOccurred = true;
console.error(error);
}
});
if (errorOccured) {
vscode.window.showErrorMessage(localize('resourceDeployment.DisposableError', "Error occured while closing the wizard: {0}, open 'Debugger Console' for more information."), this.title);
if (errorOccurred) {
vscode.window.showErrorMessage(localize('resourceDeployment.DisposableError', "Error occurred while closing the wizard: {0}, open 'Debugger Console' for more information."), this.title);
}
}