mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Hide installation options in Configure Python wizard if python is already setup. (#10635)
This commit is contained in:
@@ -15,23 +15,37 @@ const localize = nls.loadMessageBundle();
|
|||||||
|
|
||||||
export class ConfigurePathPage extends BasePage {
|
export class ConfigurePathPage extends BasePage {
|
||||||
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Browse");
|
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Browse");
|
||||||
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
|
|
||||||
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
|
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
|
||||||
|
|
||||||
private pythonLocationDropdown: azdata.DropDownComponent;
|
private pythonLocationDropdown: azdata.DropDownComponent;
|
||||||
private pythonDropdownLoader: azdata.LoadingComponent;
|
private pythonDropdownLoader: azdata.LoadingComponent;
|
||||||
private browseButton: azdata.ButtonComponent;
|
|
||||||
private newInstallButton: azdata.RadioButtonComponent;
|
private newInstallButton: azdata.RadioButtonComponent;
|
||||||
private existingInstallButton: azdata.RadioButtonComponent;
|
private existingInstallButton: azdata.RadioButtonComponent;
|
||||||
|
|
||||||
|
private selectInstallEnabled: boolean;
|
||||||
private usingCustomPath: boolean = false;
|
private usingCustomPath: boolean = false;
|
||||||
|
|
||||||
public async initialize(): Promise<boolean> {
|
public async initialize(): Promise<boolean> {
|
||||||
|
let wizardDescription: string;
|
||||||
|
if (this.model.kernelName) {
|
||||||
|
wizardDescription = localize('configurePython.descriptionWithKernel', "The {0} kernel requires a Python runtime to be configured and dependencies to be installed.", this.model.kernelName);
|
||||||
|
} else {
|
||||||
|
wizardDescription = localize('configurePython.descriptionWithoutKernel', "Notebook kernels require a Python runtime to be configured and dependencies to be installed.");
|
||||||
|
}
|
||||||
|
let wizardDescriptionLabel = this.view.modelBuilder.text()
|
||||||
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
|
value: wizardDescription,
|
||||||
|
CSSStyles: {
|
||||||
|
'padding': '0px',
|
||||||
|
'margin': '0px'
|
||||||
|
}
|
||||||
|
}).component();
|
||||||
|
|
||||||
this.pythonLocationDropdown = this.view.modelBuilder.dropDown()
|
this.pythonLocationDropdown = this.view.modelBuilder.dropDown()
|
||||||
.withProperties<azdata.DropDownProperties>({
|
.withProperties<azdata.DropDownProperties>({
|
||||||
value: undefined,
|
value: undefined,
|
||||||
values: [],
|
values: [],
|
||||||
width: '100%'
|
width: '400px'
|
||||||
}).component();
|
}).component();
|
||||||
this.pythonDropdownLoader = this.view.modelBuilder.loadingComponent()
|
this.pythonDropdownLoader = this.view.modelBuilder.loadingComponent()
|
||||||
.withItem(this.pythonLocationDropdown)
|
.withItem(this.pythonLocationDropdown)
|
||||||
@@ -39,17 +53,16 @@ export class ConfigurePathPage extends BasePage {
|
|||||||
loading: false
|
loading: false
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
|
let browseButton = this.view.modelBuilder.button()
|
||||||
this.browseButton = this.view.modelBuilder.button()
|
|
||||||
.withProperties<azdata.ButtonProperties>({
|
.withProperties<azdata.ButtonProperties>({
|
||||||
label: this.BrowseButtonText,
|
label: this.BrowseButtonText,
|
||||||
width: '70px'
|
width: '70px'
|
||||||
}).component();
|
}).component();
|
||||||
this.browseButton.onDidClick(() => this.handleBrowse());
|
browseButton.onDidClick(() => this.handleBrowse());
|
||||||
|
|
||||||
this.createInstallRadioButtons(this.view.modelBuilder, this.model.useExistingPython);
|
this.createInstallRadioButtons(this.view.modelBuilder, this.model.useExistingPython);
|
||||||
|
|
||||||
let formModel = this.view.modelBuilder.formContainer()
|
let selectInstallForm = this.view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: this.newInstallButton,
|
component: this.newInstallButton,
|
||||||
title: localize('configurePython.installationType', "Installation Type")
|
title: localize('configurePython.installationType', "Installation Type")
|
||||||
@@ -58,14 +71,66 @@ export class ConfigurePathPage extends BasePage {
|
|||||||
title: ''
|
title: ''
|
||||||
}, {
|
}, {
|
||||||
component: this.pythonDropdownLoader,
|
component: this.pythonDropdownLoader,
|
||||||
title: this.LocationTextBoxTitle
|
title: localize('configurePython.locationTextBoxText', "Python Install Location")
|
||||||
}, {
|
}, {
|
||||||
component: this.browseButton,
|
component: browseButton,
|
||||||
title: ''
|
title: ''
|
||||||
}]).component();
|
}]).component();
|
||||||
|
let selectInstallContainer = this.view.modelBuilder.divContainer()
|
||||||
|
.withItems([selectInstallForm])
|
||||||
|
.withProperties<azdata.DivContainerProperties>({
|
||||||
|
clickable: false
|
||||||
|
}).component();
|
||||||
|
|
||||||
await this.view.initializeModel(formModel);
|
let allParentItems = [selectInstallContainer];
|
||||||
|
if (this.model.pythonLocation) {
|
||||||
|
let installedPathTextBox = this.view.modelBuilder.inputBox().withProperties<azdata.TextComponentProperties>({
|
||||||
|
value: this.model.pythonLocation,
|
||||||
|
enabled: false,
|
||||||
|
width: '400px'
|
||||||
|
}).component();
|
||||||
|
let editPathButton = this.view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
label: 'Edit',
|
||||||
|
width: '70px'
|
||||||
|
}).component();
|
||||||
|
let editPathForm = this.view.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
title: localize('configurePython.pythonConfigured', "Python runtime configured!"),
|
||||||
|
component: installedPathTextBox
|
||||||
|
}, {
|
||||||
|
title: '',
|
||||||
|
component: editPathButton
|
||||||
|
}]).component();
|
||||||
|
|
||||||
|
let editPathContainer = this.view.modelBuilder.divContainer()
|
||||||
|
.withItems([editPathForm])
|
||||||
|
.withProperties<azdata.DivContainerProperties>({
|
||||||
|
clickable: false
|
||||||
|
}).component();
|
||||||
|
allParentItems.push(editPathContainer);
|
||||||
|
|
||||||
|
editPathButton.onDidClick(async () => {
|
||||||
|
editPathContainer.display = 'none';
|
||||||
|
selectInstallContainer.display = 'block';
|
||||||
|
this.selectInstallEnabled = true;
|
||||||
|
});
|
||||||
|
selectInstallContainer.display = 'none';
|
||||||
|
|
||||||
|
this.selectInstallEnabled = false;
|
||||||
|
} else {
|
||||||
|
this.selectInstallEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentContainer = this.view.modelBuilder.flexContainer()
|
||||||
|
.withLayout({ flexFlow: 'column' }).component();
|
||||||
|
parentContainer.addItem(wizardDescriptionLabel, {
|
||||||
|
CSSStyles: {
|
||||||
|
'padding': '0px 30px 0px 30px'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
parentContainer.addItems(allParentItems);
|
||||||
|
|
||||||
|
await this.view.initializeModel(parentContainer);
|
||||||
await this.updatePythonPathsDropdown(this.model.useExistingPython);
|
await this.updatePythonPathsDropdown(this.model.useExistingPython);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -75,6 +140,10 @@ export class ConfigurePathPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<boolean> {
|
public async onPageLeave(): Promise<boolean> {
|
||||||
|
if (this.pythonDropdownLoader.loading) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.selectInstallEnabled) {
|
||||||
let pythonLocation = utils.getDropdownValue(this.pythonLocationDropdown);
|
let pythonLocation = utils.getDropdownValue(this.pythonLocationDropdown);
|
||||||
if (!pythonLocation || pythonLocation.length === 0) {
|
if (!pythonLocation || pythonLocation.length === 0) {
|
||||||
this.instance.showErrorMessage(this.instance.InvalidLocationMsg);
|
this.instance.showErrorMessage(this.instance.InvalidLocationMsg);
|
||||||
@@ -83,11 +152,12 @@ export class ConfigurePathPage extends BasePage {
|
|||||||
|
|
||||||
this.model.pythonLocation = pythonLocation;
|
this.model.pythonLocation = pythonLocation;
|
||||||
this.model.useExistingPython = !!this.existingInstallButton.checked;
|
this.model.useExistingPython = !!this.existingInstallButton.checked;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updatePythonPathsDropdown(useExistingPython: boolean): Promise<void> {
|
private async updatePythonPathsDropdown(useExistingPython: boolean): Promise<void> {
|
||||||
|
this.instance.wizard.nextButton.enabled = false;
|
||||||
this.pythonDropdownLoader.loading = true;
|
this.pythonDropdownLoader.loading = true;
|
||||||
try {
|
try {
|
||||||
let pythonPaths: PythonPathInfo[];
|
let pythonPaths: PythonPathInfo[];
|
||||||
@@ -121,6 +191,7 @@ export class ConfigurePathPage extends BasePage {
|
|||||||
values: dropdownValues
|
values: dropdownValues
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
this.instance.wizard.nextButton.enabled = true;
|
||||||
this.pythonDropdownLoader.loading = false;
|
this.pythonDropdownLoader.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export class ConfigurePythonWizard {
|
|||||||
kernelName: kernelName,
|
kernelName: kernelName,
|
||||||
pythonPathsPromise: this.pythonPathsPromise,
|
pythonPathsPromise: this.pythonPathsPromise,
|
||||||
installation: this.jupyterInstallation,
|
installation: this.jupyterInstallation,
|
||||||
|
pythonLocation: JupyterServerInstallation.getPythonPathSetting(this.apiWrapper),
|
||||||
useExistingPython: JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper)
|
useExistingPython: JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,31 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { BasePage } from './basePage';
|
import { BasePage } from './basePage';
|
||||||
import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
import { python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName } from '../../common/constants';
|
import { python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName } from '../../common/constants';
|
||||||
import { getDropdownValue } from '../../common/utils';
|
import { getDropdownValue } from '../../common/utils';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
interface RequiredPackageInfo {
|
||||||
|
name: string;
|
||||||
|
existingVersion: string;
|
||||||
|
requiredVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace cssStyles {
|
||||||
|
export const tableHeader = { 'text-align': 'left', 'font-weight': 'lighter', 'font-size': '10px', 'user-select': 'text', 'border': 'none' };
|
||||||
|
export const tableRow = { 'border-top': 'solid 1px #ccc', 'border-bottom': 'solid 1px #ccc', 'border-left': 'none', 'border-right': 'none' };
|
||||||
|
}
|
||||||
|
|
||||||
export class PickPackagesPage extends BasePage {
|
export class PickPackagesPage extends BasePage {
|
||||||
private kernelLabel: azdata.TextComponent | undefined;
|
private kernelLabel: azdata.TextComponent | undefined;
|
||||||
private kernelDropdown: azdata.DropDownComponent | undefined;
|
private kernelDropdown: azdata.DropDownComponent | undefined;
|
||||||
private requiredPackagesTable: azdata.DeclarativeTableComponent;
|
private requiredPackagesTable: azdata.DeclarativeTableComponent;
|
||||||
private packageTableSpinner: azdata.LoadingComponent;
|
private packageTableSpinner: azdata.LoadingComponent;
|
||||||
|
|
||||||
private installedPackagesPromise: Promise<PythonPkgDetails[]>;
|
private packageVersionRetrieval: Promise<void>;
|
||||||
private installedPackages: PythonPkgDetails[];
|
private packageVersionMap = new Map<string, string>();
|
||||||
|
|
||||||
public async initialize(): Promise<boolean> {
|
public async initialize(): Promise<boolean> {
|
||||||
if (this.model.kernelName) {
|
if (this.model.kernelName) {
|
||||||
@@ -39,22 +50,46 @@ export class PickPackagesPage extends BasePage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nameColumn = localize('configurePython.pkgNameColumn', "Name");
|
||||||
|
let existingVersionColumn = localize('configurePython.existingVersionColumn', "Existing Version");
|
||||||
|
let requiredVersionColumn = localize('configurePython.requiredVersionColumn', "Required Version");
|
||||||
this.requiredPackagesTable = this.view.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
this.requiredPackagesTable = this.view.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||||
columns: [{
|
columns: [{
|
||||||
displayName: localize('configurePython.pkgNameColumn', "Name"),
|
displayName: nameColumn,
|
||||||
|
ariaLabel: nameColumn,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: '200px'
|
width: '200px',
|
||||||
|
headerCssStyles: {
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
displayName: localize('configurePython.existingVersionColumn', "Existing Version"),
|
displayName: existingVersionColumn,
|
||||||
|
ariaLabel: existingVersionColumn,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: '200px'
|
width: '200px',
|
||||||
|
headerCssStyles: {
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
displayName: localize('configurePython.requiredVersionColumn', "Required Version"),
|
displayName: requiredVersionColumn,
|
||||||
|
ariaLabel: requiredVersionColumn,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: '200px'
|
width: '200px',
|
||||||
|
headerCssStyles: {
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow
|
||||||
|
}
|
||||||
}],
|
}],
|
||||||
data: [[]]
|
data: [[]]
|
||||||
}).component();
|
}).component();
|
||||||
@@ -74,9 +109,16 @@ export class PickPackagesPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(): Promise<void> {
|
||||||
|
this.packageVersionMap.clear();
|
||||||
let pythonExe = JupyterServerInstallation.getPythonExePath(this.model.pythonLocation, this.model.useExistingPython);
|
let pythonExe = JupyterServerInstallation.getPythonExePath(this.model.pythonLocation, this.model.useExistingPython);
|
||||||
this.installedPackagesPromise = this.model.installation.getInstalledPipPackages(pythonExe);
|
this.packageVersionRetrieval = this.model.installation.getInstalledPipPackages(pythonExe)
|
||||||
this.installedPackages = undefined;
|
.then(installedPackages => {
|
||||||
|
if (installedPackages) {
|
||||||
|
installedPackages.forEach(pkg => {
|
||||||
|
this.packageVersionMap.set(pkg.name, pkg.version);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this.kernelDropdown) {
|
if (this.kernelDropdown) {
|
||||||
if (this.model.kernelName) {
|
if (this.model.kernelName) {
|
||||||
@@ -89,45 +131,39 @@ export class PickPackagesPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<boolean> {
|
public async onPageLeave(): Promise<boolean> {
|
||||||
return true;
|
return !this.packageTableSpinner.loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateRequiredPackages(kernelName: string): Promise<void> {
|
private async updateRequiredPackages(kernelName: string): Promise<void> {
|
||||||
|
this.instance.wizard.doneButton.enabled = false;
|
||||||
this.packageTableSpinner.loading = true;
|
this.packageTableSpinner.loading = true;
|
||||||
try {
|
try {
|
||||||
let pkgVersionMap = new Map<string, { currentVersion: string, newVersion: string }>();
|
|
||||||
|
|
||||||
// Fetch list of required packages for the specified kernel
|
// Fetch list of required packages for the specified kernel
|
||||||
let requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
|
let requiredPkgVersions: RequiredPackageInfo[] = [];
|
||||||
|
let requiredPackages = this.model.installation.getRequiredPackagesForKernel(kernelName);
|
||||||
requiredPackages.forEach(pkg => {
|
requiredPackages.forEach(pkg => {
|
||||||
pkgVersionMap.set(pkg.name, { currentVersion: undefined, newVersion: pkg.version });
|
requiredPkgVersions.push({ name: pkg.name, existingVersion: undefined, requiredVersion: pkg.version });
|
||||||
});
|
});
|
||||||
|
|
||||||
// For each required package, check if there is another version of that package already installed
|
// For each required package, check if there is another version of that package already installed
|
||||||
if (!this.installedPackages) {
|
await this.packageVersionRetrieval;
|
||||||
this.installedPackages = await this.installedPackagesPromise;
|
requiredPkgVersions.forEach(pkgVersion => {
|
||||||
}
|
let installedPackageVersion = this.packageVersionMap.get(pkgVersion.name);
|
||||||
this.installedPackages.forEach(pkg => {
|
if (installedPackageVersion) {
|
||||||
let info = pkgVersionMap.get(pkg.name);
|
pkgVersion.existingVersion = installedPackageVersion;
|
||||||
if (info) {
|
|
||||||
info.currentVersion = pkg.version;
|
|
||||||
pkgVersionMap.set(pkg.name, info);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pkgVersionMap.size > 0) {
|
if (requiredPkgVersions.length > 0) {
|
||||||
let packageData = [];
|
this.requiredPackagesTable.data = requiredPkgVersions.map(pkg => [pkg.name, pkg.existingVersion ?? '-', pkg.requiredVersion]);
|
||||||
for (let [key, value] of pkgVersionMap.entries()) {
|
|
||||||
packageData.push([key, value.currentVersion ?? '-', value.newVersion]);
|
|
||||||
}
|
|
||||||
this.requiredPackagesTable.data = packageData;
|
|
||||||
this.model.packagesToInstall = requiredPackages;
|
this.model.packagesToInstall = requiredPackages;
|
||||||
} else {
|
} else {
|
||||||
this.instance.showErrorMessage(localize('msgUnsupportedKernel', "Could not retrieve packages for unsupported kernel {0}", kernelName));
|
this.instance.showErrorMessage(localize('msgUnsupportedKernel', "Could not retrieve packages for kernel {0}", kernelName));
|
||||||
this.requiredPackagesTable.data = [['-', '-', '-']];
|
this.requiredPackagesTable.data = [['-', '-', '-']];
|
||||||
this.model.packagesToInstall = undefined;
|
this.model.packagesToInstall = undefined;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
this.instance.wizard.doneButton.enabled = true;
|
||||||
this.packageTableSpinner.loading = false;
|
this.packageTableSpinner.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,8 +251,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
||||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
if (jupyterInstaller.previewFeaturesEnabled) {
|
||||||
if (enablePreviewFeatures) {
|
|
||||||
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, jupyterInstaller);
|
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, jupyterInstaller);
|
||||||
pythonWizard.start().catch((err: any) => {
|
pythonWizard.start().catch((err: any) => {
|
||||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const msgTaskName = localize('msgTaskName', "Installing Notebook dependencies");
|
|||||||
const msgInstallPkgStart = localize('msgInstallPkgStart', "Installing Notebook dependencies, see Tasks view for more information");
|
const msgInstallPkgStart = localize('msgInstallPkgStart', "Installing Notebook dependencies, see Tasks view for more information");
|
||||||
const msgInstallPkgFinish = localize('msgInstallPkgFinish', "Notebook dependencies installation is complete");
|
const msgInstallPkgFinish = localize('msgInstallPkgFinish', "Notebook dependencies installation is complete");
|
||||||
const msgPythonRunningError = localize('msgPythonRunningError', "Cannot overwrite an existing Python installation while python is running. Please close any active notebooks before proceeding.");
|
const msgPythonRunningError = localize('msgPythonRunningError', "Cannot overwrite an existing Python installation while python is running. Please close any active notebooks before proceeding.");
|
||||||
const msgSkipPythonInstall = localize('msgSkipPythonInstall', "Python already exists at the specific location. Skipping install.");
|
|
||||||
const msgWaitingForInstall = localize('msgWaitingForInstall', "Another Python installation is currently in progress. Waiting for it to complete.");
|
const msgWaitingForInstall = localize('msgWaitingForInstall', "Another Python installation is currently in progress. Waiting for it to complete.");
|
||||||
function msgDependenciesInstallationFailed(errorMessage: string): string { return localize('msgDependenciesInstallationFailed', "Installing Notebook dependencies failed with error: {0}", errorMessage); }
|
function msgDependenciesInstallationFailed(errorMessage: string): string { return localize('msgDependenciesInstallationFailed', "Installing Notebook dependencies failed with error: {0}", errorMessage); }
|
||||||
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); }
|
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); }
|
||||||
@@ -110,6 +109,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
private readonly _expectedCondaPackages: PythonPkgDetails[];
|
private readonly _expectedCondaPackages: PythonPkgDetails[];
|
||||||
|
|
||||||
private _kernelSetupCache: Map<string, boolean>;
|
private _kernelSetupCache: Map<string, boolean>;
|
||||||
|
private readonly _requiredKernelPackages: Map<string, PythonPkgDetails[]>;
|
||||||
|
private readonly _requiredPackagesSet: Set<string>;
|
||||||
|
|
||||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||||
this.extensionPath = extensionPath;
|
this.extensionPath = extensionPath;
|
||||||
@@ -129,10 +130,46 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._kernelSetupCache = new Map<string, boolean>();
|
this._kernelSetupCache = new Map<string, boolean>();
|
||||||
|
this._requiredKernelPackages = new Map<string, PythonPkgDetails[]>();
|
||||||
|
|
||||||
|
let jupyterPkg = {
|
||||||
|
name: 'jupyter',
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
this._requiredKernelPackages.set(constants.python3DisplayName, [jupyterPkg]);
|
||||||
|
|
||||||
|
let powershellPkg = {
|
||||||
|
name: 'powershell-kernel',
|
||||||
|
version: '0.1.3'
|
||||||
|
};
|
||||||
|
this._requiredKernelPackages.set(constants.powershellDisplayName, [jupyterPkg, powershellPkg]);
|
||||||
|
|
||||||
|
let sparkPackages = [
|
||||||
|
jupyterPkg,
|
||||||
|
{
|
||||||
|
name: 'sparkmagic',
|
||||||
|
version: '0.12.9'
|
||||||
|
}, {
|
||||||
|
name: 'pandas',
|
||||||
|
version: '0.24.2'
|
||||||
|
}, {
|
||||||
|
name: 'prose-codeaccelerator',
|
||||||
|
version: '1.3.0'
|
||||||
|
}];
|
||||||
|
this._requiredKernelPackages.set(constants.pysparkDisplayName, sparkPackages);
|
||||||
|
this._requiredKernelPackages.set(constants.sparkScalaDisplayName, sparkPackages);
|
||||||
|
this._requiredKernelPackages.set(constants.sparkRDisplayName, sparkPackages);
|
||||||
|
|
||||||
|
let allPackages = sparkPackages.concat(powershellPkg);
|
||||||
|
this._requiredKernelPackages.set(constants.allKernelsName, allPackages);
|
||||||
|
|
||||||
|
this._requiredPackagesSet = new Set<string>();
|
||||||
|
allPackages.forEach(pkg => {
|
||||||
|
this._requiredPackagesSet.add(pkg.name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
||||||
if (!(await utils.exists(this._pythonExecutable)) || forceInstall || this._usingExistingPython) {
|
|
||||||
window.showInformationMessage(msgInstallPkgStart);
|
window.showInformationMessage(msgInstallPkgStart);
|
||||||
|
|
||||||
this.outputChannel.show(true);
|
this.outputChannel.show(true);
|
||||||
@@ -140,7 +177,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgInstallPkgProgress);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgInstallPkgProgress);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let pythonExists = await utils.exists(this._pythonExecutable);
|
||||||
|
if (!pythonExists || forceInstall) {
|
||||||
await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel);
|
await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel);
|
||||||
|
}
|
||||||
await this.upgradePythonPackages(false, forceInstall, specificPackages);
|
await this.upgradePythonPackages(false, forceInstall, specificPackages);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
|
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
|
||||||
@@ -151,7 +191,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
backgroundOperation.updateStatus(azdata.TaskStatus.Succeeded, msgInstallPkgFinish);
|
backgroundOperation.updateStatus(azdata.TaskStatus.Succeeded, msgInstallPkgFinish);
|
||||||
window.showInformationMessage(msgInstallPkgFinish);
|
window.showInformationMessage(msgInstallPkgFinish);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: OutputChannel): Promise<void> {
|
public installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: OutputChannel): Promise<void> {
|
||||||
if (usingExistingPython) {
|
if (usingExistingPython) {
|
||||||
@@ -388,14 +427,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
this._usingExistingPython = installSettings.existingPython;
|
this._usingExistingPython = installSettings.existingPython;
|
||||||
await this.configurePackagePaths();
|
await this.configurePackagePaths();
|
||||||
|
|
||||||
let updateConfig = async () => {
|
|
||||||
let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
|
|
||||||
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
|
||||||
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, ConfigurationTarget.Global);
|
|
||||||
await this.configurePackagePaths();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!(await utils.exists(this._pythonExecutable)) || forceInstall || this._usingExistingPython) {
|
|
||||||
this.apiWrapper.startBackgroundOperation({
|
this.apiWrapper.startBackgroundOperation({
|
||||||
displayName: msgTaskName,
|
displayName: msgTaskName,
|
||||||
description: msgTaskName,
|
description: msgTaskName,
|
||||||
@@ -403,7 +434,11 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
operation: op => {
|
operation: op => {
|
||||||
this.installDependencies(op, forceInstall, installSettings.specificPackages)
|
this.installDependencies(op, forceInstall, installSettings.specificPackages)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await updateConfig();
|
let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||||
|
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
||||||
|
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, ConfigurationTarget.Global);
|
||||||
|
await this.configurePackagePaths();
|
||||||
|
|
||||||
this._installCompletion.resolve();
|
this._installCompletion.resolve();
|
||||||
this._installInProgress = false;
|
this._installInProgress = false;
|
||||||
})
|
})
|
||||||
@@ -415,14 +450,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Python executable already exists, but the path setting wasn't defined,
|
|
||||||
// so update it here
|
|
||||||
await updateConfig();
|
|
||||||
this._installCompletion.resolve();
|
|
||||||
this._installInProgress = false;
|
|
||||||
this.apiWrapper.showInfoMessage(msgSkipPythonInstall);
|
|
||||||
}
|
|
||||||
return this._installCompletion.promise;
|
return this._installCompletion.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,12 +457,20 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
* Opens a dialog for configuring the installation path for the Notebook Python dependencies.
|
* Opens a dialog for configuring the installation path for the Notebook Python dependencies.
|
||||||
*/
|
*/
|
||||||
public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
|
public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
|
||||||
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
if (this._installInProgress) {
|
||||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
this.apiWrapper.showInfoMessage(msgWaitingForInstall);
|
||||||
if (enablePreviewFeatures) {
|
return this._installCompletion.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isPythonInstalled = JupyterServerInstallation.isPythonInstalled(this.apiWrapper);
|
||||||
|
let areRequiredPackagesInstalled = await this.areRequiredPackagesInstalled(kernelDisplayName);
|
||||||
|
if (!isPythonInstalled || !areRequiredPackagesInstalled) {
|
||||||
|
if (this.previewFeaturesEnabled) {
|
||||||
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, this);
|
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, this);
|
||||||
await pythonWizard.start(kernelDisplayName, true);
|
await pythonWizard.start(kernelDisplayName, true);
|
||||||
return pythonWizard.setupComplete;
|
return pythonWizard.setupComplete.then(() => {
|
||||||
|
this._kernelSetupCache.set(kernelDisplayName, true);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
|
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
|
||||||
return pythonDialog.showDialog(true);
|
return pythonDialog.showDialog(true);
|
||||||
@@ -453,12 +488,11 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let requiredPackages: PythonPkgDetails[];
|
let requiredPackages: PythonPkgDetails[];
|
||||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
if (this.previewFeaturesEnabled) {
|
||||||
if (enablePreviewFeatures) {
|
|
||||||
if (this._kernelSetupCache.get(kernelName)) {
|
if (this._kernelSetupCache.get(kernelName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
|
requiredPackages = this.getRequiredPackagesForKernel(kernelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._installInProgress = true;
|
this._installInProgress = true;
|
||||||
@@ -477,6 +511,27 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
return this._installCompletion.promise;
|
return this._installCompletion.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async areRequiredPackagesInstalled(kernelDisplayName: string): Promise<boolean> {
|
||||||
|
if (this._kernelSetupCache.get(kernelDisplayName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let installedPackages = await this.getInstalledPipPackages();
|
||||||
|
let installedPackageMap = new Map<string, string>();
|
||||||
|
installedPackages.forEach(pkg => {
|
||||||
|
installedPackageMap.set(pkg.name, pkg.version);
|
||||||
|
});
|
||||||
|
let requiredPackages = this.getRequiredPackagesForKernel(kernelDisplayName);
|
||||||
|
for (let pkg of requiredPackages) {
|
||||||
|
let installedVersion = installedPackageMap.get(pkg.name);
|
||||||
|
if (!installedVersion || utils.comparePackageVersions(installedVersion, pkg.version) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._kernelSetupCache.set(kernelDisplayName, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
||||||
let expectedCondaPackages: PythonPkgDetails[];
|
let expectedCondaPackages: PythonPkgDetails[];
|
||||||
let expectedPipPackages: PythonPkgDetails[];
|
let expectedPipPackages: PythonPkgDetails[];
|
||||||
@@ -625,6 +680,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void> {
|
public uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void> {
|
||||||
|
for (let pkg of packages) {
|
||||||
|
if (this._requiredPackagesSet.has(pkg.name)) {
|
||||||
|
this._kernelSetupCache.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
||||||
let cmd = `"${this.pythonExecutable}" -m pip uninstall -y ${packagesStr}`;
|
let cmd = `"${this.pythonExecutable}" -m pip uninstall -y ${packagesStr}`;
|
||||||
return this.executeStreamedCommand(cmd);
|
return this.executeStreamedCommand(cmd);
|
||||||
@@ -669,6 +731,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
|
public uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
|
||||||
|
for (let pkg of packages) {
|
||||||
|
if (this._requiredPackagesSet.has(pkg.name)) {
|
||||||
|
this._kernelSetupCache.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let condaExe = this.getCondaExePath();
|
let condaExe = this.getCondaExePath();
|
||||||
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
let packagesStr = packages.map(pkg => `"${pkg.name}==${pkg.version}"`).join(' ');
|
||||||
let cmd = `"${condaExe}" uninstall -y ${packagesStr}`;
|
let cmd = `"${condaExe}" uninstall -y ${packagesStr}`;
|
||||||
@@ -751,7 +820,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
return useExistingPython;
|
return useExistingPython;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getPythonPathSetting(apiWrapper: ApiWrapper): string {
|
public static getPythonPathSetting(apiWrapper: ApiWrapper): string {
|
||||||
let path = undefined;
|
let path = undefined;
|
||||||
if (apiWrapper) {
|
if (apiWrapper) {
|
||||||
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
|
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||||
@@ -813,53 +882,12 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getRequiredPackagesForKernel(kernelName: string): PythonPkgDetails[] {
|
public getRequiredPackagesForKernel(kernelName: string): PythonPkgDetails[] {
|
||||||
let packages = [{
|
return this._requiredKernelPackages.get(kernelName) ?? [];
|
||||||
name: 'jupyter',
|
|
||||||
version: '1.0.0'
|
|
||||||
}];
|
|
||||||
switch (kernelName) {
|
|
||||||
case constants.python3DisplayName:
|
|
||||||
break;
|
|
||||||
case constants.pysparkDisplayName:
|
|
||||||
case constants.sparkScalaDisplayName:
|
|
||||||
case constants.sparkRDisplayName:
|
|
||||||
packages.push({
|
|
||||||
name: 'sparkmagic',
|
|
||||||
version: '0.12.9'
|
|
||||||
}, {
|
|
||||||
name: 'pandas',
|
|
||||||
version: '0.24.2'
|
|
||||||
}, {
|
|
||||||
name: 'prose-codeaccelerator',
|
|
||||||
version: '1.3.0'
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case constants.powershellDisplayName:
|
|
||||||
packages.push({
|
|
||||||
name: 'powershell-kernel',
|
|
||||||
version: '0.1.3'
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case constants.allKernelsName:
|
|
||||||
packages.push({
|
|
||||||
name: 'sparkmagic',
|
|
||||||
version: '0.12.9'
|
|
||||||
}, {
|
|
||||||
name: 'pandas',
|
|
||||||
version: '0.24.2'
|
|
||||||
}, {
|
|
||||||
name: 'prose-codeaccelerator',
|
|
||||||
version: '1.3.0'
|
|
||||||
}, {
|
|
||||||
name: 'powershell-kernel',
|
|
||||||
version: '0.1.3'
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
return packages;
|
|
||||||
|
public get previewFeaturesEnabled(): boolean {
|
||||||
|
return this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
|
|||||||
private async doStartServer(kernelSpec: nb.IKernelSpec): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
|
private async doStartServer(kernelSpec: nb.IKernelSpec): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
|
||||||
let installation = this.options.jupyterInstallation;
|
let installation = this.options.jupyterInstallation;
|
||||||
await installation.promptForPythonInstall(kernelSpec.display_name);
|
await installation.promptForPythonInstall(kernelSpec.display_name);
|
||||||
|
if (!installation.previewFeaturesEnabled) {
|
||||||
await installation.promptForPackageUpgrade(kernelSpec.display_name);
|
await installation.promptForPackageUpgrade(kernelSpec.display_name);
|
||||||
|
}
|
||||||
this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true);
|
this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true);
|
||||||
|
|
||||||
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
|
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
|
||||||
|
|||||||
@@ -237,7 +237,11 @@ export class JupyterSession implements nb.ISession {
|
|||||||
public async changeKernel(kernelInfo: nb.IKernelSpec): Promise<nb.IKernel> {
|
public async changeKernel(kernelInfo: nb.IKernelSpec): Promise<nb.IKernel> {
|
||||||
if (this._installation) {
|
if (this._installation) {
|
||||||
try {
|
try {
|
||||||
|
if (this._installation.previewFeaturesEnabled) {
|
||||||
|
await this._installation.promptForPythonInstall(kernelInfo.display_name);
|
||||||
|
} else {
|
||||||
await this._installation.promptForPackageUpgrade(kernelInfo.display_name);
|
await this._installation.promptForPackageUpgrade(kernelInfo.display_name);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Have to swallow the error here to prevent hangs when changing back to the old kernel.
|
// Have to swallow the error here to prevent hangs when changing back to the old kernel.
|
||||||
console.error(err.toString());
|
console.error(err.toString());
|
||||||
|
|||||||
@@ -337,6 +337,45 @@ class TestFormContainer extends TestComponentBase implements azdata.FormContaine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestDivContainer extends TestComponentBase implements azdata.DivContainer {
|
||||||
|
onDidClick: vscode.Event<any>;
|
||||||
|
items: azdata.Component[] = [];
|
||||||
|
clearItems(): void {
|
||||||
|
}
|
||||||
|
addItems(itemConfigs: azdata.Component[], itemLayout?: azdata.DivItemLayout): void {
|
||||||
|
}
|
||||||
|
addItem(component: azdata.Component, itemLayout?: azdata.DivItemLayout): void {
|
||||||
|
}
|
||||||
|
insertItem(component: azdata.Component, index: number, itemLayout?: azdata.DivItemLayout): void {
|
||||||
|
}
|
||||||
|
removeItem(component: azdata.Component): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
setLayout(layout: azdata.DivLayout): void {
|
||||||
|
}
|
||||||
|
setItemLayout(component: azdata.Component, layout: azdata.DivItemLayout): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestFlexContainer extends TestComponentBase implements azdata.FlexContainer {
|
||||||
|
items: azdata.Component[] = [];
|
||||||
|
clearItems(): void {
|
||||||
|
}
|
||||||
|
addItems(itemConfigs: azdata.Component[], itemLayout?: azdata.FlexItemLayout): void {
|
||||||
|
}
|
||||||
|
addItem(component: azdata.Component, itemLayout?: azdata.FlexItemLayout): void {
|
||||||
|
}
|
||||||
|
insertItem(component: azdata.Component, index: number, itemLayout?: azdata.FlexItemLayout): void {
|
||||||
|
}
|
||||||
|
removeItem(component: azdata.Component): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
setLayout(layout: azdata.FlexLayout): void {
|
||||||
|
}
|
||||||
|
setItemLayout(component: azdata.Component, layout: azdata.FlexItemLayout): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TestComponentBuilder<T extends azdata.Component> implements azdata.ComponentBuilder<T> {
|
class TestComponentBuilder<T extends azdata.Component> implements azdata.ComponentBuilder<T> {
|
||||||
constructor(private _component: T) {
|
constructor(private _component: T) {
|
||||||
}
|
}
|
||||||
@@ -383,6 +422,34 @@ export function createViewContext(): TestContext {
|
|||||||
withLayout: () => formBuilder
|
withLayout: () => formBuilder
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let div: azdata.DivContainer = new TestDivContainer();
|
||||||
|
let divBuilder: azdata.DivBuilder = Object.assign({}, {
|
||||||
|
component: () => div,
|
||||||
|
addFormItem: () => { },
|
||||||
|
insertFormItem: () => { },
|
||||||
|
removeFormItem: () => true,
|
||||||
|
addFormItems: () => { },
|
||||||
|
withFormItems: () => divBuilder,
|
||||||
|
withProperties: () => divBuilder,
|
||||||
|
withValidation: () => divBuilder,
|
||||||
|
withItems: () => divBuilder,
|
||||||
|
withLayout: () => divBuilder
|
||||||
|
});
|
||||||
|
|
||||||
|
let flex: azdata.FlexContainer = new TestFlexContainer();
|
||||||
|
let flexBuilder: azdata.FlexBuilder = Object.assign({}, {
|
||||||
|
component: () => flex,
|
||||||
|
addFormItem: () => { },
|
||||||
|
insertFormItem: () => { },
|
||||||
|
removeFormItem: () => true,
|
||||||
|
addFormItems: () => { },
|
||||||
|
withFormItems: () => flexBuilder,
|
||||||
|
withProperties: () => flexBuilder,
|
||||||
|
withValidation: () => flexBuilder,
|
||||||
|
withItems: () => flexBuilder,
|
||||||
|
withLayout: () => flexBuilder
|
||||||
|
});
|
||||||
|
|
||||||
let view: azdata.ModelView = {
|
let view: azdata.ModelView = {
|
||||||
onClosed: undefined!,
|
onClosed: undefined!,
|
||||||
connection: undefined!,
|
connection: undefined!,
|
||||||
@@ -398,7 +465,9 @@ export function createViewContext(): TestContext {
|
|||||||
dropDown: () => dropdownBuilder,
|
dropDown: () => dropdownBuilder,
|
||||||
declarativeTable: () => declarativeTableBuilder,
|
declarativeTable: () => declarativeTableBuilder,
|
||||||
formContainer: () => formBuilder,
|
formContainer: () => formBuilder,
|
||||||
loadingComponent: () => loadingBuilder
|
loadingComponent: () => loadingBuilder,
|
||||||
|
divContainer: () => divBuilder,
|
||||||
|
flexContainer: () => flexBuilder
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -412,4 +481,13 @@ export interface TestContext {
|
|||||||
view: azdata.ModelView;
|
view: azdata.ModelView;
|
||||||
onClick: vscode.EventEmitter<any>;
|
onClick: vscode.EventEmitter<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TestButton implements azdata.window.Button {
|
||||||
|
label: string;
|
||||||
|
enabled: boolean;
|
||||||
|
hidden: boolean;
|
||||||
|
constructor(private onClickEmitter: vscode.EventEmitter<void>) {
|
||||||
|
}
|
||||||
|
onClick: vscode.Event<void> = this.onClickEmitter.event;
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { ConfigurePathPage } from '../dialog/configurePython/configurePathPage';
|
|||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import { PickPackagesPage } from '../dialog/configurePython/pickPackagesPage';
|
import { PickPackagesPage } from '../dialog/configurePython/pickPackagesPage';
|
||||||
import { python3DisplayName, allKernelsName } from '../common/constants';
|
import { python3DisplayName, allKernelsName } from '../common/constants';
|
||||||
import { TestContext, createViewContext } from './common';
|
import { TestContext, createViewContext, TestButton } from './common';
|
||||||
|
import { EventEmitter } from 'vscode';
|
||||||
|
|
||||||
describe('Configure Python Wizard', function () {
|
describe('Configure Python Wizard', function () {
|
||||||
let apiWrapper: ApiWrapper = new ApiWrapper();
|
let apiWrapper: ApiWrapper = new ApiWrapper();
|
||||||
@@ -23,11 +24,20 @@ describe('Configure Python Wizard', function () {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation);
|
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation);
|
||||||
mockInstall.setup(i => i.getInstalledPipPackages(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([]));
|
mockInstall.setup(i => i.getInstalledPipPackages(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([]));
|
||||||
|
mockInstall.setup(i => i.getRequiredPackagesForKernel(TypeMoq.It.isAnyString())).returns(() => [{ name: 'TestPkg', version: '1.0.0'}]);
|
||||||
testInstallation = mockInstall.object;
|
testInstallation = mockInstall.object;
|
||||||
|
|
||||||
let mockWizard = TypeMoq.Mock.ofType(ConfigurePythonWizard);
|
let mockDoneButton = new TestButton(new EventEmitter<void>());
|
||||||
mockWizard.setup(w => w.showErrorMessage(TypeMoq.It.isAnyString()));
|
let mockNextButton = new TestButton(new EventEmitter<void>());
|
||||||
testWizard = mockWizard.object;
|
|
||||||
|
let mockWizard = TypeMoq.Mock.ofType<azdata.window.Wizard>();
|
||||||
|
mockWizard.setup(w => w.doneButton).returns(() => mockDoneButton);
|
||||||
|
mockWizard.setup(w => w.nextButton).returns(() => mockNextButton);
|
||||||
|
|
||||||
|
let mockPythonWizard = TypeMoq.Mock.ofType(ConfigurePythonWizard);
|
||||||
|
mockPythonWizard.setup(w => w.showErrorMessage(TypeMoq.It.isAnyString()));
|
||||||
|
mockPythonWizard.setup(w => w.wizard).returns(() => mockWizard.object);
|
||||||
|
testWizard = mockPythonWizard.object;
|
||||||
|
|
||||||
viewContext = createViewContext();
|
viewContext = createViewContext();
|
||||||
});
|
});
|
||||||
@@ -82,6 +92,7 @@ describe('Configure Python Wizard', function () {
|
|||||||
|
|
||||||
// First page, so onPageEnter should do nothing
|
// First page, so onPageEnter should do nothing
|
||||||
await should(configurePathPage.onPageEnter()).be.resolved();
|
await should(configurePathPage.onPageEnter()).be.resolved();
|
||||||
|
should(testWizard.wizard.nextButton.enabled).be.true();
|
||||||
|
|
||||||
should(await configurePathPage.onPageLeave()).be.true();
|
should(await configurePathPage.onPageLeave()).be.true();
|
||||||
should(model.useExistingPython).be.true();
|
should(model.useExistingPython).be.true();
|
||||||
@@ -108,7 +119,8 @@ describe('Configure Python Wizard', function () {
|
|||||||
should(await pickPackagesPage.onPageLeave()).be.true();
|
should(await pickPackagesPage.onPageLeave()).be.true();
|
||||||
|
|
||||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(allKernelsName));
|
should(testWizard.wizard.doneButton.enabled).be.true();
|
||||||
|
should(model.packagesToInstall).be.deepEqual(testInstallation.getRequiredPackagesForKernel(allKernelsName));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Undefined kernel test', async () => {
|
it('Undefined kernel test', async () => {
|
||||||
@@ -128,6 +140,6 @@ describe('Configure Python Wizard', function () {
|
|||||||
should((<any>pickPackagesPage).kernelDropdown).not.be.undefined();
|
should((<any>pickPackagesPage).kernelDropdown).not.be.undefined();
|
||||||
|
|
||||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(python3DisplayName));
|
should(model.packagesToInstall).be.deepEqual(testInstallation.getRequiredPackagesForKernel(python3DisplayName));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user