Skip to content

Commit 03a3efb

Browse files
kimadelineKartik RajDonJayamanne
authored
Make the Jupyter extension an optional dependency (#16422)
* Make Jupyter an optional dependency (#16267) * News entry * Move Jupyter to the optional dependencies step * Update news/1 Enhancements/16102.md Co-authored-by: Kartik Raj <[email protected]> Co-authored-by: Kartik Raj <[email protected]> * License wording update (#16278) * Wording * License wording * Add a "Jupyter not installed" notification helper (#16321) * Add telemetry info * Use enum for the telemetry * Add prompt as a standalone function * Remove "Install" from the prompt * Make it a class * Register singleton * Rename file to a long but descriptive name * Unit tests * Add to package.nls.json * Use sinon for tests * Use the same "Jupyter is not installed" message everywhere (#16372) * rename to showJupyterNotInstalledPrompt * Replace existing prompt with new prompt * Remove Jupyter check from command manager * Update the start page to use the prompt (#16417) * Update copy * Update origin key * Show prompt if jupyter not installed & should show * Add tests for this functionality only * Update news entry * Remove comments * follow-up from the merge * Add singletons for startpage functional tests * Missing one symbol * Update src/client/common/startPage/startPage.ts Co-authored-by: Don Jayamanne <[email protected]> * Add logging Co-authored-by: Don Jayamanne <[email protected]> Co-authored-by: Kartik Raj <[email protected]> Co-authored-by: Don Jayamanne <[email protected]>
1 parent a393225 commit 03a3efb

21 files changed

+530
-87
lines changed

.github/actions/build-vsix/action.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ runs:
4444
run: npm run updateBuildNumber -- --buildNumber $GITHUB_RUN_ID
4545
shell: bash
4646

47-
- name: Update extension dependencies
48-
run: npm run addExtensionDependencies
49-
shell: bash
50-
51-
- name: Update Optional extension dependencies
47+
- name: Update optional extension dependencies
5248
run: npm run addExtensionPackDependencies
5349
shell: bash
5450

build/license-header.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
PLEASE NOTE: This Python extension for Visual Studio Code has a hard dependency on the Jupyter extension for Visual Studio Code which is installed automatically alongside it. The Python extension for Visual Studio Code also holds an optional dependency on the Pylance extension for Visual Studio Code, which is also installed automatically but is separately licensed.
1+
PLEASE NOTE: This is the license for the Python extension for Visual Studio Code. The Python extension automatically installs other extensions as optional dependencies, which can be uninstalled at any time. These extensions have separate licenses:
22

3-
All the source code for the Python extension for Visual Studio Code is available under the MIT License (given below) as is the source code for the Jupyter extension for Visual Studio Code. But the optional Pylance extension for Visual Studio Code is only available in binary form and it is not licensed under the MIT License. The Pylance extension for Visual Studio Code is licensed under a Microsoft proprietary license, the terms of which are available here: https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license.
3+
- The Jupyter extension is released under an MIT License:
4+
https://marketplace.visualstudio.com/items/ms-toolsai.jupyter/license
5+
6+
- The Pylance extension is only available in binary form and is released under a Microsoft proprietary license, the terms of which are available here:
7+
https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license
48

59
------------------------------------------------------------------------------

gulpfile.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,32 +77,17 @@ gulp.task('webpack', async () => {
7777
await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.config.js', 'extension');
7878
});
7979

80-
gulp.task('addExtensionDependencies', async () => {
81-
await addExtensionDependencies();
82-
});
83-
8480
gulp.task('addExtensionPackDependencies', async () => {
8581
await buildLicense();
8682
await addExtensionPackDependencies();
8783
});
8884

89-
async function addExtensionDependencies() {
90-
// Update the package.json to add extension dependencies at build time so that
91-
// extension dependencies need not be installed during development
92-
const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8');
93-
const packageJson = JSON.parse(packageJsonContents);
94-
packageJson.extensionDependencies = ['ms-toolsai.jupyter'].concat(
95-
packageJson.extensionDependencies ? packageJson.extensionDependencies : [],
96-
);
97-
await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8');
98-
}
99-
10085
async function addExtensionPackDependencies() {
10186
// Update the package.json to add extension pack dependencies at build time so that
10287
// extension dependencies need not be installed during development
10388
const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8');
10489
const packageJson = JSON.parse(packageJsonContents);
105-
packageJson.extensionPack = ['ms-python.vscode-pylance'].concat(
90+
packageJson.extensionPack = ['ms-toolsai.jupyter', 'ms-python.vscode-pylance'].concat(
10691
packageJson.extensionPack ? packageJson.extensionPack : [],
10792
);
10893
await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8');

news/1 Enhancements/16102.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Move the Jupyter extension from being a hard dependency to an optional one, and display an informational prompt if Jupyter commands try to be executed from the Start Page.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,6 @@
20862086
"format-check": "prettier --check 'src/**/*.ts' 'src/**/*.tsx' 'build/**/*.js' '.github/**/*.yml' gulpfile.js",
20872087
"format-fix": "prettier --write 'src/**/*.ts' 'src/**/*.tsx' 'build/**/*.js' '.github/**/*.yml' gulpfile.js",
20882088
"clean": "gulp clean",
2089-
"addExtensionDependencies": "gulp addExtensionDependencies",
20902089
"addExtensionPackDependencies": "gulp addExtensionPackDependencies",
20912090
"updateBuildNumber": "gulp updateBuildNumber",
20922091
"verifyBundle": "gulp verifyBundle",

package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@
221221
"StartPage.createAPythonFile": "Create a Python File",
222222
"StartPage.pythonFileDescription": "- Create a <div class=\"link\" role=\"button\" onclick={0}>new file</div> with a .py extension",
223223
"StartPage.openInteractiveWindow": "Use the Interactive Window to develop Python Scripts",
224-
"StartPage.interactiveWindowDesc": "- You can create cells on a Python file by typing \"#%%\" <br /> - Use \"<div class=\"italics\">Shift + Enter</div> \" to run a cell, the output will be shown in the interactive window",
224+
"StartPage.interactiveWindowDesc": "- You can create cells on a Python file by typing \"#%%\". Make sure you have the Jupyter extension installed. <br /> - Use \"<div class=\"italics\">Shift + Enter</div> \" to run a cell, the output will be shown in the interactive window",
225225
"StartPage.releaseNotes": "Take a look at our <a class=\"link\" href={0}>Release Notes</a> to learn more about the latest features.",
226226
"StartPage.mailingList": "<a class=\"link\" href={0}>Sign up</a> for tips and tutorials through our mailing list.",
227227
"StartPage.tutorialAndDoc": "Explore more features in our <a class=\"link\" href={0}>Tutorials</a> or check <a class=\"link\" href={1}>Documentation</a> for tips and troubleshooting.",
@@ -232,6 +232,7 @@
232232
"StartPage.folderDesc": "- Open a <div class=\"link\" role=\"button\" onclick={0}>Folder</div><br /> - Open a <div class=\"link\" role=\"button\" onclick={1}>Workspace</div>",
233233
"StartPage.badWebPanelFormatString": "<html><body><h1>{0} is not a valid file name</h1></body></html>",
234234
"Jupyter.extensionRequired": "The Jupyter extension is required to perform that task. Click Yes to open the Jupyter extension installation page.",
235+
"Jupyter.extensionNotInstalled": "This feature is available in the Jupyter extension, which isn't currently installed.",
235236
"TensorBoard.missingSourceFile": "We could not locate the requested source file on disk. Please manually specify the file.",
236237
"TensorBoard.selectMissingSourceFile": "Choose File",
237238
"TensorBoard.selectMissingSourceFileDescription": "The source file's contents may not match the original contents in the trace.",

src/client/common/application/commandManager.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { inject, injectable } from 'inversify';
4+
import { injectable } from 'inversify';
55
import { commands, Disposable, TextEditor, TextEditorEdit } from 'vscode';
66
import { ICommandNameArgumentTypeMapping } from './commands';
7-
import { ICommandManager, IJupyterExtensionDependencyManager } from './types';
7+
import { ICommandManager } from './types';
88

99
@injectable()
1010
export class CommandManager implements ICommandManager {
11-
constructor(
12-
@inject(IJupyterExtensionDependencyManager)
13-
private jupyterExtensionDependencyManager: IJupyterExtensionDependencyManager,
14-
) {}
11+
constructor() {}
1512

1613
/**
1714
* Registers a command that can be invoked via a keyboard shortcut,
@@ -73,11 +70,7 @@ export class CommandManager implements ICommandManager {
7370
E extends keyof ICommandNameArgumentTypeMapping,
7471
U extends ICommandNameArgumentTypeMapping[E]
7572
>(command: E, ...rest: U): Thenable<T | undefined> {
76-
if (command.includes('jupyter') && !this.jupyterExtensionDependencyManager.isJupyterExtensionInstalled) {
77-
return this.jupyterExtensionDependencyManager.installJupyterExtension(this);
78-
} else {
79-
return commands.executeCommand<T>(command, ...rest);
80-
}
73+
return commands.executeCommand<T>(command, ...rest);
8174
}
8275

8376
/**

src/client/common/application/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,6 @@ export interface ICommandManager {
489489
export const IJupyterExtensionDependencyManager = Symbol('IJupyterExtensionDependencyManager');
490490
export interface IJupyterExtensionDependencyManager {
491491
readonly isJupyterExtensionInstalled: boolean;
492-
installJupyterExtension(commandManager: ICommandManager): Promise<undefined>;
493492
}
494493

495494
export const IDocumentManager = Symbol('IDocumentManager');

src/client/common/serviceRegistry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ import {
119119
} from './types';
120120
import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStepInput';
121121
import { Random } from './utils/random';
122+
import { JupyterNotInstalledNotificationHelper } from '../jupyter/jupyterNotInstalledNotificationHelper';
123+
import { IJupyterNotInstalledNotificationHelper } from '../jupyter/types';
122124

123125
export function registerTypes(serviceManager: IServiceManager) {
124126
serviceManager.addSingletonInstance<boolean>(IsWindows, IS_WINDOWS);
@@ -140,6 +142,10 @@ export function registerTypes(serviceManager: IServiceManager) {
140142
IJupyterExtensionDependencyManager,
141143
JupyterExtensionDependencyManager,
142144
);
145+
serviceManager.addSingleton<IJupyterNotInstalledNotificationHelper>(
146+
IJupyterNotInstalledNotificationHelper,
147+
JupyterNotInstalledNotificationHelper,
148+
);
143149
serviceManager.addSingleton<ICommandManager>(ICommandManager, CommandManager);
144150
serviceManager.addSingleton<IConfigurationService>(IConfigurationService, ConfigurationService);
145151
serviceManager.addSingleton<IWorkspaceService>(IWorkspaceService, WorkspaceService);

src/client/common/startPage/startPage.ts

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@
33

44
'use strict';
55

6-
import { inject, injectable } from 'inversify';
6+
import { inject, injectable, named } from 'inversify';
77
import * as path from 'path';
88
import { ConfigurationTarget, EventEmitter, UIKind, Uri, ViewColumn } from 'vscode';
99
import { IExtensionSingleActivationService } from '../../activation/types';
1010
import { EXTENSION_ROOT_DIR } from '../../constants';
11+
import { IJupyterNotInstalledNotificationHelper, JupyterNotInstalledOrigin } from '../../jupyter/types';
1112
import { sendTelemetryEvent } from '../../telemetry';
1213
import {
1314
IApplicationEnvironment,
1415
IApplicationShell,
1516
ICommandManager,
1617
IDocumentManager,
18+
IJupyterExtensionDependencyManager,
1719
IWebviewPanelProvider,
1820
IWorkspaceService,
1921
} from '../application/types';
20-
import { CommandSource } from '../constants';
22+
import { CommandSource, STANDARD_OUTPUT_CHANNEL } from '../constants';
2123
import { IFileSystem } from '../platform/types';
22-
import { IConfigurationService, IExtensionContext, Resource } from '../types';
24+
import { IConfigurationService, IExtensionContext, IOutputChannel, Resource } from '../types';
2325
import * as localize from '../utils/localize';
26+
import { Jupyter } from '../utils/localize';
2427
import { StopWatch } from '../utils/stopWatch';
2528
import { Telemetry } from './constants';
2629
import { StartPageMessageListener } from './startPageMessageListener';
@@ -62,6 +65,10 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
6265
@inject(IApplicationShell) private appShell: IApplicationShell,
6366
@inject(IExtensionContext) private readonly context: IExtensionContext,
6467
@inject(IApplicationEnvironment) private appEnvironment: IApplicationEnvironment,
68+
@inject(IJupyterNotInstalledNotificationHelper)
69+
private notificationHelper: IJupyterNotInstalledNotificationHelper,
70+
@inject(IJupyterExtensionDependencyManager) private depsManager: IJupyterExtensionDependencyManager,
71+
@inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel,
6572
) {
6673
super(
6774
configuration,
@@ -128,6 +135,9 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
128135
}
129136

130137
public async onMessage(message: string, payload: unknown): Promise<void> {
138+
const shouldShowJupyterNotInstalledPrompt = await this.notificationHelper.shouldShowJupypterExtensionNotInstalledPrompt();
139+
const isJupyterInstalled = this.depsManager.isJupyterExtensionInstalled;
140+
131141
switch (message) {
132142
case StartPageMessages.Started:
133143
this.webviewDidLoad = true;
@@ -140,19 +150,29 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
140150
break;
141151
}
142152
case StartPageMessages.OpenBlankNotebook: {
143-
sendTelemetryEvent(Telemetry.StartPageOpenBlankNotebook);
144-
this.setTelemetryFlags();
145-
146-
const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);
147-
148-
if (savedVersion) {
149-
await this.commandManager.executeCommand(
150-
'jupyter.opennotebook',
151-
undefined,
152-
CommandSource.commandPalette,
153-
);
153+
if (!isJupyterInstalled) {
154+
this.output.appendLine(Jupyter.jupyterExtensionNotInstalled());
155+
156+
if (shouldShowJupyterNotInstalledPrompt) {
157+
await this.notificationHelper.showJupyterNotInstalledPrompt(
158+
JupyterNotInstalledOrigin.StartPageOpenBlankNotebook,
159+
);
160+
}
154161
} else {
155-
this.openSampleNotebook().ignoreErrors();
162+
sendTelemetryEvent(Telemetry.StartPageOpenBlankNotebook);
163+
this.setTelemetryFlags();
164+
165+
const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);
166+
167+
if (savedVersion) {
168+
await this.commandManager.executeCommand(
169+
'jupyter.opennotebook',
170+
undefined,
171+
CommandSource.commandPalette,
172+
);
173+
} else {
174+
this.openSampleNotebook().ignoreErrors();
175+
}
156176
}
157177
break;
158178
}
@@ -168,15 +188,25 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
168188
break;
169189
}
170190
case StartPageMessages.OpenInteractiveWindow: {
171-
sendTelemetryEvent(Telemetry.StartPageOpenInteractiveWindow);
172-
this.setTelemetryFlags();
173-
174-
const doc2 = await this.documentManager.openTextDocument({
175-
language: 'python',
176-
content: `#%%\nprint("${localize.StartPage.helloWorld()}")`,
177-
});
178-
await this.documentManager.showTextDocument(doc2, 1, true);
179-
await this.commandManager.executeCommand('jupyter.runallcells', Uri.parse(''));
191+
if (!isJupyterInstalled) {
192+
this.output.appendLine(Jupyter.jupyterExtensionNotInstalled());
193+
194+
if (shouldShowJupyterNotInstalledPrompt) {
195+
await this.notificationHelper.showJupyterNotInstalledPrompt(
196+
JupyterNotInstalledOrigin.StartPageOpenInteractiveWindow,
197+
);
198+
}
199+
} else {
200+
sendTelemetryEvent(Telemetry.StartPageOpenInteractiveWindow);
201+
this.setTelemetryFlags();
202+
203+
const doc2 = await this.documentManager.openTextDocument({
204+
language: 'python',
205+
content: `#%%\nprint("${localize.StartPage.helloWorld()}")`,
206+
});
207+
await this.documentManager.showTextDocument(doc2, 1, true);
208+
await this.commandManager.executeCommand('jupyter.runallcells', doc2.uri);
209+
}
180210
break;
181211
}
182212
case StartPageMessages.OpenCommandPalette:
@@ -192,10 +222,20 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
192222
await this.commandManager.executeCommand('workbench.action.quickOpen', '>Create New Blank Notebook');
193223
break;
194224
case StartPageMessages.OpenSampleNotebook:
195-
sendTelemetryEvent(Telemetry.StartPageOpenSampleNotebook);
196-
this.setTelemetryFlags();
225+
if (!isJupyterInstalled) {
226+
this.output.appendLine(Jupyter.jupyterExtensionNotInstalled());
227+
228+
if (shouldShowJupyterNotInstalledPrompt) {
229+
await this.notificationHelper.showJupyterNotInstalledPrompt(
230+
JupyterNotInstalledOrigin.StartPageOpenSampleNotebook,
231+
);
232+
}
233+
} else {
234+
sendTelemetryEvent(Telemetry.StartPageOpenSampleNotebook);
235+
this.setTelemetryFlags();
197236

198-
this.openSampleNotebook().ignoreErrors();
237+
this.openSampleNotebook().ignoreErrors();
238+
}
199239
break;
200240
case StartPageMessages.OpenFileBrowser: {
201241
sendTelemetryEvent(Telemetry.StartPageOpenFileBrowser);

0 commit comments

Comments
 (0)