mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Allow user to select source package type in Manage Packages dialog. (#6092)
This commit is contained in:
@@ -36,6 +36,11 @@ export enum CommandContext {
|
|||||||
NotebookPythonInstalled = 'notebook:pythonInstalled'
|
NotebookPythonInstalled = 'notebook:pythonInstalled'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PythonPkgType {
|
||||||
|
Pip = 'Pip',
|
||||||
|
Anaconda = 'Anaconda'
|
||||||
|
}
|
||||||
|
|
||||||
export const pythonOfflinePipPackagesUrl = 'https://go.microsoft.com/fwlink/?linkid=2092867';
|
export const pythonOfflinePipPackagesUrl = 'https://go.microsoft.com/fwlink/?linkid=2092867';
|
||||||
export const pythonWindowsInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092866';
|
export const pythonWindowsInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092866';
|
||||||
export const pythonMacInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092865';
|
export const pythonMacInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2092865';
|
||||||
|
|||||||
@@ -13,7 +13,4 @@ export const msgYes = localize('msgYes', 'Yes');
|
|||||||
export const msgNo = localize('msgNo', 'No');
|
export const msgNo = localize('msgNo', 'No');
|
||||||
|
|
||||||
// Jupyter Constants ///////////////////////////////////////////////////////
|
// Jupyter Constants ///////////////////////////////////////////////////////
|
||||||
export const msgManagePackagesPowershell = localize('msgManagePackagesPowershell', '<#\n--------------------------------------------------------------------------------\n\tThis is the sandboxed instance of python used by Jupyter server.\n\tTo install packages used by the python kernel use \'.\\python.exe -m pip install\'\n--------------------------------------------------------------------------------\n#>');
|
|
||||||
export const msgManagePackagesBash = localize('msgJupyterManagePackagesBash', ': \'\n--------------------------------------------------------------------------------\n\tThis is the sandboxed instance of python used by Jupyter server.\n\tTo install packages used by the python kernel use \'./python3 -m pip install\'\n--------------------------------------------------------------------------------\n\'');
|
|
||||||
export const msgManagePackagesCmd = localize('msgJupyterManagePackagesCmd', 'REM This is the sandboxed instance of python used by Jupyter server. To install packages used by the python kernel use \'.\\python.exe -m pip install\'');
|
|
||||||
export const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
export const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as azdata from 'azdata';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
|
|
||||||
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
|
||||||
import { ApiWrapper } from '../common/apiWrapper';
|
import { ApiWrapper } from '../common/apiWrapper';
|
||||||
import { Deferred } from '../common/promise';
|
import { Deferred } from '../common/promise';
|
||||||
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup';
|
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup';
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import * as nls from 'vscode-nls';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
|
|
||||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation, PipPackageOverview } from '../../jupyter/jupyterServerInstallation';
|
||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
import { ManagePackagesDialog } from './managePackagesDialog';
|
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||||
|
import { PythonPkgType } from '../../common/constants';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -27,15 +28,14 @@ export class AddNewPackageTab {
|
|||||||
private packageInstallButton: azdata.ButtonComponent;
|
private packageInstallButton: azdata.ButtonComponent;
|
||||||
|
|
||||||
private readonly InvalidTextPlaceholder = localize('managePackages.invalidTextPlaceholder', "N/A");
|
private readonly InvalidTextPlaceholder = localize('managePackages.invalidTextPlaceholder', "N/A");
|
||||||
|
private readonly PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package");
|
||||||
|
private readonly SearchPlaceholder = (pkgType: string) => localize('managePackages.searchBarPlaceholder', "Search {0} packages", pkgType);
|
||||||
|
|
||||||
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
||||||
this.addNewPkgTab = azdata.window.createTab(localize('managePackages.addNewTabTitle', "Add new"));
|
this.addNewPkgTab = azdata.window.createTab(localize('managePackages.addNewTabTitle', "Add new"));
|
||||||
|
|
||||||
this.addNewPkgTab.registerContent(async view => {
|
this.addNewPkgTab.registerContent(async view => {
|
||||||
this.newPackagesSearchBar = view.modelBuilder.inputBox()
|
this.newPackagesSearchBar = view.modelBuilder.inputBox().component();
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
|
||||||
placeHolder: localize('managePackages.searchBarPlaceholder', "Search for packages")
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.packagesSearchButton = view.modelBuilder.button()
|
this.packagesSearchButton = view.modelBuilder.button()
|
||||||
.withProperties<azdata.ButtonProperties>({
|
.withProperties<azdata.ButtonProperties>({
|
||||||
@@ -46,34 +46,23 @@ export class AddNewPackageTab {
|
|||||||
this.loadNewPackageInfo();
|
this.loadNewPackageInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.newPackagesName = view.modelBuilder.text().withProperties({
|
this.newPackagesName = view.modelBuilder.text().component();
|
||||||
value: this.InvalidTextPlaceholder
|
|
||||||
}).component();
|
|
||||||
this.newPackagesNameLoader = view.modelBuilder.loadingComponent()
|
this.newPackagesNameLoader = view.modelBuilder.loadingComponent()
|
||||||
.withItem(this.newPackagesName)
|
.withItem(this.newPackagesName)
|
||||||
.withProperties({ loading: false })
|
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this.newPackagesVersions = view.modelBuilder.dropDown().withProperties({
|
this.newPackagesVersions = view.modelBuilder.dropDown().component();
|
||||||
value: this.InvalidTextPlaceholder,
|
|
||||||
values: [this.InvalidTextPlaceholder]
|
|
||||||
}).component();
|
|
||||||
this.newPackagesVersionsLoader = view.modelBuilder.loadingComponent()
|
this.newPackagesVersionsLoader = view.modelBuilder.loadingComponent()
|
||||||
.withItem(this.newPackagesVersions)
|
.withItem(this.newPackagesVersions)
|
||||||
.withProperties({ loading: false })
|
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this.newPackagesSummary = view.modelBuilder.text().withProperties({
|
this.newPackagesSummary = view.modelBuilder.text().component();
|
||||||
value: this.InvalidTextPlaceholder
|
|
||||||
}).component();
|
|
||||||
this.newPackagesSummaryLoader = view.modelBuilder.loadingComponent()
|
this.newPackagesSummaryLoader = view.modelBuilder.loadingComponent()
|
||||||
.withItem(this.newPackagesSummary)
|
.withItem(this.newPackagesSummary)
|
||||||
.withProperties({ loading: false })
|
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this.packageInstallButton = view.modelBuilder.button().withProperties({
|
this.packageInstallButton = view.modelBuilder.button().withProperties({
|
||||||
label: localize('managePackages.installButtonText', "Install"),
|
label: localize('managePackages.installButtonText', "Install"),
|
||||||
enabled: false,
|
|
||||||
width: '80px'
|
width: '80px'
|
||||||
}).component();
|
}).component();
|
||||||
this.packageInstallButton.onDidClick(() => {
|
this.packageInstallButton.onDidClick(() => {
|
||||||
@@ -102,6 +91,8 @@ export class AddNewPackageTab {
|
|||||||
}]).component();
|
}]).component();
|
||||||
|
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
|
|
||||||
|
await this.resetPageFields();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +100,26 @@ export class AddNewPackageTab {
|
|||||||
return this.addNewPkgTab;
|
return this.addNewPkgTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async resetPageFields(): Promise<void> {
|
||||||
|
await this.toggleNewPackagesFields(false);
|
||||||
|
try {
|
||||||
|
await this.packageInstallButton.updateProperties({ enabled: false });
|
||||||
|
|
||||||
|
await this.newPackagesSearchBar.updateProperties({
|
||||||
|
value: '',
|
||||||
|
placeHolder: this.SearchPlaceholder(this.dialog.currentPkgType)
|
||||||
|
});
|
||||||
|
await this.newPackagesName.updateProperties({ value: this.InvalidTextPlaceholder });
|
||||||
|
await this.newPackagesVersions.updateProperties({
|
||||||
|
values: [this.InvalidTextPlaceholder],
|
||||||
|
value: this.InvalidTextPlaceholder
|
||||||
|
});
|
||||||
|
await this.newPackagesSummary.updateProperties({ value: this.InvalidTextPlaceholder });
|
||||||
|
} finally {
|
||||||
|
await this.toggleNewPackagesFields(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async toggleNewPackagesFields(enable: boolean): Promise<void> {
|
private async toggleNewPackagesFields(enable: boolean): Promise<void> {
|
||||||
await this.packagesSearchButton.updateProperties({ enabled: enable });
|
await this.packagesSearchButton.updateProperties({ enabled: enable });
|
||||||
await this.newPackagesNameLoader.updateProperties({ loading: !enable });
|
await this.newPackagesNameLoader.updateProperties({ loading: !enable });
|
||||||
@@ -125,7 +136,12 @@ export class AddNewPackageTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipPackage = await this.fetchPypiPackage(packageName);
|
let pipPackage: PipPackageOverview;
|
||||||
|
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
||||||
|
pipPackage = await this.fetchCondaPackage(packageName);
|
||||||
|
} else {
|
||||||
|
pipPackage = await this.fetchPypiPackage(packageName);
|
||||||
|
}
|
||||||
if (!pipPackage.versions || pipPackage.versions.length === 0) {
|
if (!pipPackage.versions || pipPackage.versions.length === 0) {
|
||||||
this.dialog.showErrorMessage(
|
this.dialog.showErrorMessage(
|
||||||
localize('managePackages.noVersionsFound',
|
localize('managePackages.noVersionsFound',
|
||||||
@@ -164,15 +180,15 @@ export class AddNewPackageTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchPypiPackage(packageName: string): Promise<PipPackageData> {
|
private async fetchPypiPackage(packageName: string): Promise<PipPackageOverview> {
|
||||||
return new Promise<PipPackageData>((resolve, reject) => {
|
return new Promise<PipPackageOverview>((resolve, reject) => {
|
||||||
request.get(`https://pypi.org/pypi/${packageName}/json`, { timeout: 10000 }, (error, response, body) => {
|
request.get(`https://pypi.org/pypi/${packageName}/json`, { timeout: 10000 }, (error, response, body) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode === 404) {
|
if (response.statusCode === 404) {
|
||||||
return reject(localize('managePackages.packageNotFound', "Could not find the specified package"));
|
return reject(this.PackageNotFoundError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
@@ -194,7 +210,59 @@ export class AddNewPackageTab {
|
|||||||
let releaseInfo = packagesJson.releases[versionKey];
|
let releaseInfo = packagesJson.releases[versionKey];
|
||||||
return Array.isArray(releaseInfo) && releaseInfo.length > 0;
|
return Array.isArray(releaseInfo) && releaseInfo.length > 0;
|
||||||
});
|
});
|
||||||
versionKeys.sort((first, second) => {
|
versionNums = AddNewPackageTab.sortPackageVersions(versionKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packagesJson.info && packagesJson.info.summary) {
|
||||||
|
packageSummary = packagesJson.info.summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
name: packageName,
|
||||||
|
versions: versionNums,
|
||||||
|
summary: packageSummary
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchCondaPackage(packageName: string): Promise<PipPackageOverview> {
|
||||||
|
let condaExe = this.jupyterInstallation.getCondaExePath();
|
||||||
|
let cmd = `"${condaExe}" search --json ${packageName}`;
|
||||||
|
let packageResult: string;
|
||||||
|
try {
|
||||||
|
packageResult = await this.jupyterInstallation.executeBufferedCommand(cmd);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(this.PackageNotFoundError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageResult) {
|
||||||
|
let packageJson = JSON.parse(packageResult);
|
||||||
|
if (packageJson) {
|
||||||
|
if (packageJson.error) {
|
||||||
|
throw new Error(packageJson.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let packages = packageJson[packageName];
|
||||||
|
if (Array.isArray(packages)) {
|
||||||
|
let allVersions = packages.filter(pkg => pkg && pkg.version).map(pkg => pkg.version);
|
||||||
|
let singletonVersions = new Set<string>(allVersions);
|
||||||
|
let sortedVersions = AddNewPackageTab.sortPackageVersions(Array.from(singletonVersions));
|
||||||
|
return {
|
||||||
|
name: packageName,
|
||||||
|
versions: sortedVersions,
|
||||||
|
summary: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static sortPackageVersions(versions: string[]): string[] {
|
||||||
|
return versions.sort((first, second) => {
|
||||||
// sort in descending order
|
// sort in descending order
|
||||||
let firstVersion = first.split('.').map(numStr => Number.parseInt(numStr));
|
let firstVersion = first.split('.').map(numStr => Number.parseInt(numStr));
|
||||||
let secondVersion = second.split('.').map(numStr => Number.parseInt(numStr));
|
let secondVersion = second.split('.').map(numStr => Number.parseInt(numStr));
|
||||||
@@ -217,21 +285,6 @@ export class AddNewPackageTab {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
versionNums = versionKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packagesJson.info && packagesJson.info.summary) {
|
|
||||||
packageSummary = packagesJson.info.summary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
name: packageName,
|
|
||||||
versions: versionNums,
|
|
||||||
summary: packageSummary
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doPackageInstall(): Promise<void> {
|
private async doPackageInstall(): Promise<void> {
|
||||||
@@ -251,7 +304,13 @@ export class AddNewPackageTab {
|
|||||||
description: taskName,
|
description: taskName,
|
||||||
isCancelable: false,
|
isCancelable: false,
|
||||||
operation: op => {
|
operation: op => {
|
||||||
this.jupyterInstallation.installPipPackage(packageName, packageVersion)
|
let installPromise: Promise<void>;
|
||||||
|
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
||||||
|
installPromise = this.jupyterInstallation.installCondaPackage(packageName, packageVersion);
|
||||||
|
} else {
|
||||||
|
installPromise = this.jupyterInstallation.installPipPackage(packageName, packageVersion);
|
||||||
|
}
|
||||||
|
installPromise
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
let installMsg = localize('managePackages.backgroundInstallComplete',
|
let installMsg = localize('managePackages.backgroundInstallComplete',
|
||||||
"Completed install for {0} {1}",
|
"Completed install for {0} {1}",
|
||||||
@@ -277,9 +336,3 @@ export class AddNewPackageTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PipPackageData {
|
|
||||||
name: string;
|
|
||||||
versions: string[];
|
|
||||||
summary: string;
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,12 @@
|
|||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
import JupyterServerInstallation, { PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
import { ManagePackagesDialog } from './managePackagesDialog';
|
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||||
import CodeAdapter from '../../prompts/adapter';
|
import CodeAdapter from '../../prompts/adapter';
|
||||||
import { QuestionTypes, IQuestion } from '../../prompts/question';
|
import { QuestionTypes, IQuestion } from '../../prompts/question';
|
||||||
|
import { PythonPkgType } from '../../common/constants';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
private installedPkgTab: azdata.window.DialogTab;
|
private installedPkgTab: azdata.window.DialogTab;
|
||||||
|
|
||||||
|
private packageTypeDropdown: azdata.DropDownComponent;
|
||||||
private installedPackageCount: azdata.TextComponent;
|
private installedPackageCount: azdata.TextComponent;
|
||||||
private installedPackagesTable: azdata.TableComponent;
|
private installedPackagesTable: azdata.TableComponent;
|
||||||
private installedPackagesLoader: azdata.LoadingComponent;
|
private installedPackagesLoader: azdata.LoadingComponent;
|
||||||
@@ -30,6 +32,23 @@ export class InstalledPackagesTab {
|
|||||||
this.installedPkgTab = azdata.window.createTab(localize('managePackages.installedTabTitle', "Installed"));
|
this.installedPkgTab = azdata.window.createTab(localize('managePackages.installedTabTitle', "Installed"));
|
||||||
|
|
||||||
this.installedPkgTab.registerContent(async view => {
|
this.installedPkgTab.registerContent(async view => {
|
||||||
|
let dropdownValues: string[];
|
||||||
|
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
||||||
|
dropdownValues = [PythonPkgType.Anaconda, PythonPkgType.Pip];
|
||||||
|
} else {
|
||||||
|
dropdownValues = [PythonPkgType.Pip];
|
||||||
|
}
|
||||||
|
this.packageTypeDropdown = view.modelBuilder.dropDown().withProperties({
|
||||||
|
values: dropdownValues,
|
||||||
|
value: dropdownValues[0]
|
||||||
|
}).component();
|
||||||
|
this.packageTypeDropdown.onValueChanged(() => {
|
||||||
|
this.dialog.resetPages(this.packageTypeDropdown.value as PythonPkgType)
|
||||||
|
.catch(err => {
|
||||||
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.installedPackageCount = view.modelBuilder.text().withProperties({
|
this.installedPackageCount = view.modelBuilder.text().withProperties({
|
||||||
value: ''
|
value: ''
|
||||||
}).component();
|
}).component();
|
||||||
@@ -54,6 +73,9 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
|
component: this.packageTypeDropdown,
|
||||||
|
title: localize('managePackages.packageType', "Package Type")
|
||||||
|
}, {
|
||||||
component: this.installedPackageCount,
|
component: this.installedPackageCount,
|
||||||
title: ''
|
title: ''
|
||||||
}, {
|
}, {
|
||||||
@@ -82,13 +104,15 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
public async loadInstalledPackagesInfo(): Promise<void> {
|
public async loadInstalledPackagesInfo(): Promise<void> {
|
||||||
let pythonPackages: PythonPkgDetails[];
|
let pythonPackages: PythonPkgDetails[];
|
||||||
let packagesLocation: string;
|
|
||||||
|
|
||||||
await this.installedPackagesLoader.updateProperties({ loading: true });
|
await this.installedPackagesLoader.updateProperties({ loading: true });
|
||||||
await this.uninstallPackageButton.updateProperties({ enabled: false });
|
await this.uninstallPackageButton.updateProperties({ enabled: false });
|
||||||
try {
|
try {
|
||||||
|
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
||||||
|
pythonPackages = await this.jupyterInstallation.getInstalledCondaPackages();
|
||||||
|
} else {
|
||||||
pythonPackages = await this.jupyterInstallation.getInstalledPipPackages();
|
pythonPackages = await this.jupyterInstallation.getInstalledPipPackages();
|
||||||
packagesLocation = await this.jupyterInstallation.getPythonPackagesPath();
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -104,17 +128,10 @@ export class InstalledPackagesTab {
|
|||||||
packageCount = 0;
|
packageCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let countMsg: string;
|
|
||||||
if (packagesLocation && packagesLocation.length > 0) {
|
|
||||||
countMsg = localize('managePackages.packageCount', "{0} packages found in '{1}'",
|
|
||||||
packageCount,
|
|
||||||
packagesLocation);
|
|
||||||
} else {
|
|
||||||
countMsg = localize('managePackages.packageCountNoPath', "{0} packages found",
|
|
||||||
packageCount);
|
|
||||||
}
|
|
||||||
await this.installedPackageCount.updateProperties({
|
await this.installedPackageCount.updateProperties({
|
||||||
value: countMsg
|
value: localize('managePackages.packageCount', "{0} {1} packages found",
|
||||||
|
packageCount,
|
||||||
|
this.dialog.currentPkgType)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (packageData && packageData.length > 0) {
|
if (packageData && packageData.length > 0) {
|
||||||
@@ -161,7 +178,13 @@ export class InstalledPackagesTab {
|
|||||||
description: taskName,
|
description: taskName,
|
||||||
isCancelable: false,
|
isCancelable: false,
|
||||||
operation: op => {
|
operation: op => {
|
||||||
this.jupyterInstallation.uninstallPipPackages(packages)
|
let uninstallPromise: Promise<void>;
|
||||||
|
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
||||||
|
uninstallPromise = this.jupyterInstallation.uninstallCondaPackages(packages);
|
||||||
|
} else {
|
||||||
|
uninstallPromise = this.jupyterInstallation.uninstallPipPackages(packages);
|
||||||
|
}
|
||||||
|
uninstallPromise
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
let uninstallMsg = localize('managePackages.backgroundUninstallComplete',
|
let uninstallMsg = localize('managePackages.backgroundUninstallComplete',
|
||||||
"Completed uninstall for {0}",
|
"Completed uninstall for {0}",
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
import { InstalledPackagesTab } from './installedPackagesTab';
|
import { InstalledPackagesTab } from './installedPackagesTab';
|
||||||
import { AddNewPackageTab } from './addNewPackageTab';
|
import { AddNewPackageTab } from './addNewPackageTab';
|
||||||
|
import { PythonPkgType } from '../../common/constants';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -17,7 +18,10 @@ export class ManagePackagesDialog {
|
|||||||
private installedPkgTab: InstalledPackagesTab;
|
private installedPkgTab: InstalledPackagesTab;
|
||||||
private addNewPkgTab: AddNewPackageTab;
|
private addNewPkgTab: AddNewPackageTab;
|
||||||
|
|
||||||
|
public currentPkgType: PythonPkgType;
|
||||||
|
|
||||||
constructor(private jupyterInstallation: JupyterServerInstallation) {
|
constructor(private jupyterInstallation: JupyterServerInstallation) {
|
||||||
|
this.currentPkgType = this.jupyterInstallation.usingConda ? PythonPkgType.Anaconda : PythonPkgType.Pip;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +45,12 @@ export class ManagePackagesDialog {
|
|||||||
return this.installedPkgTab.loadInstalledPackagesInfo();
|
return this.installedPkgTab.loadInstalledPackagesInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async resetPages(newPkgType: PythonPkgType): Promise<void> {
|
||||||
|
this.currentPkgType = newPkgType;
|
||||||
|
await this.installedPkgTab.loadInstalledPackagesInfo();
|
||||||
|
await this.addNewPkgTab.resetPageFields();
|
||||||
|
}
|
||||||
|
|
||||||
public showInfoMessage(message: string): void {
|
public showInfoMessage(message: string): void {
|
||||||
this.dialog.message = {
|
this.dialog.message = {
|
||||||
text: message,
|
text: message,
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import * as path from 'path';
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { JupyterController } from '../jupyter/jupyterController';
|
import { JupyterController } from '../jupyter/jupyterController';
|
||||||
import JupyterServerInstallation, { PythonPkgDetails } from '../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation, PythonPkgDetails } from '../jupyter/jupyterServerInstallation';
|
||||||
import { pythonBundleVersion } from '../common/constants';
|
import { pythonBundleVersion } from '../common/constants';
|
||||||
import { executeStreamedCommand } from '../common/utils';
|
import { executeStreamedCommand } from '../common/utils';
|
||||||
|
import { AddNewPackageTab } from '../dialog/managePackages/addNewPackageTab';
|
||||||
|
|
||||||
describe('Notebook Extension Python Installation', function () {
|
describe('Notebook Extension Python Installation', function () {
|
||||||
this.timeout(600000);
|
this.timeout(600000);
|
||||||
@@ -20,6 +21,7 @@ describe('Notebook Extension Python Installation', function () {
|
|||||||
let installComplete = false;
|
let installComplete = false;
|
||||||
let pythonInstallDir = process.env.PYTHON_TEST_PATH;
|
let pythonInstallDir = process.env.PYTHON_TEST_PATH;
|
||||||
let jupyterController: JupyterController;
|
let jupyterController: JupyterController;
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
assert.ok(pythonInstallDir, 'Python install directory was not defined.');
|
assert.ok(pythonInstallDir, 'Python install directory was not defined.');
|
||||||
|
|
||||||
@@ -81,13 +83,9 @@ describe('Notebook Extension Python Installation', function () {
|
|||||||
console.log('Existing Python Installation is done');
|
console.log('Existing Python Installation is done');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Python Install Utilities Test', async function () {
|
it('Pip Install Utilities Test', async function () {
|
||||||
let install = jupyterController.jupyterInstallation;
|
let install = jupyterController.jupyterInstallation;
|
||||||
|
|
||||||
let packagePath = await install.getPythonPackagesPath();
|
|
||||||
should(packagePath).not.be.undefined();
|
|
||||||
should(packagePath.length).be.greaterThan(0);
|
|
||||||
|
|
||||||
let testPkg = 'pandas';
|
let testPkg = 'pandas';
|
||||||
let testPkgVersion = '0.24.2';
|
let testPkgVersion = '0.24.2';
|
||||||
let expectedPkg: PythonPkgDetails = { name: testPkg, version: testPkgVersion };
|
let expectedPkg: PythonPkgDetails = { name: testPkg, version: testPkgVersion };
|
||||||
@@ -104,4 +102,26 @@ describe('Notebook Extension Python Installation', function () {
|
|||||||
packages = await install.getInstalledPipPackages();
|
packages = await install.getInstalledPipPackages();
|
||||||
should(packages).containEql(expectedPkg);
|
should(packages).containEql(expectedPkg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Conda Install Utilities Test', async function () {
|
||||||
|
let install = jupyterController.jupyterInstallation;
|
||||||
|
|
||||||
|
// Anaconda is not included in our Python package, so all
|
||||||
|
// the conda utilities should fail.
|
||||||
|
should(install.usingConda).be.false();
|
||||||
|
|
||||||
|
should(install.getInstalledCondaPackages()).be.rejected();
|
||||||
|
|
||||||
|
should(install.installCondaPackage('pandas', '0.24.2')).be.rejected();
|
||||||
|
|
||||||
|
should(install.uninstallCondaPackages([{ name: 'pandas', version: '0.24.2' }])).be.rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Manage Packages Dialog: New Package Test', async function () {
|
||||||
|
let testVersions = ['1.0.0', '1.1', '0.0.0.9', '0.0.5', '100', '0.3', '3'];
|
||||||
|
let expectedVerions = ['100', '3', '1.1', '1.0.0', '0.3', '0.0.5', '0.0.0.9'];
|
||||||
|
|
||||||
|
let actualVersions = AddNewPackageTab.sortPackageVersions(testVersions);
|
||||||
|
should(actualVersions).be.deepEqual(expectedVerions);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const localize = nls.loadMessageBundle();
|
|||||||
|
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as localizedConstants from '../common/localizedConstants';
|
import * as localizedConstants from '../common/localizedConstants';
|
||||||
import JupyterServerInstallation from './jupyterServerInstallation';
|
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
import { IServerInstance } from './common';
|
import { IServerInstance } from './common';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question';
|
import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question';
|
||||||
@@ -210,20 +210,6 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTextToSendToTerminal(shellType: any): string {
|
|
||||||
if (utils.getOSPlatform() === utils.Platform.Windows && typeof shellType === 'string') {
|
|
||||||
if (shellType.endsWith('powershell.exe')) {
|
|
||||||
return localizedConstants.msgManagePackagesPowershell;
|
|
||||||
} else if (shellType.endsWith('cmd.exe')) {
|
|
||||||
return localizedConstants.msgManagePackagesCmd;
|
|
||||||
} else {
|
|
||||||
return localizedConstants.msgManagePackagesBash;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return localizedConstants.msgManagePackagesBash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get jupyterInstallation() {
|
public get jupyterInstallation() {
|
||||||
return this._jupyterInstallation;
|
return this._jupyterInstallation;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const msgSkipPythonInstall = localize('msgSkipPythonInstall', "Python already ex
|
|||||||
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); }
|
||||||
|
|
||||||
export default class JupyterServerInstallation {
|
export class JupyterServerInstallation {
|
||||||
public apiWrapper: ApiWrapper;
|
public apiWrapper: ApiWrapper;
|
||||||
public extensionPath: string;
|
public extensionPath: string;
|
||||||
public pythonBinPath: string;
|
public pythonBinPath: string;
|
||||||
@@ -86,11 +86,11 @@ export default class JupyterServerInstallation {
|
|||||||
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadComplete);
|
backgroundOperation.updateStatus(azdata.TaskStatus.InProgress, msgPythonDownloadComplete);
|
||||||
|
|
||||||
if (this._usingConda) {
|
if (this._usingConda) {
|
||||||
await this.installCondaPackages();
|
await this.installCondaDependencies();
|
||||||
} else if (this._usingExistingPython) {
|
} else if (this._usingExistingPython) {
|
||||||
await this.installPipPackages();
|
await this.installPipDependencies();
|
||||||
} else {
|
} else {
|
||||||
await this.installOfflinePipPackages();
|
await this.installOfflinePipDependencies();
|
||||||
}
|
}
|
||||||
let doOnlineInstall = this._usingExistingPython;
|
let doOnlineInstall = this._usingExistingPython;
|
||||||
await this.installSparkMagic(doOnlineInstall);
|
await this.installSparkMagic(doOnlineInstall);
|
||||||
@@ -369,7 +369,36 @@ export default class JupyterServerInstallation {
|
|||||||
return this.executeStreamedCommand(cmd);
|
return this.executeStreamedCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installOfflinePipPackages(): Promise<void> {
|
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
|
||||||
|
let condaExe = this.getCondaExePath();
|
||||||
|
let cmd = `"${condaExe}" list --json`;
|
||||||
|
let packagesInfo = await this.executeBufferedCommand(cmd);
|
||||||
|
|
||||||
|
if (packagesInfo) {
|
||||||
|
let packagesResult = JSON.parse(packagesInfo);
|
||||||
|
if (Array.isArray(packagesResult)) {
|
||||||
|
return packagesResult
|
||||||
|
.filter(pkg => pkg && pkg.channel && pkg.channel !== 'pypi')
|
||||||
|
.map(pkg => <PythonPkgDetails>{ name: pkg.name, version: pkg.version });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public installCondaPackage(packageName: string, version: string): Promise<void> {
|
||||||
|
let condaExe = this.getCondaExePath();
|
||||||
|
let cmd = `"${condaExe}" install -y ${packageName}==${version}`;
|
||||||
|
return this.executeStreamedCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
|
||||||
|
let condaExe = this.getCondaExePath();
|
||||||
|
let packagesStr = packages.map(pkg => `${pkg.name}==${pkg.version}`).join(' ');
|
||||||
|
let cmd = `"${condaExe}" uninstall -y ${packagesStr}`;
|
||||||
|
return this.executeStreamedCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async installOfflinePipDependencies(): Promise<void> {
|
||||||
let installJupyterCommand: string;
|
let installJupyterCommand: string;
|
||||||
if (process.platform === constants.winPlatform) {
|
if (process.platform === constants.winPlatform) {
|
||||||
let requirements = path.join(this._pythonPackageDir, 'requirements.txt');
|
let requirements = path.join(this._pythonPackageDir, 'requirements.txt');
|
||||||
@@ -404,7 +433,7 @@ export default class JupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installPipPackages(): Promise<void> {
|
private async installPipDependencies(): Promise<void> {
|
||||||
this.outputChannel.show(true);
|
this.outputChannel.show(true);
|
||||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||||
|
|
||||||
@@ -417,7 +446,7 @@ export default class JupyterServerInstallation {
|
|||||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installCondaPackages(): Promise<void> {
|
private async installCondaDependencies(): Promise<void> {
|
||||||
this.outputChannel.show(true);
|
this.outputChannel.show(true);
|
||||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||||
|
|
||||||
@@ -437,7 +466,7 @@ export default class JupyterServerInstallation {
|
|||||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeBufferedCommand(command: string): Promise<string> {
|
public async executeBufferedCommand(command: string): Promise<string> {
|
||||||
return await utils.executeBufferedCommand(command, { env: this.execOptions.env });
|
return await utils.executeBufferedCommand(command, { env: this.execOptions.env });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,11 +474,15 @@ export default class JupyterServerInstallation {
|
|||||||
return this._pythonExecutable;
|
return this._pythonExecutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCondaExePath(): string {
|
public getCondaExePath(): string {
|
||||||
return path.join(this._pythonInstallationPath,
|
return path.join(this._pythonInstallationPath,
|
||||||
process.platform === constants.winPlatform ? 'Scripts\\conda.exe' : 'bin/conda');
|
process.platform === constants.winPlatform ? 'Scripts\\conda.exe' : 'bin/conda');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get usingConda(): boolean {
|
||||||
|
return this._usingConda;
|
||||||
|
}
|
||||||
|
|
||||||
private checkCondaExists(): boolean {
|
private checkCondaExists(): boolean {
|
||||||
if (!this._usingExistingPython) {
|
if (!this._usingExistingPython) {
|
||||||
return false;
|
return false;
|
||||||
@@ -526,11 +559,6 @@ export default class JupyterServerInstallation {
|
|||||||
pythonBinPathSuffix);
|
pythonBinPathSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPythonPackagesPath(): Promise<string> {
|
|
||||||
let cmd = `"${this.pythonExecutable}" -c "import site; print(site.getsitepackages()[0])"`;
|
|
||||||
return await this.executeBufferedCommand(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
||||||
return path.join(
|
return path.join(
|
||||||
pythonInstallPath,
|
pythonInstallPath,
|
||||||
@@ -543,3 +571,9 @@ export interface PythonPkgDetails {
|
|||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PipPackageOverview {
|
||||||
|
name: string;
|
||||||
|
versions: string[];
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import * as nls from 'vscode-nls';
|
|||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { ApiWrapper } from '../common/apiWrapper';
|
import { ApiWrapper } from '../common/apiWrapper';
|
||||||
import JupyterServerInstallation from './jupyterServerInstallation';
|
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import { IServerInstance } from './common';
|
import { IServerInstance } from './common';
|
||||||
import { PerNotebookServerInstance, IInstanceOptions } from './serverInstance';
|
import { PerNotebookServerInstance, IInstanceOptions } from './serverInstance';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import * as nls from 'vscode-nls';
|
|||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { IServerInstance } from './common';
|
import { IServerInstance } from './common';
|
||||||
import JupyterServerInstallation from './jupyterServerInstallation';
|
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as notebookUtils from '../common/notebookUtils';
|
import * as notebookUtils from '../common/notebookUtils';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as stream from 'stream';
|
|||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
import { ApiWrapper } from '../..//common/apiWrapper';
|
import { ApiWrapper } from '../..//common/apiWrapper';
|
||||||
import { PerNotebookServerInstance, ServerInstanceUtils } from '../../jupyter/serverInstance';
|
import { PerNotebookServerInstance, ServerInstanceUtils } from '../../jupyter/serverInstance';
|
||||||
import { MockOutputChannel } from '../common/stubs';
|
import { MockOutputChannel } from '../common/stubs';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'mocha';
|
|||||||
|
|
||||||
import { JupyterServerInstanceStub } from '../common';
|
import { JupyterServerInstanceStub } from '../common';
|
||||||
import { LocalJupyterServerManager, ServerInstanceFactory } from '../../jupyter/jupyterServerManager';
|
import { LocalJupyterServerManager, ServerInstanceFactory } from '../../jupyter/jupyterServerManager';
|
||||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
import { Deferred } from '../../common/promise';
|
import { Deferred } from '../../common/promise';
|
||||||
import { ApiWrapper } from '../../common/apiWrapper';
|
import { ApiWrapper } from '../../common/apiWrapper';
|
||||||
import * as testUtils from '../common/testUtils';
|
import * as testUtils from '../common/testUtils';
|
||||||
|
|||||||
Reference in New Issue
Block a user