mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user