Revise Python Notebook learning experience prototype - #3526
Revise Python Notebook learning experience prototype#3526Andrew Casey (amcasey) wants to merge 102 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR revises the VS Code “QDK Learning” prototype to support multiple courses (including authored Python notebook courses), shifting the primary UX for notebook courses from the lesson panel to the notebook surface while adding per-course environment diagnostics and setup via the Python Environments API.
Changes:
- Introduces a multi-course catalog + switching UX (tree view and webview branding/actions) and exposes course metadata via Copilot learning tools.
- Adds support for
python-notebookcourses: drop-in course discovery (course.json+ unit notebooks), workbook materialization, notebook sync, and exercise metadata parsing from cell tags. - Implements per-course Python environment management and diagnostics (extensions checks, venv setup, import checks).
Reviewed changes
Copilot reviewed 40 out of 43 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/requirements.txt | Adds pinned deps for a sample notebook course. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/README.md | Course overview/requirements for the sample course. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/course.json | Defines a drop-in course manifest and import checks. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/02-circuits/circuits.ipynb | Adds authored unit notebook with exercises/hints/solutions. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/02-circuits/_unit.py | Adds unit-specific Python exercise registration/checking. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/01-intro/intro.ipynb | Adds intro unit notebook with first exercise. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/01-intro/_unit.py | Adds intro unit exercise registration. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/_course_lib.py | Adds shared notebook exercise harness utilities. |
| source/vscode/test/suites/learning/test-workspace/qdk-learning/courses/circuit-diagrams-new/_check_env.py | Adds notebook-side environment check rendering. |
| source/vscode/test/suites/extensionUtils.ts | Returns typed Extension API from activation helper. |
| source/vscode/test/runTests.mjs | Adds the “learning” integration test suite option. |
| source/vscode/test/buildTests.mjs | Adds build entrypoints for the learning test suite. |
| source/vscode/src/telemetry.ts | Extends telemetry source enum with “notebook”. |
| source/vscode/src/learning/webview/webview.css | Adds styling for course actions in the webview header. |
| source/vscode/src/learning/webview/webview-client.tsx | Displays active course title and adds course info/browse actions. |
| source/vscode/src/learning/types.d.ts | Adds multi-course, notebook exercise metadata, and env diagnostics types. |
| source/vscode/src/learning/service.ts | Reworks service to handle multiple courses + notebook workflows + env checks. |
| source/vscode/src/learning/python/materialization.ts | Materializes per-unit *.workbook.ipynb working copies. |
| source/vscode/src/learning/python/extensionUtils.ts | Checks/prompts installation for Python + Jupyter extensions. |
| source/vscode/src/learning/python/environment.ts | Adds per-course environment creation/import checks via Python Environments API. |
| source/vscode/src/learning/progressTreeView.ts | Changes tree to Course → Unit → Activity, adds course nodes and Python contexts. |
| source/vscode/src/learning/panel.ts | Disables panel for notebook courses; adds course switching/info commands from webview. |
| source/vscode/src/learning/notebookSync.ts | Syncs service state/context keys with active course notebooks. |
| source/vscode/src/learning/notebookExercises.ts | Parses exercises/hints/solutions from notebook cell tags; strips authoring cells. |
| source/vscode/src/learning/notebookCellStatusBar.ts | Adds per-cell “Ask for a Hint” button for notebook exercises. |
| source/vscode/src/learning/katasProvider.ts | Refactors katas loading into a provider; adds course metadata. |
| source/vscode/src/learning/index.ts | Registers notebook sync/status bar provider; auto-completes exercises on execution. |
| source/vscode/src/learning/fsUtils.ts | Adds shared FS helpers (exists / ensure parent dir). |
| source/vscode/src/learning/dropInCourseProvider.ts | Discovers/parses drop-in notebook courses under qdk-learning/courses/*. |
| source/vscode/src/learning/courseProvider.ts | Adds composite provider abstraction and course descriptors. |
| source/vscode/src/learning/courseLayout.ts | Adds URIs for course roots/source notebooks/workbooks. |
| source/vscode/src/learning/constants.ts | Adds constants for course folders, manifests, workbook suffix, notebook context key. |
| source/vscode/src/learning/commands.ts | Adds course switching/info/diagnostics + notebook navigation helpers and reset-unit. |
| source/vscode/src/gh-copilot/tools.ts | Exposes new learning tools (list/switch/info/check-environment). |
| source/vscode/src/gh-copilot/learningTools.ts | Implements new chat tools and state serialization for courses/env checks. |
| source/vscode/package.json | Wires new commands/menus for course nodes + notebook toolbar actions. |
| source/vscode/ai/qdk-learning.prompt.md | Updates agent prompt to mention multiple courses. |
| source/vscode/ai/qdk-learning.agent.md | Updates agent instructions to support multi-course + notebook courses. |
| source/vscode/.gitignore | Ignores .vscode/ under extension folder. |
| package.json | Adds @vscode/python-environments dependency. |
| package-lock.json | Locks @vscode/python-environments and its engine constraints. |
| .vscode/launch.shared.json | Updates debug launch to open the learning test workspace. |
| .gitignore | Updates ignored learning workspace artifacts at repo root. |
| // Register the course folder as a Python project. This creates a workspace | ||
| // setting, which causes Jupyter to pick up the venv. | ||
| const courseName = courseRoot.path.split("/").pop(); | ||
| await api.addPythonProject({ | ||
| name: `QDK Course: ${courseName}`, | ||
| uri: courseRoot, | ||
| }); |
| const results: { module: string; ok: boolean }[] = []; | ||
| for (const module of modules) { | ||
| const code = await runPython(api, env, ["-c", `import ${module}`]); | ||
| results.push({ module, ok: code === 0 }); | ||
| } | ||
| return results; |
| log.warn( | ||
| `Found multiple virtual environments, using first: ${envs.join(", ")}`, | ||
| ); | ||
| return envs[0]; |
| errors.append( | ||
| "Install missing packages by running this in a new cell, then re-run this one:" | ||
| f"<pre> %pip install -r ../requirements.txt</pre>" | ||
| "Or click the <b>Run Course Diagnostics</b> button in the notebook toolbar " | ||
| "to set up the full environment." | ||
| ) |
| // Expected when the file is missing or unparseable. | ||
| // In this case, leave ws (and, in particular, ws.ProgressData) | ||
| // untouched, since it's either the last known state or the default. | ||
| return; |
| * quick pick when invoked without one (e.g. from the command palette). | ||
| */ | ||
| async function resolveCourseId( | ||
| service: LearningService, |
There was a problem hiding this comment.
Take a note to review this carefully
| * report, offering the fixes the report surfaces (e.g. one-click | ||
| * environment setup, install extensions). | ||
| */ | ||
| async function runEnvironmentCheckCommand( |
There was a problem hiding this comment.
Check this, there's supposed to be two buttons
| }, | ||
| () => service.runEnvironmentCheck(), | ||
| ); | ||
|
|
There was a problem hiding this comment.
Check this part
| return undefined; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
validation
| this._onDidChangeCellStatusBarItems.event; | ||
|
|
||
| private readonly subscription: vscode.Disposable; | ||
|
|
| * | ||
| * VS Code activates this extension for any Jupyter notebook, so a session | ||
| * can be restored with a course workbook in the editor and the learning | ||
| * views never shown. Nothing else would initialize the service in that |
| update(snapshot: OverallProgress | undefined): void { | ||
| this.snapshot = snapshot; | ||
| this.emitter.fire(undefined); | ||
| } | ||
|
|
||
| getTreeItem(node: LearningProgressNode): vscode.TreeItem { | ||
| if (node.kind === "course") { |
| import { | ||
| checkPythonExtensions, | ||
| promptInstallPythonExtensions, |
There was a problem hiding this comment.
Important review
| @@ -344,7 +344,7 @@ type EventTypes = { | |||
| properties: { | |||
There was a problem hiding this comment.
might make more changes to telemetry
| "when": "false" | ||
| }, | ||
| { | ||
| "command": "qsharp-vscode.learningNotebookExplain", |
There was a problem hiding this comment.
Maybe a few commands and tools could be removed in case unnecessary
| "quantum-katas" | ||
| ], | ||
| "toolReferenceName": "qdkLearningCheckEnvironment", | ||
| "displayName": "QDK Learning: Check Environment", |
There was a problem hiding this comment.
Switch course review
| * environment setup is available). | ||
| */ | ||
| async checkEnvironment(): Promise<EnvironmentCheckReport> { | ||
| // TODO (acasey): ensure only one can run at a time |
| const uri = this.getCurrentFileUri(); | ||
| if (this.service.getActiveCourseInfo().kind === "python-notebook") { | ||
| return { | ||
| code: "", // TODO (acasey): can/should we get the code in the active cell? |
| // otherwise pick up where the learner left off. | ||
| if (service.getActiveCourseInfo().kind === "python-notebook") { | ||
| if (service.getProgress().stats.completedActivities === 0) { | ||
| // TODO (acasey): the readme serves as a sort of splash screen while things are set up. |
| service: LearningService, | ||
| node?: LearningProgressNode, | ||
| ): Promise<void> { | ||
| // TODO (acasey): don't allow overlapping runs. |
| ].join("\n"); | ||
|
|
||
| const actions = report.fixes.map((r) => r.label); | ||
| // TODO (acasey): this dialog is ugly and unthemed - can we do better? |
| const course = this.requireCourse(ws, courseId); | ||
| // Idempotent: existing workbooks are left alone, so this only writes | ||
| // files the first time the learner opens the course. | ||
| // TODO (acasey): if materializing fails, you basically have to reload the |
| */ | ||
| private firstIncompletePosition(course: CatalogCourse): ActivityLocation { | ||
| for (const unit of course.units) { | ||
| // TODO (acasey): reuse firstIncompleteInUnit |
| throw err; | ||
| } | ||
| this.startWatcher(); | ||
| // TODO (acasey): make sure we're firing this an appropriate number of times |
| if (activity.type === "exercise") { | ||
| // Python-notebook exercises live in the notebook — show their | ||
| // description as lesson text so the panel renders something useful. | ||
| // TODO (acasey): If we went back to using the lesson panel, we'd probably |
| this._onDidChangeState.fire(this.getState()); | ||
|
|
||
| const units = this.requireWorkspace().catalog.units; | ||
| // TODO (acasey): do we actually want telemetry for other courses? |
| }, | ||
| "node_modules/@vscode/python-environments": { | ||
| "version": "1.0.0", | ||
| "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@vscode/python-environments/-/python-environments-1.0.0.tgz", |
There was a problem hiding this comment.
Make sure we don't merge with ms-feed-25
Builds on #3394.