mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Manage Package Dialog Refactor (#8473)
* Refactoring Manage Packages dialog so that other extensions can contribute to it by registering package mange providers for different location and package type
This commit is contained in:
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
// CONFIG VALUES ///////////////////////////////////////////////////////////
|
// CONFIG VALUES ///////////////////////////////////////////////////////////
|
||||||
export const extensionOutputChannel = 'Notebooks';
|
export const extensionOutputChannel = 'Notebooks';
|
||||||
|
|
||||||
@@ -27,6 +31,9 @@ export const jupyterReinstallDependenciesCommand = 'jupyter.reinstallDependencie
|
|||||||
export const jupyterAnalyzeCommand = 'jupyter.cmd.analyzeNotebook';
|
export const jupyterAnalyzeCommand = 'jupyter.cmd.analyzeNotebook';
|
||||||
export const jupyterManagePackages = 'jupyter.cmd.managePackages';
|
export const jupyterManagePackages = 'jupyter.cmd.managePackages';
|
||||||
export const jupyterConfigurePython = 'jupyter.cmd.configurePython';
|
export const jupyterConfigurePython = 'jupyter.cmd.configurePython';
|
||||||
|
export const localhostName = 'localhost';
|
||||||
|
export const localhostTitle = localize('managePackages.localhost', "localhost");
|
||||||
|
export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package");
|
||||||
|
|
||||||
export enum BuiltInCommands {
|
export enum BuiltInCommands {
|
||||||
SetContext = 'setContext'
|
SetContext = 'setContext'
|
||||||
|
|||||||
@@ -5,12 +5,10 @@
|
|||||||
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as request from 'request';
|
|
||||||
|
|
||||||
import { JupyterServerInstallation, PipPackageOverview } 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();
|
||||||
|
|
||||||
@@ -28,7 +26,6 @@ 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);
|
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) {
|
||||||
@@ -42,8 +39,8 @@ export class AddNewPackageTab {
|
|||||||
label: localize('managePackages.searchButtonLabel', "Search"),
|
label: localize('managePackages.searchButtonLabel', "Search"),
|
||||||
width: '200px'
|
width: '200px'
|
||||||
}).component();
|
}).component();
|
||||||
this.packagesSearchButton.onDidClick(() => {
|
this.packagesSearchButton.onDidClick(async () => {
|
||||||
this.loadNewPackageInfo();
|
await this.loadNewPackageInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.newPackagesName = view.modelBuilder.text().withProperties({ width: '400px' }).component();
|
this.newPackagesName = view.modelBuilder.text().withProperties({ width: '400px' }).component();
|
||||||
@@ -65,8 +62,8 @@ export class AddNewPackageTab {
|
|||||||
label: localize('managePackages.installButtonText', "Install"),
|
label: localize('managePackages.installButtonText', "Install"),
|
||||||
width: '200px'
|
width: '200px'
|
||||||
}).component();
|
}).component();
|
||||||
this.packageInstallButton.onDidClick(() => {
|
this.packageInstallButton.onDidClick(async () => {
|
||||||
this.doPackageInstall();
|
await this.doPackageInstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
@@ -107,7 +104,7 @@ export class AddNewPackageTab {
|
|||||||
|
|
||||||
await this.newPackagesSearchBar.updateProperties({
|
await this.newPackagesSearchBar.updateProperties({
|
||||||
value: '',
|
value: '',
|
||||||
placeHolder: this.SearchPlaceholder(this.dialog.currentPkgType)
|
placeHolder: this.SearchPlaceholder(this.dialog.model.currentPackageType)
|
||||||
});
|
});
|
||||||
await this.setFieldsToEmpty();
|
await this.setFieldsToEmpty();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -145,11 +142,7 @@ export class AddNewPackageTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pipPackage: PipPackageOverview;
|
let pipPackage: PipPackageOverview;
|
||||||
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
pipPackage = await this.dialog.model.getPackageOverview(packageName);
|
||||||
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',
|
||||||
@@ -179,86 +172,7 @@ export class AddNewPackageTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchPypiPackage(packageName: string): Promise<PipPackageOverview> {
|
|
||||||
return new Promise<PipPackageOverview>((resolve, reject) => {
|
|
||||||
request.get(`https://pypi.org/pypi/${packageName}/json`, { timeout: 10000 }, (error, response, body) => {
|
|
||||||
if (error) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode === 404) {
|
|
||||||
return reject(this.PackageNotFoundError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode !== 200) {
|
|
||||||
return reject(
|
|
||||||
localize('managePackages.packageRequestError',
|
|
||||||
"Package info request failed with error: {0} {1}",
|
|
||||||
response.statusCode,
|
|
||||||
response.statusMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
let versionNums: string[] = [];
|
|
||||||
let packageSummary = '';
|
|
||||||
|
|
||||||
let packagesJson = JSON.parse(body);
|
|
||||||
if (packagesJson) {
|
|
||||||
if (packagesJson.releases) {
|
|
||||||
let versionKeys = Object.keys(packagesJson.releases);
|
|
||||||
versionKeys = versionKeys.filter(versionKey => {
|
|
||||||
let releaseInfo = packagesJson.releases[versionKey];
|
|
||||||
return Array.isArray(releaseInfo) && releaseInfo.length > 0;
|
|
||||||
});
|
|
||||||
versionNums = utils.sortPackageVersions(versionKeys, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = utils.sortPackageVersions(Array.from(singletonVersions), false);
|
|
||||||
return {
|
|
||||||
name: packageName,
|
|
||||||
versions: sortedVersions,
|
|
||||||
summary: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doPackageInstall(): Promise<void> {
|
private async doPackageInstall(): Promise<void> {
|
||||||
let packageName = this.newPackagesName.value;
|
let packageName = this.newPackagesName.value;
|
||||||
@@ -278,11 +192,7 @@ export class AddNewPackageTab {
|
|||||||
isCancelable: false,
|
isCancelable: false,
|
||||||
operation: op => {
|
operation: op => {
|
||||||
let installPromise: Promise<void>;
|
let installPromise: Promise<void>;
|
||||||
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
installPromise = this.dialog.model.installPackages([{ name: packageName, version: packageVersion }]);
|
||||||
installPromise = this.jupyterInstallation.installCondaPackages([{ name: packageName, version: packageVersion }], false);
|
|
||||||
} else {
|
|
||||||
installPromise = this.jupyterInstallation.installPipPackages([{ name: packageName, version: packageVersion }], false);
|
|
||||||
}
|
|
||||||
installPromise
|
installPromise
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
let installMsg = localize('managePackages.backgroundInstallComplete',
|
let installMsg = localize('managePackages.backgroundInstallComplete',
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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();
|
||||||
|
|
||||||
@@ -21,6 +20,7 @@ export class InstalledPackagesTab {
|
|||||||
private installedPkgTab: azdata.window.DialogTab;
|
private installedPkgTab: azdata.window.DialogTab;
|
||||||
|
|
||||||
private packageTypeDropdown: azdata.DropDownComponent;
|
private packageTypeDropdown: azdata.DropDownComponent;
|
||||||
|
private locationComponent: azdata.TextComponent;
|
||||||
private installedPackageCount: azdata.TextComponent;
|
private installedPackageCount: azdata.TextComponent;
|
||||||
private installedPackagesTable: azdata.TableComponent;
|
private installedPackagesTable: azdata.TableComponent;
|
||||||
private installedPackagesLoader: azdata.LoadingComponent;
|
private installedPackagesLoader: azdata.LoadingComponent;
|
||||||
@@ -32,18 +32,28 @@ 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) {
|
// TODO: only supporting single location for now. We should add a drop down for multi locations mode
|
||||||
dropdownValues = [PythonPkgType.Anaconda, PythonPkgType.Pip];
|
//
|
||||||
} else {
|
let locationTitle = await this.dialog.model.getLocationTitle();
|
||||||
dropdownValues = [PythonPkgType.Pip];
|
this.locationComponent = view.modelBuilder.text().withProperties({
|
||||||
}
|
value: locationTitle
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let dropdownValues = this.dialog.model.getPackageTypes().map(x => {
|
||||||
|
return {
|
||||||
|
name: x.providerId,
|
||||||
|
displayName: x.packageType
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let defaultPackageType = this.dialog.model.getDefaultPackageType();
|
||||||
this.packageTypeDropdown = view.modelBuilder.dropDown().withProperties({
|
this.packageTypeDropdown = view.modelBuilder.dropDown().withProperties({
|
||||||
values: dropdownValues,
|
values: dropdownValues,
|
||||||
value: dropdownValues[0]
|
value: defaultPackageType
|
||||||
}).component();
|
}).component();
|
||||||
|
this.dialog.changeProvider(defaultPackageType.providerId);
|
||||||
this.packageTypeDropdown.onValueChanged(() => {
|
this.packageTypeDropdown.onValueChanged(() => {
|
||||||
this.dialog.resetPages(this.packageTypeDropdown.value as PythonPkgType)
|
this.dialog.resetPages((<azdata.CategoryValue>this.packageTypeDropdown.value).name)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
});
|
});
|
||||||
@@ -73,6 +83,9 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
|
component: this.locationComponent,
|
||||||
|
title: localize('managePackages.location', "Location")
|
||||||
|
}, {
|
||||||
component: this.packageTypeDropdown,
|
component: this.packageTypeDropdown,
|
||||||
title: localize('managePackages.packageType', "Package Type")
|
title: localize('managePackages.packageType', "Package Type")
|
||||||
}, {
|
}, {
|
||||||
@@ -95,6 +108,7 @@ export class InstalledPackagesTab {
|
|||||||
await view.initializeModel(this.installedPackagesLoader);
|
await view.initializeModel(this.installedPackagesLoader);
|
||||||
|
|
||||||
await this.loadInstalledPackagesInfo();
|
await this.loadInstalledPackagesInfo();
|
||||||
|
this.packageTypeDropdown.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,11 +122,7 @@ export class InstalledPackagesTab {
|
|||||||
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.dialog.model.listPackages();
|
||||||
pythonPackages = await this.jupyterInstallation.getInstalledCondaPackages();
|
|
||||||
} else {
|
|
||||||
pythonPackages = await this.jupyterInstallation.getInstalledPipPackages();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -131,7 +141,7 @@ export class InstalledPackagesTab {
|
|||||||
await this.installedPackageCount.updateProperties({
|
await this.installedPackageCount.updateProperties({
|
||||||
value: localize('managePackages.packageCount', "{0} {1} packages found",
|
value: localize('managePackages.packageCount', "{0} {1} packages found",
|
||||||
packageCount,
|
packageCount,
|
||||||
this.dialog.currentPkgType)
|
this.dialog.model.currentPackageType)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (packageData && packageData.length > 0) {
|
if (packageData && packageData.length > 0) {
|
||||||
@@ -178,12 +188,7 @@ export class InstalledPackagesTab {
|
|||||||
description: taskName,
|
description: taskName,
|
||||||
isCancelable: false,
|
isCancelable: false,
|
||||||
operation: op => {
|
operation: op => {
|
||||||
let uninstallPromise: Promise<void>;
|
let uninstallPromise: Promise<void> = this.dialog.model.uninstallPackages(packages);
|
||||||
if (this.dialog.currentPkgType === PythonPkgType.Anaconda) {
|
|
||||||
uninstallPromise = this.jupyterInstallation.uninstallCondaPackages(packages);
|
|
||||||
} else {
|
|
||||||
uninstallPromise = this.jupyterInstallation.uninstallPipPackages(packages);
|
|
||||||
}
|
|
||||||
uninstallPromise
|
uninstallPromise
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
let uninstallMsg = localize('managePackages.backgroundUninstallComplete',
|
let uninstallMsg = localize('managePackages.backgroundUninstallComplete',
|
||||||
@@ -213,4 +218,4 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
this.uninstallPackageButton.updateProperties({ enabled: true });
|
this.uninstallPackageButton.updateProperties({ enabled: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ 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';
|
import { ManagePackagesDialogModel } from './managePackagesDialogModel';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -18,10 +18,8 @@ export class ManagePackagesDialog {
|
|||||||
private installedPkgTab: InstalledPackagesTab;
|
private installedPkgTab: InstalledPackagesTab;
|
||||||
private addNewPkgTab: AddNewPackageTab;
|
private addNewPkgTab: AddNewPackageTab;
|
||||||
|
|
||||||
public currentPkgType: PythonPkgType;
|
constructor(
|
||||||
|
private _managePackageDialogModel: ManagePackagesDialogModel) {
|
||||||
constructor(private jupyterInstallation: JupyterServerInstallation) {
|
|
||||||
this.currentPkgType = this.jupyterInstallation.usingConda ? PythonPkgType.Anaconda : PythonPkgType.Pip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,8 +47,37 @@ export class ManagePackagesDialog {
|
|||||||
return this.installedPkgTab.loadInstalledPackagesInfo();
|
return this.installedPkgTab.loadInstalledPackagesInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resetPages(newPkgType: PythonPkgType): Promise<void> {
|
public get jupyterInstallation(): JupyterServerInstallation {
|
||||||
this.currentPkgType = newPkgType;
|
return this._managePackageDialogModel.jupyterInstallation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog model instance
|
||||||
|
*/
|
||||||
|
public get model(): ManagePackagesDialogModel {
|
||||||
|
return this._managePackageDialogModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the current provider id
|
||||||
|
* @param providerId Provider Id
|
||||||
|
*/
|
||||||
|
public changeProvider(providerId: string): void {
|
||||||
|
this.model.changeProvider(providerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the tabs for given provider Id
|
||||||
|
* @param providerId Package Management Provider Id
|
||||||
|
*/
|
||||||
|
public async resetPages(providerId: string): Promise<void> {
|
||||||
|
|
||||||
|
// Change the provider in the model
|
||||||
|
//
|
||||||
|
this.changeProvider(providerId);
|
||||||
|
|
||||||
|
// Load packages for given provider
|
||||||
|
//
|
||||||
await this.installedPkgTab.loadInstalledPackagesInfo();
|
await this.installedPkgTab.loadInstalledPackagesInfo();
|
||||||
await this.addNewPkgTab.resetPageFields();
|
await this.addNewPkgTab.resetPageFields();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
|
import { IPackageManageProvider, IPackageDetails, IPackageOverview } from '../../types';
|
||||||
|
|
||||||
|
export interface ManagePackageDialogOptions {
|
||||||
|
multiLocations: boolean;
|
||||||
|
defaultLocation?: string;
|
||||||
|
defaultProviderId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProviderPackageType {
|
||||||
|
packageType: string;
|
||||||
|
providerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage package dialog model
|
||||||
|
*/
|
||||||
|
export class ManagePackagesDialogModel {
|
||||||
|
|
||||||
|
private _currentProvider: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set for locations
|
||||||
|
*/
|
||||||
|
private _locations: Set<string> = new Set<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of locations to providers
|
||||||
|
*/
|
||||||
|
private _packageTypes: Map<string, IPackageManageProvider[]> = new Map<string, IPackageManageProvider[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new instance of the model
|
||||||
|
* @param _jupyterInstallation Jupyter installation
|
||||||
|
* @param _packageManageProviders package manage providers
|
||||||
|
* @param _options dialog options
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private _jupyterInstallation: JupyterServerInstallation,
|
||||||
|
private _packageManageProviders: Map<string, IPackageManageProvider>,
|
||||||
|
private _options?: ManagePackageDialogOptions) {
|
||||||
|
|
||||||
|
if (!this._packageManageProviders || this._packageManageProviders.size === 0) {
|
||||||
|
throw Error('Invalid list of package manager providers');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized the model
|
||||||
|
*/
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
await this.loadCaches();
|
||||||
|
this.loadOptions();
|
||||||
|
this.changeProvider(this.defaultProviderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the model options
|
||||||
|
*/
|
||||||
|
private loadOptions(): void {
|
||||||
|
|
||||||
|
// Set Default Options
|
||||||
|
//
|
||||||
|
if (!this._options) {
|
||||||
|
this._options = this.defaultOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._options.defaultLocation && !this._packageTypes.has(this._options.defaultLocation)) {
|
||||||
|
throw new Error(`Invalid default location '${this._options.defaultLocation}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._options.defaultProviderId && !this._packageManageProviders.has(this._options.defaultProviderId)) {
|
||||||
|
throw new Error(`Invalid default provider id '${this._options.defaultProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._options.multiLocations && !this.defaultLocation) {
|
||||||
|
throw new Error('Default location not specified for single location mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get defaultOptions(): ManagePackageDialogOptions {
|
||||||
|
return {
|
||||||
|
multiLocations: true,
|
||||||
|
defaultLocation: undefined,
|
||||||
|
defaultProviderId: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the providers map
|
||||||
|
*/
|
||||||
|
public get packageManageProviders(): Map<string, IPackageManageProvider> {
|
||||||
|
return this._packageManageProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current provider
|
||||||
|
*/
|
||||||
|
public get currentPackageManageProvider(): IPackageManageProvider | undefined {
|
||||||
|
if (this._currentProvider) {
|
||||||
|
let provider = this._packageManageProviders.get(this._currentProvider);
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current provider
|
||||||
|
*/
|
||||||
|
public get currentPackageType(): string | undefined {
|
||||||
|
if (this._currentProvider) {
|
||||||
|
let provider = this._packageManageProviders.get(this._currentProvider);
|
||||||
|
return provider.packageTarget.packageType;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if multi locations mode is enabled
|
||||||
|
*/
|
||||||
|
public get multiLocationMode(): boolean {
|
||||||
|
return this.options.multiLocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns options
|
||||||
|
*/
|
||||||
|
public get options(): ManagePackageDialogOptions {
|
||||||
|
return this._options || this.defaultOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the array of target locations
|
||||||
|
*/
|
||||||
|
public get targetLocations(): string[] {
|
||||||
|
return Array.from(this._locations.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default location
|
||||||
|
*/
|
||||||
|
public get defaultLocation(): string {
|
||||||
|
return this.options.defaultLocation || this.targetLocations[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default location
|
||||||
|
*/
|
||||||
|
public get defaultProviderId(): string {
|
||||||
|
return this.options.defaultProviderId || Array.from(this.packageManageProviders.keys())[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the provider cache
|
||||||
|
*/
|
||||||
|
private async loadCaches(): Promise<void> {
|
||||||
|
if (this.packageManageProviders) {
|
||||||
|
let keyArray = Array.from(this.packageManageProviders.keys());
|
||||||
|
for (let index = 0; index < keyArray.length; index++) {
|
||||||
|
const element = this.packageManageProviders.get(keyArray[index]);
|
||||||
|
if (await element.canUseProvider()) {
|
||||||
|
if (!this._locations.has(element.packageTarget.location)) {
|
||||||
|
this._locations.add(element.packageTarget.location);
|
||||||
|
}
|
||||||
|
if (!this._packageTypes.has(element.packageTarget.location)) {
|
||||||
|
this._packageTypes.set(element.packageTarget.location, []);
|
||||||
|
}
|
||||||
|
this._packageTypes.get(element.packageTarget.location).push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of providerId to package types for given location
|
||||||
|
*/
|
||||||
|
public getPackageTypes(targetLocation?: string): ProviderPackageType[] {
|
||||||
|
targetLocation = targetLocation || this.defaultLocation;
|
||||||
|
let providers = this._packageTypes.get(targetLocation);
|
||||||
|
return providers.map(x => {
|
||||||
|
return {
|
||||||
|
providerId: x.providerId,
|
||||||
|
packageType: x.packageTarget.packageType
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of providerId to package types for given location
|
||||||
|
*/
|
||||||
|
public getDefaultPackageType(): ProviderPackageType {
|
||||||
|
let defaultProviderId = this.defaultProviderId;
|
||||||
|
let packageTypes = this.getPackageTypes();
|
||||||
|
return packageTypes.find(x => x.providerId === defaultProviderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the list of packages for current provider
|
||||||
|
*/
|
||||||
|
public async listPackages(): Promise<IPackageDetails[]> {
|
||||||
|
let provider = this.currentPackageManageProvider;
|
||||||
|
if (provider) {
|
||||||
|
return await provider.listPackages();
|
||||||
|
} else {
|
||||||
|
throw new Error('Current Provider is not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the current provider
|
||||||
|
*/
|
||||||
|
public changeProvider(providerId: string): void {
|
||||||
|
if (this._packageManageProviders.has(providerId)) {
|
||||||
|
this._currentProvider = providerId;
|
||||||
|
} else {
|
||||||
|
throw Error(`Invalid package type ${providerId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs given packages using current provider
|
||||||
|
* @param packages Packages to install
|
||||||
|
*/
|
||||||
|
public async installPackages(packages: IPackageDetails[]): Promise<void> {
|
||||||
|
let provider = this.currentPackageManageProvider;
|
||||||
|
if (provider) {
|
||||||
|
await provider.installPackages(packages, false);
|
||||||
|
} else {
|
||||||
|
throw new Error('Current Provider is not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the location title for current provider
|
||||||
|
*/
|
||||||
|
public async getLocationTitle(): Promise<string | undefined> {
|
||||||
|
let provider = this.currentPackageManageProvider;
|
||||||
|
if (provider) {
|
||||||
|
return await provider.getLocationTitle();
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnInstalls given packages using current provider
|
||||||
|
* @param packages Packages to install
|
||||||
|
*/
|
||||||
|
public async uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||||
|
let provider = this.currentPackageManageProvider;
|
||||||
|
if (provider) {
|
||||||
|
await provider.uninstallPackages(packages);
|
||||||
|
} else {
|
||||||
|
throw new Error('Current Provider is not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns package preview for given name
|
||||||
|
* @param packageName Package name
|
||||||
|
*/
|
||||||
|
public async getPackageOverview(packageName: string): Promise<IPackageOverview> {
|
||||||
|
let provider = this.currentPackageManageProvider;
|
||||||
|
if (provider) {
|
||||||
|
return await provider.getPackageOverview(packageName);
|
||||||
|
} else {
|
||||||
|
throw new Error('Current Provider is not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the jupyterInstallation instance
|
||||||
|
*/
|
||||||
|
public get jupyterInstallation(): JupyterServerInstallation {
|
||||||
|
return this._jupyterInstallation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import { JupyterController } from './jupyter/jupyterController';
|
import { JupyterController } from './jupyter/jupyterController';
|
||||||
import { AppContext } from './common/appContext';
|
import { AppContext } from './common/appContext';
|
||||||
import { ApiWrapper } from './common/apiWrapper';
|
import { ApiWrapper } from './common/apiWrapper';
|
||||||
import { IExtensionApi } from './types';
|
import { IExtensionApi, IPackageManageProvider } from './types';
|
||||||
import { CellType } from './contracts/content';
|
import { CellType } from './contracts/content';
|
||||||
import { getErrorMessage, isEditorTitleFree } from './common/utils';
|
import { getErrorMessage, isEditorTitleFree } from './common/utils';
|
||||||
import { NotebookUriHandler } from './protocol/notebookUriHandler';
|
import { NotebookUriHandler } from './protocol/notebookUriHandler';
|
||||||
@@ -115,6 +115,12 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
|
|||||||
return {
|
return {
|
||||||
getJupyterController() {
|
getJupyterController() {
|
||||||
return controller;
|
return controller;
|
||||||
|
},
|
||||||
|
registerPackageManager(providerId: string, packageManagerProvider: IPackageManageProvider): void {
|
||||||
|
controller.registerPackageManager(providerId, packageManagerProvider);
|
||||||
|
},
|
||||||
|
getPackageManagers() {
|
||||||
|
return controller.packageManageProviders;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ import { JupyterNotebookProvider } from './jupyterNotebookProvider';
|
|||||||
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
||||||
import CodeAdapter from '../prompts/adapter';
|
import CodeAdapter from '../prompts/adapter';
|
||||||
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
|
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
|
||||||
|
import { IPackageManageProvider } from '../types';
|
||||||
|
import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
|
||||||
|
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
|
||||||
|
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
||||||
|
import { PiPyClient } from './pipyClient';
|
||||||
|
|
||||||
let untitledCounter = 0;
|
let untitledCounter = 0;
|
||||||
|
|
||||||
@@ -32,6 +37,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
private _jupyterInstallation: JupyterServerInstallation;
|
private _jupyterInstallation: JupyterServerInstallation;
|
||||||
private _notebookInstances: IServerInstance[] = [];
|
private _notebookInstances: IServerInstance[] = [];
|
||||||
private _serverInstanceFactory: ServerInstanceFactory = new ServerInstanceFactory();
|
private _serverInstanceFactory: ServerInstanceFactory = new ServerInstanceFactory();
|
||||||
|
private _packageManageProviders = new Map<string, IPackageManageProvider>();
|
||||||
|
|
||||||
private outputChannel: vscode.OutputChannel;
|
private outputChannel: vscode.OutputChannel;
|
||||||
private prompter: IPrompter;
|
private prompter: IPrompter;
|
||||||
@@ -76,7 +82,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
|
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
|
||||||
this.apiWrapper.registerCommand(constants.jupyterManagePackages, () => { return this.doManagePackages(); });
|
this.apiWrapper.registerCommand(constants.jupyterManagePackages, async (args) => { return this.doManagePackages(args); });
|
||||||
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
|
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
|
||||||
|
|
||||||
let supportedFileFilter: vscode.DocumentFilter[] = [
|
let supportedFileFilter: vscode.DocumentFilter[] = [
|
||||||
@@ -85,6 +91,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
let notebookProvider = this.registerNotebookProvider();
|
let notebookProvider = this.registerNotebookProvider();
|
||||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider)));
|
this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider)));
|
||||||
|
|
||||||
|
this.registerDefaultPackageManageProviders();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +117,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
|
|
||||||
public deactivate(): void {
|
public deactivate(): void {
|
||||||
// Shutdown any open notebooks
|
// Shutdown any open notebooks
|
||||||
this._notebookInstances.forEach(instance => { instance.stop(); });
|
this._notebookInstances.forEach(async (instance) => { await instance.stop(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVENT HANDLERS //////////////////////////////////////////////////////
|
// EVENT HANDLERS //////////////////////////////////////////////////////
|
||||||
@@ -196,9 +203,19 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public doManagePackages(): void {
|
public async doManagePackages(options?: ManagePackageDialogOptions): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let packagesDialog = new ManagePackagesDialog(this._jupyterInstallation);
|
if (!options) {
|
||||||
|
options = {
|
||||||
|
multiLocations: false,
|
||||||
|
defaultLocation: constants.localhostName,
|
||||||
|
defaultProviderId: LocalPipPackageManageProvider.ProviderId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
let packagesDialog = new ManagePackagesDialog(model);
|
||||||
packagesDialog.showDialog();
|
packagesDialog.showDialog();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = utils.getErrorMessage(error);
|
let message = utils.getErrorMessage(error);
|
||||||
@@ -206,6 +223,33 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a package provider
|
||||||
|
* @param providerId Provider Id
|
||||||
|
* @param packageManageProvider Provider instance
|
||||||
|
*/
|
||||||
|
public registerPackageManager(providerId: string, packageManageProvider: IPackageManageProvider): void {
|
||||||
|
if (packageManageProvider) {
|
||||||
|
if (!this._packageManageProviders.has(providerId)) {
|
||||||
|
this._packageManageProviders.set(providerId, packageManageProvider);
|
||||||
|
} else {
|
||||||
|
throw Error(`Package manager provider is already registered. provider id: ${providerId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of registered providers
|
||||||
|
*/
|
||||||
|
public get packageManageProviders(): Map<string, IPackageManageProvider> {
|
||||||
|
return this._packageManageProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerDefaultPackageManageProviders(): void {
|
||||||
|
this.registerPackageManager(LocalPipPackageManageProvider.ProviderId, new LocalPipPackageManageProvider(this._jupyterInstallation, new PiPyClient()));
|
||||||
|
this.registerPackageManager(LocalCondaPackageManageProvider.ProviderId, new LocalCondaPackageManageProvider(this._jupyterInstallation));
|
||||||
|
}
|
||||||
|
|
||||||
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
||||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
|
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
|
||||||
pythonDialog.showDialog().catch((err: any) => {
|
pythonDialog.showDialog().catch((err: any) => {
|
||||||
|
|||||||
@@ -38,7 +38,22 @@ function msgDependenciesInstallationFailed(errorMessage: string): string { retur
|
|||||||
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); }
|
||||||
function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); }
|
function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); }
|
||||||
|
|
||||||
export class JupyterServerInstallation {
|
export interface IJupyterServerInstallation {
|
||||||
|
installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
|
||||||
|
configurePackagePaths(): Promise<void>;
|
||||||
|
startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void>;
|
||||||
|
getInstalledPipPackages(): Promise<PythonPkgDetails[]>;
|
||||||
|
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
|
||||||
|
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||||
|
usingConda: boolean;
|
||||||
|
getCondaExePath(): string;
|
||||||
|
executeBufferedCommand(command: string): Promise<string>;
|
||||||
|
executeStreamedCommand(command: string): Promise<void>;
|
||||||
|
installPipPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
|
||||||
|
uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||||
|
pythonExecutable: string;
|
||||||
|
}
|
||||||
|
export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||||
public apiWrapper: ApiWrapper;
|
public apiWrapper: ApiWrapper;
|
||||||
public extensionPath: string;
|
public extensionPath: string;
|
||||||
public pythonBinPath: string;
|
public pythonBinPath: string;
|
||||||
@@ -625,7 +640,7 @@ export class JupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeStreamedCommand(command: string): Promise<void> {
|
public async executeStreamedCommand(command: string): Promise<void> {
|
||||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||||
|
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
|
import * as constants from '../common/constants';
|
||||||
|
import * as utils from '../common/utils';
|
||||||
|
|
||||||
|
export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider Id for Anaconda package manage provider
|
||||||
|
*/
|
||||||
|
public static ProviderId = 'localhost_Anaconda';
|
||||||
|
|
||||||
|
constructor(private jupyterInstallation: IJupyterServerInstallation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns package target
|
||||||
|
*/
|
||||||
|
public get packageTarget(): IPackageTarget {
|
||||||
|
return { location: constants.localhostName, packageType: constants.PythonPkgType.Anaconda };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns provider Id
|
||||||
|
*/
|
||||||
|
public get providerId(): string {
|
||||||
|
return LocalCondaPackageManageProvider.ProviderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of packages
|
||||||
|
*/
|
||||||
|
public async listPackages(): Promise<IPackageDetails[]> {
|
||||||
|
return await this.jupyterInstallation.getInstalledCondaPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs given packages
|
||||||
|
* @param packages Packages to install
|
||||||
|
* @param useMinVersion minimum version
|
||||||
|
*/
|
||||||
|
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||||
|
return this.jupyterInstallation.installCondaPackages(packages, useMinVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls given packages
|
||||||
|
* @param packages Packages to uninstall
|
||||||
|
*/
|
||||||
|
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||||
|
return this.jupyterInstallation.uninstallCondaPackages(packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the provider can be used
|
||||||
|
*/
|
||||||
|
canUseProvider(): Promise<boolean> {
|
||||||
|
return Promise.resolve(this.jupyterInstallation.usingConda);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns location title
|
||||||
|
*/
|
||||||
|
getLocationTitle(): Promise<string> {
|
||||||
|
return Promise.resolve(constants.localhostTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns package overview for given name
|
||||||
|
* @param packageName Package Name
|
||||||
|
*/
|
||||||
|
getPackageOverview(packageName: string): Promise<IPackageOverview> {
|
||||||
|
return this.fetchCondaPackage(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchCondaPackage(packageName: string): Promise<IPackageOverview> {
|
||||||
|
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(constants.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 = utils.sortPackageVersions(Array.from(singletonVersions), false);
|
||||||
|
return {
|
||||||
|
name: packageName,
|
||||||
|
versions: sortedVersions,
|
||||||
|
summary: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
extensions/notebook/src/jupyter/localPipPackageManageProvider.ts
Normal file
110
extensions/notebook/src/jupyter/localPipPackageManageProvider.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||||
|
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
|
import * as constants from '../common/constants';
|
||||||
|
import * as utils from '../common/utils';
|
||||||
|
import { IPiPyClient } from './pipyClient';
|
||||||
|
|
||||||
|
export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider Id for Pip package manage provider
|
||||||
|
*/
|
||||||
|
public static ProviderId = 'localhost_Pip';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private jupyterInstallation: IJupyterServerInstallation,
|
||||||
|
private pipyClient: IPiPyClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns provider Id
|
||||||
|
*/
|
||||||
|
public get providerId(): string {
|
||||||
|
return LocalPipPackageManageProvider.ProviderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns package target
|
||||||
|
*/
|
||||||
|
public get packageTarget(): IPackageTarget {
|
||||||
|
return { location: constants.localhostName, packageType: constants.PythonPkgType.Pip };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of packages
|
||||||
|
*/
|
||||||
|
public async listPackages(): Promise<IPackageDetails[]> {
|
||||||
|
return await this.jupyterInstallation.getInstalledPipPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs given packages
|
||||||
|
* @param packages Packages to install
|
||||||
|
* @param useMinVersion minimum version
|
||||||
|
*/
|
||||||
|
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||||
|
return this.jupyterInstallation.installPipPackages(packages, useMinVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls given packages
|
||||||
|
* @param packages Packages to uninstall
|
||||||
|
*/
|
||||||
|
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||||
|
return this.jupyterInstallation.uninstallPipPackages(packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the provider can be used
|
||||||
|
*/
|
||||||
|
canUseProvider(): Promise<boolean> {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns location title
|
||||||
|
*/
|
||||||
|
getLocationTitle(): Promise<string> {
|
||||||
|
return Promise.resolve(constants.localhostTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns package overview for given name
|
||||||
|
* @param packageName Package Name
|
||||||
|
*/
|
||||||
|
getPackageOverview(packageName: string): Promise<IPackageOverview> {
|
||||||
|
return this.fetchPypiPackage(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchPypiPackage(packageName: string): Promise<IPackageOverview> {
|
||||||
|
let body = await this.pipyClient.fetchPypiPackage(packageName);
|
||||||
|
let packagesJson = JSON.parse(body);
|
||||||
|
let versionNums: string[] = [];
|
||||||
|
let packageSummary = '';
|
||||||
|
if (packagesJson) {
|
||||||
|
if (packagesJson.releases) {
|
||||||
|
let versionKeys = Object.keys(packagesJson.releases);
|
||||||
|
versionKeys = versionKeys.filter(versionKey => {
|
||||||
|
let releaseInfo = packagesJson.releases[versionKey];
|
||||||
|
return Array.isArray(releaseInfo) && releaseInfo.length > 0;
|
||||||
|
});
|
||||||
|
versionNums = utils.sortPackageVersions(versionKeys, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packagesJson.info && packagesJson.info.summary) {
|
||||||
|
packageSummary = packagesJson.info.summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: packageName,
|
||||||
|
versions: versionNums,
|
||||||
|
summary: packageSummary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
46
extensions/notebook/src/jupyter/pipyClient.ts
Normal file
46
extensions/notebook/src/jupyter/pipyClient.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
import * as request from 'request';
|
||||||
|
import * as constants from '../common/constants';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export interface IPiPyClient {
|
||||||
|
fetchPypiPackage(packageName: string): Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PiPyClient implements IPiPyClient {
|
||||||
|
|
||||||
|
private readonly RequestTimeout = 10000;
|
||||||
|
private getLink(packageName: string): string {
|
||||||
|
return `https://pypi.org/pypi/${packageName}/json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchPypiPackage(packageName: string): Promise<any> {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
request.get(this.getLink(packageName), { timeout: this.RequestTimeout }, (error, response, body) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
return reject(constants.PackageNotFoundError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
return reject(
|
||||||
|
localize('managePackages.packageRequestError',
|
||||||
|
"Package info request failed with error: {0} {1}",
|
||||||
|
response.statusCode,
|
||||||
|
response.statusMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import 'mocha';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
|
import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider';
|
||||||
|
import * as constants from '../../common/constants';
|
||||||
|
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
|
||||||
|
import { IPiPyClient, PiPyClient } from '../../jupyter/pipyClient';
|
||||||
|
|
||||||
|
interface TestContext {
|
||||||
|
serverInstallation: IJupyterServerInstallation;
|
||||||
|
piPyClient: IPiPyClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Manage Package Providers', () => {
|
||||||
|
|
||||||
|
it('Conda should return valid package target', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
should.deepEqual(provider.packageTarget, { location: constants.localhostName, packageType: constants.PythonPkgType.Anaconda });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip should return valid package target', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
should.deepEqual(provider.packageTarget, { location: constants.localhostName, packageType: constants.PythonPkgType.Pip });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip listPackages should return valid packages', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
testContext.serverInstallation.getInstalledPipPackages = () => {
|
||||||
|
return Promise.resolve(packages);
|
||||||
|
};
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
|
||||||
|
should.deepEqual(await provider.listPackages(), packages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Conda listPackages should return valid packages', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
testContext.serverInstallation.getInstalledCondaPackages = () => {
|
||||||
|
return Promise.resolve(packages);
|
||||||
|
};
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
|
||||||
|
let actual = await provider.listPackages();
|
||||||
|
should.deepEqual(actual, packages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip installPackages should install packages successfully', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
|
||||||
|
await provider.installPackages(packages, true);
|
||||||
|
serverInstallation.verify(x => x.installPipPackages(packages, true), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Conda installPackages should install packages successfully', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
|
||||||
|
await provider.installPackages(packages, true);
|
||||||
|
serverInstallation.verify(x => x.installCondaPackages(packages, true), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip uninstallPackages should install packages successfully', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
|
||||||
|
await provider.uninstallPackages(packages);
|
||||||
|
serverInstallation.verify(x => x.uninstallPipPackages(packages), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Conda uninstallPackages should install packages successfully', async function (): Promise<void> {
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'name1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
|
||||||
|
await provider.uninstallPackages(packages);
|
||||||
|
serverInstallation.verify(x => x.uninstallCondaPackages(packages), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Conda canUseProvider should return what the server is returning', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
|
||||||
|
should.equal(await provider.canUseProvider(), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip canUseProvider should return true', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
|
||||||
|
should.equal(await provider.canUseProvider(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Pip getPackageOverview should return package info successfully', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
testContext.piPyClient.fetchPypiPackage = (packageName) => {
|
||||||
|
return Promise.resolve(`
|
||||||
|
"{"info":{"summary":"package summary"}, "releases":{"0.0.1":[{"comment_text":""}], "0.0.2":[{"comment_text":""}]}"`);
|
||||||
|
};
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
let client = createPipyClient(testContext);
|
||||||
|
let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object);
|
||||||
|
|
||||||
|
should(provider.getPackageOverview('name')).resolvedWith({
|
||||||
|
name: 'name',
|
||||||
|
versions: ['0.0.1', '0.0.2'],
|
||||||
|
summary: 'summary'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Conda getPackageOverview should return package info successfully', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
testContext.serverInstallation.executeBufferedCommand = (command) => {
|
||||||
|
return Promise.resolve(`
|
||||||
|
"{"name":[{"version":"0.0.1"}, {"version":"0.0.2}]"`);
|
||||||
|
};
|
||||||
|
let serverInstallation = createJupyterServerInstallation(testContext);
|
||||||
|
|
||||||
|
let provider = new LocalCondaPackageManageProvider(serverInstallation.object);
|
||||||
|
|
||||||
|
should(provider.getPackageOverview('name')).resolvedWith({
|
||||||
|
name: 'name',
|
||||||
|
versions: ['0.0.1', '0.0.2'],
|
||||||
|
summary: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createContext(): TestContext {
|
||||||
|
return {
|
||||||
|
serverInstallation: {
|
||||||
|
installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||||
|
configurePackagePaths: () => { return Promise.resolve(); },
|
||||||
|
startInstallProcess: (forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }) => { return Promise.resolve(); },
|
||||||
|
getInstalledPipPackages: () => { return Promise.resolve([]); },
|
||||||
|
installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||||
|
uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
||||||
|
getInstalledCondaPackages: () => { return Promise.resolve([]); },
|
||||||
|
uninstallCondaPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
||||||
|
executeBufferedCommand: (command: string) => { return Promise.resolve(''); },
|
||||||
|
executeStreamedCommand: (command: string) => { return Promise.resolve(); },
|
||||||
|
getCondaExePath: () => { return ''; },
|
||||||
|
pythonExecutable: '',
|
||||||
|
usingConda: false
|
||||||
|
},
|
||||||
|
piPyClient: {
|
||||||
|
fetchPypiPackage: (packageName) => { return Promise.resolve(); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createJupyterServerInstallation(testContext: TestContext): TypeMoq.IMock<JupyterServerInstallation> {
|
||||||
|
let mockInstance = TypeMoq.Mock.ofType(JupyterServerInstallation);
|
||||||
|
mockInstance.setup(x => x.installCondaPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.installCondaPackages(packages, useMinVersion));
|
||||||
|
mockInstance.setup(x => x.installPipPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.installPipPackages(packages, useMinVersion));
|
||||||
|
mockInstance.setup(x => x.uninstallCondaPackages(TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.uninstallCondaPackages(packages));
|
||||||
|
mockInstance.setup(x => x.uninstallPipPackages(TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.uninstallPipPackages(packages));
|
||||||
|
mockInstance.setup(x => x.getInstalledPipPackages()).returns(() => testContext.serverInstallation.getInstalledPipPackages());
|
||||||
|
mockInstance.setup(x => x.getInstalledCondaPackages()).returns(() => testContext.serverInstallation.getInstalledCondaPackages());
|
||||||
|
mockInstance.setup(x => x.usingConda).returns(() => testContext.serverInstallation.usingConda);
|
||||||
|
return mockInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPipyClient(testContext: TestContext): TypeMoq.IMock<IPiPyClient> {
|
||||||
|
let mockInstance = TypeMoq.Mock.ofType(PiPyClient);
|
||||||
|
mockInstance.setup(x => x.fetchPypiPackage(TypeMoq.It.isAny())).returns((packageName) =>
|
||||||
|
testContext.piPyClient.fetchPypiPackage(packageName));
|
||||||
|
return mockInstance;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import 'mocha';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { IPackageManageProvider, IPackageDetails } from '../../types';
|
||||||
|
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
|
||||||
|
|
||||||
|
import { ManagePackagesDialogModel } from '../../dialog/managePackages/managePackagesDialogModel';
|
||||||
|
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||||
|
|
||||||
|
interface TestContext {
|
||||||
|
provider: IPackageManageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Manage Packages', () => {
|
||||||
|
let jupyterServerInstallation: JupyterServerInstallation;
|
||||||
|
beforeEach(() => {
|
||||||
|
jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined, undefined, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exception given undefined providers', async function (): Promise<void> {
|
||||||
|
should.throws(() => { new ManagePackagesDialogModel(jupyterServerInstallation, undefined); }, 'Invalid list of package manager providers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exception given empty providers', async function (): Promise<void> {
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
should.throws(() => { new ManagePackagesDialogModel(jupyterServerInstallation, providers); }, 'Invalid list of package manager providers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should not throw exception given undefined options', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
testContext.provider.listPackages = () => {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
};
|
||||||
|
let provider = createProvider(testContext);
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(provider.providerId, provider);
|
||||||
|
|
||||||
|
should.doesNotThrow(() => { new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Init should throw exception given invalid default location', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let provider = createProvider(testContext);
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(provider.providerId, provider);
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
multiLocations: true,
|
||||||
|
defaultLocation: 'invalid location'
|
||||||
|
};
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||||
|
should(model.init()).rejectedWith(`Invalid default location '${options.defaultLocation}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Init should throw exception given invalid default provider', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let provider = createProvider(testContext);
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(provider.providerId, provider);
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
multiLocations: true,
|
||||||
|
defaultProviderId: 'invalid provider'
|
||||||
|
};
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||||
|
should(model.init()).rejectedWith(`Invalid default provider id '${options.defaultProviderId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Init should throw exception not given valid default location for single location mode', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let provider = createProvider(testContext);
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(provider.providerId, provider);
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
multiLocations: false
|
||||||
|
};
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||||
|
should(model.init()).rejectedWith(`Default location not specified for single location mode`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Init should set default options given undefined', async function (): Promise<void> {
|
||||||
|
let testContext = createContext();
|
||||||
|
let provider = createProvider(testContext);
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(provider.providerId, provider);
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
should.equal(model.multiLocationMode, true);
|
||||||
|
should.equal(model.defaultLocation, provider.packageTarget.location);
|
||||||
|
should.equal(model.defaultProviderId, provider.providerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Init should set default provider Id given valid options', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
let options = {
|
||||||
|
multiLocations: false,
|
||||||
|
defaultLocation: testContext2.provider.packageTarget.location,
|
||||||
|
defaultProviderId: testContext2.provider.providerId
|
||||||
|
};
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
should.equal(model.multiLocationMode, false);
|
||||||
|
should.equal(model.defaultLocation, testContext2.provider.packageTarget.location);
|
||||||
|
should.equal(model.defaultProviderId, testContext2.provider.providerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should create a cache for multiple providers successfully', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext3 = createContext();
|
||||||
|
testContext3.provider.providerId = 'providerId3';
|
||||||
|
testContext3.provider.packageTarget = {
|
||||||
|
location: 'location2',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
providers.set(testContext3.provider.providerId, createProvider(testContext3));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
should.equal(model.defaultLocation, testContext1.provider.packageTarget.location);
|
||||||
|
should.deepEqual(model.getPackageTypes('location1'), [{ providerId: 'providerId1', packageType: 'package-type1'}, {providerId: 'providerId2', packageType: 'package-type2'}]);
|
||||||
|
should.deepEqual(model.getPackageTypes('location2'), [{providerId: 'providerId3', packageType: 'package-type1'}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should not include a provider that can not be used in current context', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
testContext2.provider.canUseProvider = () => { return Promise.resolve(false); };
|
||||||
|
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
should.equal(model.defaultLocation, testContext1.provider.packageTarget.location);
|
||||||
|
should.deepEqual(model.getPackageTypes('location1'), [{providerId: 'providerId1', packageType: 'package-type1'}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changeProvider should change current provider successfully', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.getLocationTitle = () => Promise.resolve('location title 1');
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2');
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location2',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
model.changeProvider('providerId2');
|
||||||
|
should.deepEqual(await model.getLocationTitle(), 'location title 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changeProvider should throw exception given invalid provider', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location2',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
should.throws(() => model.changeProvider('providerId3'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('currentPackageManageProvider should return undefined if current provider is not set', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location2',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
should.equal(model.currentPackageManageProvider, undefined);
|
||||||
|
should(model.listPackages()).rejected();
|
||||||
|
should(model.installPackages(TypeMoq.It.isAny())).rejected();
|
||||||
|
should(model.uninstallPackages(TypeMoq.It.isAny())).rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('current provider should install and uninstall packages successfully', async function (): Promise<void> {
|
||||||
|
let testContext1 = createContext();
|
||||||
|
testContext1.provider.providerId = 'providerId1';
|
||||||
|
testContext1.provider.packageTarget = {
|
||||||
|
location: 'location1',
|
||||||
|
packageType: 'package-type1'
|
||||||
|
};
|
||||||
|
|
||||||
|
let testContext2 = createContext();
|
||||||
|
testContext2.provider.providerId = 'providerId2';
|
||||||
|
testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2');
|
||||||
|
testContext2.provider.packageTarget = {
|
||||||
|
location: 'location2',
|
||||||
|
packageType: 'package-type2'
|
||||||
|
};
|
||||||
|
let packages = [
|
||||||
|
{
|
||||||
|
name: 'p1',
|
||||||
|
version: '1.1.1.1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'p2',
|
||||||
|
version: '1.1.1.2'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
testContext2.provider.listPackages = () => {
|
||||||
|
return Promise.resolve(packages);
|
||||||
|
};
|
||||||
|
|
||||||
|
let providers = new Map<string, IPackageManageProvider>();
|
||||||
|
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||||
|
providers.set(testContext2.provider.providerId, createProvider(testContext2));
|
||||||
|
|
||||||
|
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||||
|
|
||||||
|
await model.init();
|
||||||
|
model.changeProvider('providerId2');
|
||||||
|
should(model.listPackages()).resolvedWith(packages);
|
||||||
|
should(model.installPackages(packages)).resolved();
|
||||||
|
should(model.uninstallPackages(packages)).resolved();
|
||||||
|
should(model.getPackageOverview('p1')).resolved();
|
||||||
|
should(model.getLocationTitle()).rejectedWith('location title 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
function createContext(): TestContext {
|
||||||
|
return {
|
||||||
|
provider: {
|
||||||
|
providerId: 'providerId',
|
||||||
|
packageTarget: {
|
||||||
|
location: 'location',
|
||||||
|
packageType: 'package-type'
|
||||||
|
},
|
||||||
|
canUseProvider: () => { return Promise.resolve(true); },
|
||||||
|
getLocationTitle: () => { return Promise.resolve('location-title'); },
|
||||||
|
installPackages:() => { return Promise.resolve(); },
|
||||||
|
uninstallPackages: (packages: IPackageDetails[]) => { return Promise.resolve(); },
|
||||||
|
listPackages: () => { return Promise.resolve([]); },
|
||||||
|
getPackageOverview: (name: string) => { return Promise.resolve(undefined); },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createProvider(testContext: TestContext): IPackageManageProvider {
|
||||||
|
let mockProvider = TypeMoq.Mock.ofType(LocalPipPackageManageProvider);
|
||||||
|
mockProvider.setup(x => x.canUseProvider()).returns(() => testContext.provider.canUseProvider());
|
||||||
|
mockProvider.setup(x => x.getLocationTitle()).returns(() => testContext.provider.getLocationTitle());
|
||||||
|
mockProvider.setup(x => x.installPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.provider.installPackages(packages, useMinVersion));
|
||||||
|
mockProvider.setup(x => x.uninstallPackages(TypeMoq.It.isAny())).returns((packages) => testContext.provider.uninstallPackages(packages));
|
||||||
|
mockProvider.setup(x => x.listPackages()).returns(() => testContext.provider.listPackages());
|
||||||
|
mockProvider.setup(x => x.getPackageOverview(TypeMoq.It.isAny())).returns((name) => testContext.provider.getPackageOverview(name));
|
||||||
|
mockProvider.setup(x => x.packageTarget).returns(() => testContext.provider.packageTarget);
|
||||||
|
mockProvider.setup(x => x.providerId).returns(() => testContext.provider.providerId);
|
||||||
|
return mockProvider.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
123
extensions/notebook/src/types.d.ts
vendored
123
extensions/notebook/src/types.d.ts
vendored
@@ -3,13 +3,130 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { JupyterController } from './jupyter/jupyterController';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The API provided by this extension.
|
* The API provided by this extension.
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export interface IExtensionApi {
|
export interface IExtensionApi {
|
||||||
getJupyterController(): JupyterController;
|
getJupyterController(): IJupyterController;
|
||||||
|
registerPackageManager(providerId: string, packageManagerProvider: IPackageManageProvider): void;
|
||||||
|
getPackageManagers(): Map<string, IPackageManageProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jupyter controller interface
|
||||||
|
*/
|
||||||
|
export interface IJupyterController {
|
||||||
|
/**
|
||||||
|
* Server installation instance
|
||||||
|
*/
|
||||||
|
jupyterInstallation: IJupyterServerInstallation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IJupyterServerInstallation {
|
||||||
|
/**
|
||||||
|
* Installs packages using pip
|
||||||
|
* @param packages packages to install
|
||||||
|
* @param useMinVersion if true, minimal version will be used
|
||||||
|
*/
|
||||||
|
installPipPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls packages using pip
|
||||||
|
* @param packages packages to uninstall
|
||||||
|
*/
|
||||||
|
uninstallPipPackages(packages: IPackageDetails[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs conda packages
|
||||||
|
* @param packages packages to install
|
||||||
|
* @param useMinVersion if true, minimal version will be used
|
||||||
|
*/
|
||||||
|
installCondaPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls packages using conda
|
||||||
|
* @param packages packages to uninstall
|
||||||
|
*/
|
||||||
|
uninstallCondaPackages(packages: IPackageDetails[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns installed pip packages
|
||||||
|
*/
|
||||||
|
getInstalledPipPackages(): Promise<IPackageDetails[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package details interface
|
||||||
|
*/
|
||||||
|
export interface IPackageDetails {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package target interface
|
||||||
|
*/
|
||||||
|
export interface IPackageTarget {
|
||||||
|
location: string;
|
||||||
|
packageType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package overview
|
||||||
|
*/
|
||||||
|
export interface IPackageOverview {
|
||||||
|
name: string;
|
||||||
|
versions: string[];
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package manage provider interface
|
||||||
|
*/
|
||||||
|
export interface IPackageManageProvider {
|
||||||
|
/**
|
||||||
|
* Provider id
|
||||||
|
*/
|
||||||
|
providerId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* package target
|
||||||
|
*/
|
||||||
|
packageTarget: IPackageTarget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of installed packages
|
||||||
|
*/
|
||||||
|
listPackages(): Promise<IPackageDetails[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs give packages
|
||||||
|
* @param package Packages to install
|
||||||
|
* @param useMinVersion if true, minimal version will be used
|
||||||
|
*/
|
||||||
|
installPackages(package: IPackageDetails[], useMinVersion: boolean): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls given packages
|
||||||
|
* @param package package to uninstall
|
||||||
|
*/
|
||||||
|
uninstallPackages(package: IPackageDetails[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the provider can be used in current context
|
||||||
|
*/
|
||||||
|
canUseProvider(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns location title
|
||||||
|
*/
|
||||||
|
getLocationTitle(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Package Overview
|
||||||
|
* @param packageName package name
|
||||||
|
*/
|
||||||
|
getPackageOverview(packageName: string): Promise<IPackageOverview>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,4 +216,12 @@ export default class DropDownComponent extends ComponentBase implements ICompone
|
|||||||
private setValuesProperties(properties: azdata.DropDownProperties, values: string[] | azdata.CategoryValue[]): void {
|
private setValuesProperties(properties: azdata.DropDownProperties, values: string[] | azdata.CategoryValue[]): void {
|
||||||
properties.values = values;
|
properties.values = values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public focus(): void {
|
||||||
|
if (this.editable && !this._isInAccessibilityMode) {
|
||||||
|
this._editableDropdown.focus();
|
||||||
|
} else {
|
||||||
|
this._selectBox.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user