diff --git a/news/2 Fixes/10071.md b/news/2 Fixes/10071.md new file mode 100644 index 000000000000..842c8c2786d1 --- /dev/null +++ b/news/2 Fixes/10071.md @@ -0,0 +1 @@ +Re-install `Jupyter` instead of installing `kernelspec` if `kernelspec` cannot be found in the python environment. diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts index a20253accaf9..43555442abe8 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts @@ -57,9 +57,8 @@ function sortProductsInOrderForInstallation(products: Product[]) { * @returns {string} */ export function getMessageForLibrariesNotInstalled(products: Product[], interpreterName?: string): string { + // Even though kernelspec cannot be installed, display it so user knows what is missing. const names = products - // Ignore kernelspec as it not something that can be installed. - .filter(product => product !== Product.kernelspec) .map(product => ProductNames.get(product)) .filter(name => !!name) .map(name => name as string); @@ -131,15 +130,15 @@ export class JupyterInterpreterDependencyService { _error?: JupyterInstallError, token?: CancellationToken ): Promise { - const productsToInstall = await this.getDependenciesNotInstalled(interpreter, token); + const missingProducts = await this.getDependenciesNotInstalled(interpreter, token); if (Cancellation.isCanceled(token)) { return JupyterInterpreterDependencyResponse.cancel; } - if (productsToInstall.length === 0) { + if (missingProducts.length === 0) { return JupyterInterpreterDependencyResponse.ok; } - const message = getMessageForLibrariesNotInstalled(productsToInstall, interpreter.displayName); + const message = getMessageForLibrariesNotInstalled(missingProducts, interpreter.displayName); sendTelemetryEvent(Telemetry.JupyterNotInstalledErrorShown); const selection = await this.applicationShell.showErrorMessage( @@ -155,8 +154,15 @@ export class JupyterInterpreterDependencyService { switch (selection) { case DataScience.jupyterInstall(): { + // Ignore kernelspec as it not something that can be installed. + // If kernelspec isn't available, then re-install `Jupyter`. + if (missingProducts.includes(Product.kernelspec) && !missingProducts.includes(Product.jupyter)) { + missingProducts.push(Product.jupyter); + } + const productsToInstall = missingProducts.filter(product => product !== Product.kernelspec); // Install jupyter, then notebook, then others in that order. sortProductsInOrderForInstallation(productsToInstall); + let productToInstall = productsToInstall.shift(); const cancellatonPromise = createPromiseFromCancellation({ cancelAction: 'resolve', diff --git a/src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts b/src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts index 30453a3e6d5b..667cab3fa017 100644 --- a/src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts +++ b/src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts @@ -84,6 +84,33 @@ suite('Data Science - Jupyter Interpreter Configuration', () => { test('Prompt to install if notebook is not installed', async () => testPromptIfModuleNotInstalled(true, false)); test('Prompt to install if jupyter & notebook is not installed', async () => testPromptIfModuleNotInstalled(false, false)); + test('Reinstall Jupyter if jupyter and notebook are installed but kernelspec is not found', async () => { + when(installer.isInstalled(Product.jupyter, pythonInterpreter)).thenResolve(true); + when(installer.isInstalled(Product.notebook, pythonInterpreter)).thenResolve(true); + when(appShell.showErrorMessage(anything(), anything(), anything(), anything())).thenResolve( + // tslint:disable-next-line: no-any + DataScience.jupyterInstall() as any + ); + when(pythonExecService.execModule('jupyter', deepEqual(['kernelspec', '--version']), anything())).thenReject( + new Error('Not found') + ); + when(installer.install(anything(), anything(), anything())).thenResolve(InstallerResponse.Installed); + + const response = await configuration.installMissingDependencies(pythonInterpreter); + + // Jupyter must be installed & not kernelspec or anything else. + verify(installer.install(Product.jupyter, anything(), anything())).once(); + verify(installer.install(anything(), anything(), anything())).once(); + verify( + appShell.showErrorMessage( + anything(), + DataScience.jupyterInstall(), + DataScience.selectDifferentJupyterInterpreter(), + anything() + ) + ).once(); + assert.equal(response, JupyterInterpreterDependencyResponse.cancel); + }); async function testInstallationOfJupyter( installerResponse: InstallerResponse,