mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 18:48:33 -05:00
Added multiple location option to package management dialog (#9790)
* Added multiple location option to package management dialog
This commit is contained in:
@@ -147,7 +147,7 @@ export class AddNewPackageTab {
|
||||
|
||||
let pipPackage: PipPackageOverview;
|
||||
pipPackage = await this.dialog.model.getPackageOverview(packageName);
|
||||
if (!pipPackage.versions || pipPackage.versions.length === 0) {
|
||||
if (!pipPackage?.versions || pipPackage.versions.length === 0) {
|
||||
this.dialog.showErrorMessage(
|
||||
localize('managePackages.noVersionsFound',
|
||||
"Could not find any valid versions for the specified package"));
|
||||
|
||||
@@ -20,11 +20,13 @@ export class InstalledPackagesTab {
|
||||
private installedPkgTab: azdata.window.DialogTab;
|
||||
|
||||
private packageTypeDropdown: azdata.DropDownComponent;
|
||||
private locationComponent: azdata.TextComponent;
|
||||
private locationComponent: azdata.Component;
|
||||
private installedPackageCount: azdata.TextComponent;
|
||||
private installedPackagesTable: azdata.TableComponent;
|
||||
private installedPackagesLoader: azdata.LoadingComponent;
|
||||
private uninstallPackageButton: azdata.ButtonComponent;
|
||||
private view: azdata.ModelView | undefined;
|
||||
private formBuilder: azdata.FormBuilder;
|
||||
|
||||
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
||||
this.prompter = new CodeAdapter();
|
||||
@@ -32,14 +34,7 @@ export class InstalledPackagesTab {
|
||||
this.installedPkgTab = azdata.window.createTab(localize('managePackages.installedTabTitle', "Installed"));
|
||||
|
||||
this.installedPkgTab.registerContent(async view => {
|
||||
|
||||
// TODO: only supporting single location for now. We should add a drop down for multi locations mode
|
||||
//
|
||||
let locationTitle = await this.dialog.model.getLocationTitle();
|
||||
this.locationComponent = view.modelBuilder.text().withProperties({
|
||||
value: locationTitle
|
||||
}).component();
|
||||
|
||||
this.view = view;
|
||||
let dropdownValues = this.dialog.model.getPackageTypes().map(x => {
|
||||
return {
|
||||
name: x.providerId,
|
||||
@@ -52,11 +47,17 @@ export class InstalledPackagesTab {
|
||||
value: defaultPackageType
|
||||
}).component();
|
||||
this.dialog.changeProvider(defaultPackageType.providerId);
|
||||
this.packageTypeDropdown.onValueChanged(() => {
|
||||
this.dialog.resetPages((<azdata.CategoryValue>this.packageTypeDropdown.value).name)
|
||||
.catch(err => {
|
||||
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
this.packageTypeDropdown.onValueChanged(async () => {
|
||||
this.dialog.changeProvider((<azdata.CategoryValue>this.packageTypeDropdown.value).name);
|
||||
try {
|
||||
await this.resetLocations();
|
||||
await this.dialog.resetPages();
|
||||
}
|
||||
catch (err) {
|
||||
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.installedPackageCount = view.modelBuilder.text().withProperties({
|
||||
@@ -81,11 +82,8 @@ export class InstalledPackagesTab {
|
||||
}).component();
|
||||
this.uninstallPackageButton.onDidClick(() => this.doUninstallPackage());
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
this.formBuilder = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.locationComponent,
|
||||
title: localize('managePackages.location', "Location")
|
||||
}, {
|
||||
component: this.packageTypeDropdown,
|
||||
title: localize('managePackages.packageType', "Package Type")
|
||||
}, {
|
||||
@@ -97,10 +95,11 @@ export class InstalledPackagesTab {
|
||||
}, {
|
||||
component: this.uninstallPackageButton,
|
||||
title: ''
|
||||
}]).component();
|
||||
}]);
|
||||
await this.resetLocations();
|
||||
|
||||
this.installedPackagesLoader = view.modelBuilder.loadingComponent()
|
||||
.withItem(formModel)
|
||||
.withItem(this.formBuilder.component())
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
@@ -112,6 +111,68 @@ export class InstalledPackagesTab {
|
||||
});
|
||||
}
|
||||
|
||||
private async resetLocations(): Promise<void> {
|
||||
if (this.view) {
|
||||
if (this.locationComponent) {
|
||||
this.formBuilder.removeFormItem({
|
||||
component: this.locationComponent,
|
||||
title: localize('managePackages.location', "Location")
|
||||
});
|
||||
}
|
||||
|
||||
this.locationComponent = await InstalledPackagesTab.getLocationComponent(this.view, this.dialog);
|
||||
|
||||
this.formBuilder.insertFormItem({
|
||||
component: this.locationComponent,
|
||||
title: localize('managePackages.location', "Location")
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a component for package locations
|
||||
* @param view Model view
|
||||
* @param dialog Manage package dialog
|
||||
*/
|
||||
public static async getLocationComponent(view: azdata.ModelView, dialog: ManagePackagesDialog): Promise<azdata.Component> {
|
||||
const locations = await dialog.model.getLocations();
|
||||
let component: azdata.Component;
|
||||
if (locations && locations.length === 1) {
|
||||
component = view.modelBuilder.text().withProperties({
|
||||
value: locations[0].displayName
|
||||
}).component();
|
||||
} else if (locations) {
|
||||
let dropdownValues = locations.map(x => {
|
||||
return {
|
||||
name: x.name,
|
||||
displayName: x.displayName
|
||||
};
|
||||
});
|
||||
let locationDropDown = view.modelBuilder.dropDown().withProperties({
|
||||
values: dropdownValues,
|
||||
value: dropdownValues[0]
|
||||
}).component();
|
||||
|
||||
locationDropDown.onValueChanged(async () => {
|
||||
dialog.changeLocation((<azdata.CategoryValue>locationDropDown.value).name);
|
||||
try {
|
||||
await dialog.resetPages();
|
||||
}
|
||||
catch (err) {
|
||||
dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||
}
|
||||
});
|
||||
component = locationDropDown;
|
||||
} else {
|
||||
component = view.modelBuilder.text().withProperties({
|
||||
}).component();
|
||||
}
|
||||
if (locations && locations.length > 0) {
|
||||
dialog.changeLocation(locations[0].name);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
public get tab(): azdata.window.DialogTab {
|
||||
return this.installedPkgTab;
|
||||
}
|
||||
|
||||
@@ -67,14 +67,17 @@ export class ManagePackagesDialog {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
* @param providerId Package Management Provider Id
|
||||
* Changes the current location
|
||||
* @param location location name
|
||||
*/
|
||||
public async resetPages(providerId: string): Promise<void> {
|
||||
public changeLocation(location: string): void {
|
||||
this.model.changeLocation(location);
|
||||
}
|
||||
|
||||
// Change the provider in the model
|
||||
//
|
||||
this.changeProvider(providerId);
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
*/
|
||||
public async resetPages(): Promise<void> {
|
||||
|
||||
// Load packages for given provider
|
||||
//
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageOverview } from '../../types';
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageOverview, IPackageLocation } from '../../types';
|
||||
|
||||
export interface ManagePackageDialogOptions {
|
||||
multiLocations: boolean;
|
||||
defaultLocation?: string;
|
||||
defaultProviderId?: string;
|
||||
}
|
||||
@@ -23,11 +22,12 @@ export interface ProviderPackageType {
|
||||
export class ManagePackagesDialogModel {
|
||||
|
||||
private _currentProvider: string;
|
||||
private _currentLocation: string;
|
||||
|
||||
/**
|
||||
* A set for locations
|
||||
*/
|
||||
private _locations: Set<string> = new Set<string>();
|
||||
private _locationTypes: Set<string> = new Set<string>();
|
||||
|
||||
/**
|
||||
* Map of locations to providers
|
||||
@@ -77,15 +77,10 @@ export class ManagePackagesDialogModel {
|
||||
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
|
||||
};
|
||||
@@ -120,13 +115,6 @@ export class ManagePackagesDialogModel {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if multi locations mode is enabled
|
||||
*/
|
||||
public get multiLocationMode(): boolean {
|
||||
return this.options.multiLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options
|
||||
*/
|
||||
@@ -135,17 +123,17 @@ export class ManagePackagesDialogModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the array of target locations
|
||||
* returns the array of target location types
|
||||
*/
|
||||
public get targetLocations(): string[] {
|
||||
return Array.from(this._locations.keys());
|
||||
public get targetLocationTypes(): string[] {
|
||||
return Array.from(this._locationTypes.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default location
|
||||
*/
|
||||
public get defaultLocation(): string {
|
||||
return this.options.defaultLocation || this.targetLocations[0];
|
||||
return this.options.defaultLocation || this.targetLocationTypes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,8 +152,8 @@ export class ManagePackagesDialogModel {
|
||||
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._locationTypes.has(element.packageTarget.location)) {
|
||||
this._locationTypes.add(element.packageTarget.location);
|
||||
}
|
||||
if (!this._packageTypes.has(element.packageTarget.location)) {
|
||||
this._packageTypes.set(element.packageTarget.location, []);
|
||||
@@ -205,7 +193,7 @@ export class ManagePackagesDialogModel {
|
||||
public async listPackages(): Promise<IPackageDetails[]> {
|
||||
let provider = this.currentPackageManageProvider;
|
||||
if (provider) {
|
||||
return await provider.listPackages();
|
||||
return await provider.listPackages(this._currentLocation);
|
||||
} else {
|
||||
throw new Error('Current Provider is not set');
|
||||
}
|
||||
@@ -222,6 +210,13 @@ export class ManagePackagesDialogModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current location
|
||||
*/
|
||||
public changeLocation(location: string): void {
|
||||
this._currentLocation = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs given packages using current provider
|
||||
* @param packages Packages to install
|
||||
@@ -229,7 +224,7 @@ export class ManagePackagesDialogModel {
|
||||
public async installPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
let provider = this.currentPackageManageProvider;
|
||||
if (provider) {
|
||||
await provider.installPackages(packages, false);
|
||||
await provider.installPackages(packages, false, this._currentLocation);
|
||||
} else {
|
||||
throw new Error('Current Provider is not set');
|
||||
}
|
||||
@@ -238,10 +233,10 @@ export class ManagePackagesDialogModel {
|
||||
/**
|
||||
* Returns the location title for current provider
|
||||
*/
|
||||
public async getLocationTitle(): Promise<string | undefined> {
|
||||
public async getLocations(): Promise<IPackageLocation[] | undefined> {
|
||||
let provider = this.currentPackageManageProvider;
|
||||
if (provider) {
|
||||
return await provider.getLocationTitle();
|
||||
return await provider.getLocations();
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@@ -253,7 +248,7 @@ export class ManagePackagesDialogModel {
|
||||
public async uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
let provider = this.currentPackageManageProvider;
|
||||
if (provider) {
|
||||
await provider.uninstallPackages(packages);
|
||||
await provider.uninstallPackages(packages, this._currentLocation);
|
||||
} else {
|
||||
throw new Error('Current Provider is not set');
|
||||
}
|
||||
|
||||
@@ -207,7 +207,6 @@ export class JupyterController implements vscode.Disposable {
|
||||
try {
|
||||
if (!options) {
|
||||
options = {
|
||||
multiLocations: false,
|
||||
defaultLocation: constants.localhostName,
|
||||
defaultProviderId: LocalPipPackageManageProvider.ProviderId
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview, IPackageLocation } from '../types';
|
||||
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
@@ -35,7 +35,7 @@ export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||
/**
|
||||
* Returns list of packages
|
||||
*/
|
||||
public async listPackages(): Promise<IPackageDetails[]> {
|
||||
public async listPackages(location?: string): Promise<IPackageDetails[]> {
|
||||
return await this.jupyterInstallation.getInstalledCondaPackages();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||
* @param packages Packages to install
|
||||
* @param useMinVersion minimum version
|
||||
*/
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean, location?: string): Promise<void> {
|
||||
return this.jupyterInstallation.installCondaPackages(packages, useMinVersion);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||
* Uninstalls given packages
|
||||
* @param packages Packages to uninstall
|
||||
*/
|
||||
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
uninstallPackages(packages: IPackageDetails[], location?: string): Promise<void> {
|
||||
return this.jupyterInstallation.uninstallCondaPackages(packages);
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||
/**
|
||||
* Returns location title
|
||||
*/
|
||||
getLocationTitle(): Promise<string> {
|
||||
return Promise.resolve(constants.localhostTitle);
|
||||
getLocations(): Promise<IPackageLocation[]> {
|
||||
return Promise.resolve([{ displayName: constants.localhostTitle, name: constants.localhostName }]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview, IPackageLocation } from '../types';
|
||||
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
@@ -38,7 +38,7 @@ export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||
/**
|
||||
* Returns list of packages
|
||||
*/
|
||||
public async listPackages(): Promise<IPackageDetails[]> {
|
||||
public async listPackages(location?: string): Promise<IPackageDetails[]> {
|
||||
return await this.jupyterInstallation.getInstalledPipPackages();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||
* @param packages Packages to install
|
||||
* @param useMinVersion minimum version
|
||||
*/
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean, location?: string): Promise<void> {
|
||||
return this.jupyterInstallation.installPipPackages(packages, useMinVersion);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||
* Uninstalls given packages
|
||||
* @param packages Packages to uninstall
|
||||
*/
|
||||
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
uninstallPackages(packages: IPackageDetails[], location?: string): Promise<void> {
|
||||
return this.jupyterInstallation.uninstallPipPackages(packages);
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||
/**
|
||||
* Returns location title
|
||||
*/
|
||||
getLocationTitle(): Promise<string> {
|
||||
return Promise.resolve(constants.localhostTitle);
|
||||
getLocations(): Promise<IPackageLocation[]> {
|
||||
return Promise.resolve([{ displayName: constants.localhostTitle, name: constants.localhostName }]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { ManagePackagesDialog } from '../../dialog/managePackages/managePackagesDialog';
|
||||
import { ManagePackagesDialogModel } from '../../dialog/managePackages/managePackagesDialogModel';
|
||||
import { IPackageManageProvider, IPackageLocation } from '../../types';
|
||||
import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider';
|
||||
import { InstalledPackagesTab } from '../../dialog/managePackages/installedPackagesTab';
|
||||
import should = require('should');
|
||||
|
||||
interface TestContext {
|
||||
view: azdata.ModelView;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
dialog: TypeMoq.IMock<ManagePackagesDialog>;
|
||||
model: TypeMoq.IMock<ManagePackagesDialogModel>;
|
||||
}
|
||||
|
||||
describe('Manage Package Dialog', () => {
|
||||
|
||||
it('getLocationComponent should create text component for one location', async function (): Promise<void> {
|
||||
let testContext = createViewContext();
|
||||
let locations = [
|
||||
{
|
||||
displayName: 'dl1',
|
||||
name: 'nl1'
|
||||
}
|
||||
];
|
||||
testContext.model.setup(x => x.getLocations()).returns(() => Promise.resolve(locations));
|
||||
testContext.model.setup(x => x.changeLocation('nl1'));
|
||||
testContext.dialog.setup(x => x.changeLocation('nl1'));
|
||||
|
||||
let actual = await InstalledPackagesTab.getLocationComponent(testContext.view, testContext.dialog.object);
|
||||
should.equal('onTextChanged' in actual, true);
|
||||
testContext.dialog.verify(x => x.changeLocation('nl1'), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('getLocationComponent should create text component for undefined location', async function (): Promise<void> {
|
||||
let testContext = createViewContext();
|
||||
let locations: IPackageLocation[] | undefined = undefined;
|
||||
testContext.model.setup(x => x.getLocations()).returns(() => Promise.resolve(locations));
|
||||
|
||||
let actual = await InstalledPackagesTab.getLocationComponent(testContext.view, testContext.dialog.object);
|
||||
should.equal('onTextChanged' in actual, true);
|
||||
});
|
||||
|
||||
it('getLocationComponent should create drop down component for more than one location', async function (): Promise<void> {
|
||||
let testContext = createViewContext();
|
||||
let locations = [
|
||||
{
|
||||
displayName: 'dl1',
|
||||
name: 'nl1'
|
||||
},
|
||||
{
|
||||
displayName: 'dl2',
|
||||
name: 'nl2'
|
||||
}
|
||||
];
|
||||
testContext.model.setup(x => x.getLocations()).returns(() => Promise.resolve(locations));
|
||||
testContext.dialog.setup(x => x.changeLocation('nl1'));
|
||||
testContext.dialog.setup(x => x.resetPages()).returns(() => Promise.resolve());
|
||||
|
||||
let actual = await InstalledPackagesTab.getLocationComponent(testContext.view, testContext.dialog.object);
|
||||
should.equal('onValueChanged' in actual, true);
|
||||
testContext.dialog.verify(x => x.changeLocation('nl1'), TypeMoq.Times.once());
|
||||
(<azdata.DropDownComponent>actual).value = {
|
||||
displayName: 'dl2',
|
||||
name: 'nl2'
|
||||
};
|
||||
testContext.onClick.fire();
|
||||
testContext.dialog.verify(x => x.changeLocation('nl2'), TypeMoq.Times.once());
|
||||
testContext.dialog.verify(x => x.resetPages(), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
it('getLocationComponent should show error if reset pages fails', async function (): Promise<void> {
|
||||
let testContext = createViewContext();
|
||||
let locations = [
|
||||
{
|
||||
displayName: 'dl1',
|
||||
name: 'nl1'
|
||||
},
|
||||
{
|
||||
displayName: 'dl2',
|
||||
name: 'nl2'
|
||||
}
|
||||
];
|
||||
testContext.model.setup(x => x.getLocations()).returns(() => Promise.resolve(locations));
|
||||
testContext.dialog.setup(x => x.changeLocation('nl1'));
|
||||
testContext.dialog.setup(x => x.resetPages()).throws(new Error('failed'));
|
||||
testContext.dialog.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
|
||||
let actual = await InstalledPackagesTab.getLocationComponent(testContext.view, testContext.dialog.object);
|
||||
should.equal('onValueChanged' in actual, true);
|
||||
testContext.dialog.verify(x => x.changeLocation('nl1'), TypeMoq.Times.once());
|
||||
(<azdata.DropDownComponent>actual).value = {
|
||||
displayName: 'dl2',
|
||||
name: 'nl2'
|
||||
};
|
||||
testContext.onClick.fire();
|
||||
testContext.dialog.verify(x => x.changeLocation('nl2'), TypeMoq.Times.once());
|
||||
testContext.dialog.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
function createViewContext(): TestContext {
|
||||
let packageManageProviders = new Map<string, IPackageManageProvider>();
|
||||
packageManageProviders.set(LocalCondaPackageManageProvider.ProviderId, new LocalCondaPackageManageProvider(undefined));
|
||||
let model = TypeMoq.Mock.ofInstance(new ManagePackagesDialogModel(undefined, packageManageProviders));
|
||||
let dialog = TypeMoq.Mock.ofInstance(new ManagePackagesDialog(model.object));
|
||||
dialog.setup(x => x.model).returns(() => model.object);
|
||||
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
let componentBase: azdata.Component = {
|
||||
id: '',
|
||||
updateProperties: () => Promise.resolve(),
|
||||
updateProperty: () => Promise.resolve(),
|
||||
updateCssStyles: undefined!,
|
||||
onValidityChanged: undefined!,
|
||||
valid: true,
|
||||
validate: undefined!,
|
||||
focus: undefined!
|
||||
};
|
||||
let button: azdata.ButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
let radioButton: azdata.RadioButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
const components: azdata.Component[] = [];
|
||||
let container = {
|
||||
clearItems: () => { },
|
||||
addItems: () => { },
|
||||
addItem: () => { },
|
||||
removeItem: () => true,
|
||||
insertItem: () => { },
|
||||
items: components,
|
||||
setLayout: () => { }
|
||||
};
|
||||
let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
let flex: azdata.FlexContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => button,
|
||||
withProperties: () => buttonBuilder,
|
||||
withValidation: () => buttonBuilder
|
||||
};
|
||||
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => radioButton,
|
||||
withProperties: () => radioButtonBuilder,
|
||||
withValidation: () => radioButtonBuilder
|
||||
};
|
||||
let inputBox: () => azdata.InputBoxComponent = () => Object.assign({}, componentBase, {
|
||||
onTextChanged: undefined!,
|
||||
onEnterKeyPressed: undefined!,
|
||||
value: ''
|
||||
});
|
||||
let image: () => azdata.ImageComponent = () => Object.assign({}, componentBase, {
|
||||
|
||||
});
|
||||
let dropdown: () => azdata.DropDownComponent = () => Object.assign({}, componentBase, {
|
||||
onValueChanged: onClick.event,
|
||||
value: {
|
||||
name: '',
|
||||
displayName: ''
|
||||
},
|
||||
values: []
|
||||
});
|
||||
let declarativeTable: () => azdata.DeclarativeTableComponent = () => Object.assign({}, componentBase, {
|
||||
onDataChanged: undefined!,
|
||||
data: [],
|
||||
columns: []
|
||||
});
|
||||
|
||||
let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, {
|
||||
loading: false,
|
||||
component: undefined!
|
||||
});
|
||||
|
||||
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = {
|
||||
component: () => declarativeTable(),
|
||||
withProperties: () => declarativeTableBuilder,
|
||||
withValidation: () => declarativeTableBuilder
|
||||
};
|
||||
|
||||
let loadingBuilder: azdata.LoadingComponentBuilder = {
|
||||
component: () => loadingComponent(),
|
||||
withProperties: () => loadingBuilder,
|
||||
withValidation: () => loadingBuilder,
|
||||
withItem: () => loadingBuilder
|
||||
};
|
||||
|
||||
let formBuilder: azdata.FormBuilder = Object.assign({}, {
|
||||
component: () => form,
|
||||
addFormItem: () => { },
|
||||
insertFormItem: () => { },
|
||||
removeFormItem: () => true,
|
||||
addFormItems: () => { },
|
||||
withFormItems: () => formBuilder,
|
||||
withProperties: () => formBuilder,
|
||||
withValidation: () => formBuilder,
|
||||
withItems: () => formBuilder,
|
||||
withLayout: () => formBuilder
|
||||
});
|
||||
|
||||
let flexBuilder: azdata.FlexBuilder = Object.assign({}, {
|
||||
component: () => flex,
|
||||
withProperties: () => flexBuilder,
|
||||
withValidation: () => flexBuilder,
|
||||
withItems: () => flexBuilder,
|
||||
withLayout: () => flexBuilder
|
||||
});
|
||||
|
||||
let inputBoxBuilder: azdata.ComponentBuilder<azdata.InputBoxComponent> = {
|
||||
component: () => {
|
||||
let r = inputBox();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => inputBoxBuilder,
|
||||
withValidation: () => inputBoxBuilder
|
||||
};
|
||||
let imageBuilder: azdata.ComponentBuilder<azdata.ImageComponent> = {
|
||||
component: () => {
|
||||
let r = image();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => imageBuilder,
|
||||
withValidation: () => imageBuilder
|
||||
};
|
||||
let dropdownBuilder: azdata.ComponentBuilder<azdata.DropDownComponent> = {
|
||||
component: () => {
|
||||
let r = dropdown();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => dropdownBuilder,
|
||||
withValidation: () => dropdownBuilder
|
||||
};
|
||||
|
||||
let view: azdata.ModelView = {
|
||||
onClosed: undefined!,
|
||||
connection: undefined!,
|
||||
serverInfo: undefined!,
|
||||
valid: true,
|
||||
onValidityChanged: undefined!,
|
||||
validate: undefined!,
|
||||
initializeModel: () => { return Promise.resolve(); },
|
||||
modelBuilder: {
|
||||
radioCardGroup: undefined!,
|
||||
navContainer: undefined!,
|
||||
divContainer: undefined!,
|
||||
flexContainer: () => flexBuilder,
|
||||
splitViewContainer: undefined!,
|
||||
dom: undefined!,
|
||||
card: undefined!,
|
||||
inputBox: () => inputBoxBuilder,
|
||||
checkBox: undefined!,
|
||||
radioButton: () => radioButtonBuilder,
|
||||
webView: undefined!,
|
||||
editor: undefined!,
|
||||
diffeditor: undefined!,
|
||||
text: () => inputBoxBuilder,
|
||||
image: () => imageBuilder,
|
||||
button: () => buttonBuilder,
|
||||
dropDown: () => dropdownBuilder,
|
||||
tree: undefined!,
|
||||
listBox: undefined!,
|
||||
table: undefined!,
|
||||
declarativeTable: () => declarativeTableBuilder,
|
||||
dashboardWidget: undefined!,
|
||||
dashboardWebview: undefined!,
|
||||
formContainer: () => formBuilder,
|
||||
groupContainer: undefined!,
|
||||
toolbarContainer: undefined!,
|
||||
loadingComponent: () => loadingBuilder,
|
||||
fileBrowserTree: undefined!,
|
||||
hyperlink: undefined!,
|
||||
tabbedPanel: undefined!,
|
||||
separator: undefined!
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
dialog: dialog,
|
||||
model: model,
|
||||
view: view,
|
||||
onClick: onClick,
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -50,7 +50,6 @@ describe('Manage Packages', () => {
|
||||
providers.set(provider.providerId, provider);
|
||||
|
||||
let options = {
|
||||
multiLocations: true,
|
||||
defaultLocation: 'invalid location'
|
||||
};
|
||||
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||
@@ -64,29 +63,12 @@ describe('Manage Packages', () => {
|
||||
providers.set(provider.providerId, provider);
|
||||
|
||||
let options = {
|
||||
multiLocations: true,
|
||||
defaultProviderId: 'invalid provider'
|
||||
};
|
||||
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options);
|
||||
await should(model.init()).rejectedWith(`Invalid default provider id '${options.defaultProviderId}`);
|
||||
});
|
||||
|
||||
/* Test disabled. Tracking issue: https://github.com/microsoft/azuredatastudio/issues/8877
|
||||
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);
|
||||
await 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);
|
||||
@@ -96,7 +78,6 @@ describe('Manage Packages', () => {
|
||||
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);
|
||||
});
|
||||
@@ -119,14 +100,12 @@ describe('Manage Packages', () => {
|
||||
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);
|
||||
});
|
||||
@@ -195,7 +174,7 @@ describe('Manage Packages', () => {
|
||||
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.getLocations = () => Promise.resolve([{displayName: 'location title 1', name: 'location1'}]);
|
||||
testContext1.provider.packageTarget = {
|
||||
location: 'location1',
|
||||
packageType: 'package-type1'
|
||||
@@ -203,7 +182,7 @@ describe('Manage Packages', () => {
|
||||
|
||||
let testContext2 = createContext();
|
||||
testContext2.provider.providerId = 'providerId2';
|
||||
testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2');
|
||||
testContext2.provider.getLocations = () => Promise.resolve([{displayName: 'location title 2', name: 'location2'}]);
|
||||
testContext2.provider.packageTarget = {
|
||||
location: 'location2',
|
||||
packageType: 'package-type2'
|
||||
@@ -217,7 +196,7 @@ describe('Manage Packages', () => {
|
||||
|
||||
await model.init();
|
||||
model.changeProvider('providerId2');
|
||||
should.deepEqual(await model.getLocationTitle(), 'location title 2');
|
||||
should.deepEqual(await model.getLocations(), [{displayName: 'location title 2', name: 'location2'}]);
|
||||
});
|
||||
|
||||
it('changeProvider should throw exception given invalid provider', async function (): Promise<void> {
|
||||
@@ -283,7 +262,7 @@ describe('Manage Packages', () => {
|
||||
|
||||
let testContext2 = createContext();
|
||||
testContext2.provider.providerId = 'providerId2';
|
||||
testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2');
|
||||
testContext2.provider.getLocations = () => Promise.resolve([{displayName: 'location title 2', name: 'location2'}]);
|
||||
testContext2.provider.packageTarget = {
|
||||
location: 'location2',
|
||||
packageType: 'package-type2'
|
||||
@@ -301,6 +280,12 @@ describe('Manage Packages', () => {
|
||||
testContext2.provider.listPackages = () => {
|
||||
return Promise.resolve(packages);
|
||||
};
|
||||
testContext1.provider.listPackages = () => {
|
||||
return Promise.resolve([{
|
||||
name: 'p3',
|
||||
version: '1.1.1.3'
|
||||
}]);
|
||||
};
|
||||
|
||||
let providers = new Map<string, IPackageManageProvider>();
|
||||
providers.set(testContext1.provider.providerId, createProvider(testContext1));
|
||||
@@ -315,7 +300,50 @@ describe('Manage Packages', () => {
|
||||
await should(model.installPackages(packages)).resolved();
|
||||
await should(model.uninstallPackages(packages)).resolved();
|
||||
await should(model.getPackageOverview('p1')).resolved();
|
||||
await should(model.getLocationTitle()).resolvedWith('location title 2');
|
||||
await should(model.getLocations()).resolvedWith([{displayName: 'location title 2', name: 'location2'}]);
|
||||
});
|
||||
|
||||
it('listPackages should return packages for current location', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
testContext.provider.providerId = 'providerId1';
|
||||
testContext.provider.packageTarget = {
|
||||
location: 'location1',
|
||||
packageType: 'package-type1'
|
||||
};
|
||||
|
||||
let packages1 = [
|
||||
{
|
||||
name: 'p1',
|
||||
version: '1.1.1.1'
|
||||
},
|
||||
{
|
||||
name: 'p2',
|
||||
version: '1.1.1.2'
|
||||
}
|
||||
];
|
||||
let packages2 = [{
|
||||
name: 'p3',
|
||||
version: '1.1.1.3'
|
||||
}];
|
||||
testContext.provider.listPackages = (location) => {
|
||||
if (location === 'location1') {
|
||||
return Promise.resolve(packages1);
|
||||
} else {
|
||||
return Promise.resolve(packages2);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let providers = new Map<string, IPackageManageProvider>();
|
||||
providers.set(testContext.provider.providerId, createProvider(testContext));
|
||||
|
||||
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined);
|
||||
|
||||
await model.init();
|
||||
model.changeProvider('providerId1');
|
||||
model.changeLocation('location2');
|
||||
|
||||
await should(model.listPackages()).resolvedWith(packages2);
|
||||
});
|
||||
|
||||
function createContext(): TestContext {
|
||||
@@ -327,7 +355,7 @@ describe('Manage Packages', () => {
|
||||
packageType: 'package-type'
|
||||
},
|
||||
canUseProvider: () => { return Promise.resolve(true); },
|
||||
getLocationTitle: () => { return Promise.resolve('location-title'); },
|
||||
getLocations: () => { return Promise.resolve([{displayName: 'location-title', name: 'location'}]); },
|
||||
installPackages:() => { return Promise.resolve(); },
|
||||
uninstallPackages: (packages: IPackageDetails[]) => { return Promise.resolve(); },
|
||||
listPackages: () => { return Promise.resolve([]); },
|
||||
@@ -339,10 +367,10 @@ describe('Manage Packages', () => {
|
||||
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.getLocations()).returns(() => testContext.provider.getLocations());
|
||||
mockProvider.setup(x => x.installPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.provider.installPackages(packages, useMinVersion));
|
||||
mockProvider.setup(x => x.uninstallPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages) => testContext.provider.uninstallPackages(packages));
|
||||
mockProvider.setup(x => x.listPackages(TypeMoq.It.isAny())).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);
|
||||
|
||||
18
extensions/notebook/src/types.d.ts
vendored
18
extensions/notebook/src/types.d.ts
vendored
@@ -65,6 +65,14 @@ export interface IPackageDetails {
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Package location
|
||||
*/
|
||||
export interface IPackageLocation {
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Package target interface
|
||||
*/
|
||||
@@ -99,20 +107,22 @@ export interface IPackageManageProvider {
|
||||
/**
|
||||
* Returns list of installed packages
|
||||
*/
|
||||
listPackages(): Promise<IPackageDetails[]>;
|
||||
listPackages(location?: string): Promise<IPackageDetails[]>;
|
||||
|
||||
/**
|
||||
* Installs give packages
|
||||
* @param package Packages to install
|
||||
* @param useMinVersion if true, minimal version will be used
|
||||
* @param location package location
|
||||
*/
|
||||
installPackages(package: IPackageDetails[], useMinVersion: boolean): Promise<void>;
|
||||
installPackages(package: IPackageDetails[], useMinVersion: boolean, location?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Uninstalls given packages
|
||||
* @param package package to uninstall
|
||||
* @param location package location
|
||||
*/
|
||||
uninstallPackages(package: IPackageDetails[]): Promise<void>;
|
||||
uninstallPackages(package: IPackageDetails[], location?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns true if the provider can be used in current context
|
||||
@@ -122,7 +132,7 @@ export interface IPackageManageProvider {
|
||||
/**
|
||||
* Returns location title
|
||||
*/
|
||||
getLocationTitle(): Promise<string>;
|
||||
getLocations(): Promise<IPackageLocation[]>;
|
||||
|
||||
/**
|
||||
* Returns Package Overview
|
||||
|
||||
Reference in New Issue
Block a user