Skip to content

feat(python-notebook-migration, frontend): add Jupyter notebook iframe panel component - #5271

Merged
mengw15 merged 34 commits into
apache:mainfrom
zyratlo:migration-tool-jupyter-panel
Jul 31, 2026
Merged

feat(python-notebook-migration, frontend): add Jupyter notebook iframe panel component#5271
mengw15 merged 34 commits into
apache:mainfrom
zyratlo:migration-tool-jupyter-panel

Conversation

@zyratlo

@zyratlo zyratlo commented May 28, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Adds JupyterNotebookPanelComponent, a draggable iframe panel that hosts JupyterLab inside the Texera workspace, mounts it in the workspace shell, and wires its delete button to remove the current workflow's stored notebook through the panel service.

  • frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.ts
    • JupyterNotebookPanelComponent (standalone)
      • jupyterUrl is typed SafeResourceUrl | null and starts as null.
      • ngOnInit subscribes to jupyterPanelService.jupyterNotebookPanelVisible$. Each emission sets isVisible. When the panel becomes visible it fetches the iframe URL from notebookMigrationService.getJupyterIframeURL() (a rejected fetch is caught and yields null without ending the subscription), then assigns jupyterUrl to the sanitized URL (via DomSanitizer.bypassSecurityTrustResourceUrl) or to null, and calls checkIframeRef(). A null result (panel hidden, or a failed or empty fetch) clears jupyterUrl so a stale URL is never shown.
      • ngAfterViewInit calls checkIframeRef(), which registers the iframe with the panel service via setIframeRef inside a setTimeout, only when the panel is visible and iframeRef is present, and stays silent when the panel is hidden.
      • deletePanel() calls jupyterPanelService.deleteJupyterNotebook().
      • minimizePanel() calls jupyterPanelService.minimizeJupyterNotebookPanel(); visibility is driven by the observable.
  • frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.html
    • cdkDrag panel gated on *ngIf="isVisible", with cdkDragHandle on the header so only the header is draggable.
    • Header title plus a minimize button (tooltip "Minimize") and a delete button (tooltip "Delete notebook") that opens an nz-popconfirm confirmation before deleting.
    • <iframe [src]="jupyterUrl"> gated on *ngIf="jupyterUrl", so the iframe renders only once a sanitized URL is available.
  • frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.scss
    • Fixed-position panel styling, header visuals, a muted hover color shared by the two header buttons, and iframe sizing.
  • frontend/src/app/workspace/component/workspace.component.ts and workspace.component.html
    • The standalone WorkspaceComponent imports JupyterNotebookPanelComponent and mounts <texera-jupyter-notebook-panel> in the workspace shell.
  • frontend/src/app/workspace/service/jupyter-panel/jupyter-panel.service.ts
    • deleteJupyterNotebook(): deletes the current workflow's stored notebook via notebookMigrationService.deleteNotebookAndMapping(wid). On success it hides the panel, drops the in-memory mapping, resets jupyterNotebookExists$, and unhighlights all operators and links. On failure it shows a notification and leaves the panel open. An unsaved workflow (no wid) resets local state without a backend call.
    • hideAndClearLocalState() (private): hides the panel and drops the current workflow's in-memory mapping only. The workflow-change handler in init() uses this, so switching workflows never deletes a notebook from the backend.
    • clearHighlights() (private): unhighlights all operators and links and clears the highlight index.
  • frontend/src/app/workspace/service/notebook-migration/notebook-migration.service.ts
    • deleteNotebookAndMapping(wid): sends a POST to /notebook-migration/delete-notebook-and-mapping, short-circuiting when the feature is disabled.

Any related issues, documentation, discussions?

Closes #5270
Parent issue #4301

How was this PR tested?

Unit tests (Vitest):

  • jupyter-notebook-panel.component.spec.ts: visibility transitions, URL fetch and sanitization, the rejected-fetch path (the subscription stays alive and jupyterUrl stays null), the iframe rendering only when a URL is present and being removed on hide, iframe ref registration and the silent-when-hidden behavior, and delegation of the minimize and delete buttons.
  • jupyter-panel.service.spec.ts: deleteJupyterNotebook calls the backend, hides the panel, resets jupyterNotebookExists$, and unhighlights on success; keeps the panel open and notifies on failure; and a guard that switching workflows never triggers a backend delete.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 4.8)

@github-actions github-actions Bot added the frontend Changes related to the frontend GUI label May 28, 2026
@zyratlo zyratlo changed the title added jupyter notebook panel iframe logic feat(python-notebook-migration, frontend): add Jupyter notebook iframe panel component May 28, 2026
@codecov-commenter

codecov-commenter commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.47%. Comparing base (ba63ced) to head (34757cf).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5271      +/-   ##
============================================
+ Coverage     80.44%   80.47%   +0.02%     
- Complexity     3874     3875       +1     
============================================
  Files          1160     1162       +2     
  Lines         46192    46264      +72     
  Branches       5146     5153       +7     
============================================
+ Hits          37160    37230      +70     
- Misses         7372     7374       +2     
  Partials       1660     1660              
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø) Carriedforward from f3671b9
agent-service 77.42% <ø> (ø) Carriedforward from f3671b9
amber 75.32% <ø> (+<0.01%) ⬆️ Carriedforward from f3671b9
computing-unit-managing-service 20.49% <ø> (ø) Carriedforward from f3671b9
config-service 65.97% <ø> (ø) Carriedforward from f3671b9
file-service 66.80% <ø> (ø) Carriedforward from f3671b9
frontend 83.22% <100.00%> (+0.05%) ⬆️
notebook-migration-service 78.89% <ø> (ø) Carriedforward from f3671b9
pyamber 97.32% <ø> (-0.05%) ⬇️ Carriedforward from f3671b9
workflow-compiling-service 26.31% <ø> (ø) Carriedforward from f3671b9

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zyratlo
zyratlo marked this pull request as draft June 5, 2026 21:30
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @bobbai00, @kunwp1
    You can notify them by mentioning @bobbai00, @kunwp1 in a comment.

zyratlo added a commit to zyratlo/texera that referenced this pull request Jul 23, 2026
adopt main's reviewed versions for the already merged migration slices: backend notebook-migration-service, frontend migration service, config, build, and sql.

pull the reviewed open PR content from its branches: jupyter panel visibility surface and mini-map button (apache#5265), jupyter notebook iframe panel (apache#5271), and the AI generate workflow modal and panel host (apache#5273).

reconcile app.module.ts by keeping main's JupyterPanelService bootstrap listener and registering JupyterNotebookPanelComponent.
@zyratlo

zyratlo commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

/request-review @mengw15

@github-actions
github-actions Bot requested a review from mengw15 July 27, 2026 23:20
@zyratlo
zyratlo marked this pull request as ready for review July 27, 2026 23:24
@github-actions github-actions Bot added the ci changes related to CI label Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 0 better · 🔴 7 worse · ⚪ 8 noise (<±5%) · 0 without baseline

Compared against main 49f9e2c benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 394 0.24 24,637/34,363/34,363 us 🔴 +20.8% / 🔴 +111.7%
🔴 bs=100 sw=10 sl=64 828 0.506 119,816/152,100/152,100 us 🔴 +9.9% / 🔴 +40.0%
bs=1000 sw=10 sl=64 946 0.578 1,059,104/1,104,183/1,104,183 us ⚪ within ±5% / 🔴 +6.0%
Baseline details

Latest main 49f9e2c from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 394 tuples/sec 463 tuples/sec 767.9 tuples/sec -14.9% -48.7%
bs=10 sw=10 sl=64 MB/s 0.24 MB/s 0.282 MB/s 0.469 MB/s -14.9% -48.8%
bs=10 sw=10 sl=64 p50 24,637 us 20,396 us 12,502 us +20.8% +97.1%
bs=10 sw=10 sl=64 p95 34,363 us 32,329 us 16,234 us +6.3% +111.7%
bs=10 sw=10 sl=64 p99 34,363 us 32,329 us 18,919 us +6.3% +81.6%
bs=100 sw=10 sl=64 throughput 828 tuples/sec 842 tuples/sec 974.8 tuples/sec -1.7% -15.1%
bs=100 sw=10 sl=64 MB/s 0.506 MB/s 0.514 MB/s 0.595 MB/s -1.6% -15.0%
bs=100 sw=10 sl=64 p50 119,816 us 117,517 us 102,449 us +2.0% +17.0%
bs=100 sw=10 sl=64 p95 152,100 us 138,351 us 108,652 us +9.9% +40.0%
bs=100 sw=10 sl=64 p99 152,100 us 138,351 us 116,310 us +9.9% +30.8%
bs=1000 sw=10 sl=64 throughput 946 tuples/sec 940 tuples/sec 1,004 tuples/sec +0.6% -5.8%
bs=1000 sw=10 sl=64 MB/s 0.578 MB/s 0.574 MB/s 0.613 MB/s +0.7% -5.7%
bs=1000 sw=10 sl=64 p50 1,059,104 us 1,066,446 us 999,606 us -0.7% +6.0%
bs=1000 sw=10 sl=64 p95 1,104,183 us 1,106,522 us 1,046,770 us -0.2% +5.5%
bs=1000 sw=10 sl=64 p99 1,104,183 us 1,106,522 us 1,076,937 us -0.2% +2.5%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,508.16,200,128000,394,0.240,24637.34,34362.92,34362.92
1,100,10,64,20,2414.25,2000,1280000,828,0.506,119816.24,152099.96,152099.96
2,1000,10,64,20,21134.48,20000,12800000,946,0.578,1059104.45,1104183.35,1104183.35

@zyratlo
zyratlo force-pushed the migration-tool-jupyter-panel branch from 4d27799 to 6cb1ef4 Compare July 28, 2026 00:01
@github-actions github-actions Bot removed the ci changes related to CI label Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Angular workspace UI component intended to host JupyterLab inside Texera via a draggable/resizable iframe panel, wired to the existing JupyterPanelService visibility surface and accompanied by unit tests.

Changes:

  • Introduces JupyterNotebookPanelComponent (TS/HTML/SCSS) to render a draggable iframe-based Jupyter panel and delegate close/minimize actions to JupyterPanelService.
  • Adds a component-level spec file to cover visibility transitions, URL fetch/sanitization, iframe ref registration, and button delegation.
  • Registers the new component in AppModule.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.ts Implements visibility subscription, URL fetching + sanitization, and iframe ref registration for the Jupyter panel.
frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.html Adds draggable panel markup with header controls and iframe binding.
frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.scss Provides panel positioning, drag header styling, and iframe sizing styles.
frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.spec.ts Adds unit tests for visibility, URL handling, iframe ref registration, and control actions.
frontend/src/app/app.module.ts Registers the new panel component with the application module.
Comments suppressed due to low confidence (2)

frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.spec.ts:98

  • Avoid asserting on SafeResourceUrl stringification here; instead spy on DomSanitizer.bypassSecurityTrustResourceUrl and assert that the component stores the returned SafeResourceUrl when the panel becomes visible.
  it("should fetch and sanitize URL when panel becomes visible", async () => {
    vi.spyOn(component, "checkIframeRef").mockImplementation(() => {});
    mockJupyterPanelService.jupyterNotebookPanelVisible$.next(true);

    await fixture.whenStable();
    fixture.detectChanges();

    expect(mockNotebookMigrationService.getJupyterIframeURL).toHaveBeenCalled();
    expect(component.jupyterUrl.toString()).toContain("http://localhost:8888");
  });

frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.spec.ts:120

  • Asserting that the rendered iframe src contains the raw URL is brittle (DomSanitizer serialization is not stable). Just assert that the iframe exists and that the [src] binding produced a src attribute, similar to FlarumComponent tests.
  it("should render the iframe once a URL is available", async () => {
    vi.spyOn(component, "checkIframeRef").mockImplementation(() => {});
    mockJupyterPanelService.jupyterNotebookPanelVisible$.next(true);

    await fixture.whenStable();
    fixture.detectChanges();

    const iframe = fixture.nativeElement.querySelector("iframe");
    expect(iframe).not.toBeNull();
    expect(iframe.getAttribute("src")).toContain("http://localhost:8888");
  });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/app/app.module.ts

@mengw15 mengw15 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one comment

@zyratlo
zyratlo requested a review from mengw15 July 31, 2026 19:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

frontend/src/app/workspace/service/jupyter-panel/jupyter-panel.service.ts:246

  • The delete endpoint’s response is typed as DeleteNotebookResponse with a success flag/message, but deleteJupyterNotebook() clears local state unconditionally on any HTTP 200 (even if success: false). This can make the UI claim deletion succeeded while the notebook still exists on the backend. Check resp.success and only clear/hide on success; otherwise notify and keep the panel open.
    this.notebookMigrationService.deleteNotebookAndMapping(wid).subscribe({
      next: () => {
        this.hideAndClearLocalState();
        this.jupyterNotebookExists.next(false);
        this.clearHighlights();

frontend/src/app/workspace/component/jupyter-notebook-panel/jupyter-notebook-panel.component.ts:84

  • ngAfterViewInit() calls checkIframeRef() unconditionally. If the panel is already visible on component creation (BehaviorSubject emits true in ngOnInit) but the URL fetch hasn’t completed yet, the iframe is still gated by *ngIf="jupyterUrl" and checkIframeRef() will log "Jupyter Iframe reference not found." even though this is a normal transient state. Since checkIframeRef() is already triggered after a successful URL fetch, avoid calling it here.
  ngAfterViewInit(): void {
    // Ensure iframe is handled after it's available in the DOM
    this.checkIframeRef();
  }

frontend/src/app/workspace/service/jupyter-panel/jupyter-panel.service.ts:107

  • This comment says the workflow-change handler drops the outgoing workflow’s mapping, but hideAndClearLocalState() deletes the mapping for workflowActionService.getWorkflow().wid (i.e., the current/new workflow emitted by workflowMetaDataChanged()). Please update the comment to reflect the actual behavior to avoid confusion during future debugging.
        // On every workflow change, hide the panel and drop the outgoing
        // workflow's stale in-memory mapping, and clear the highlight index, so a
        // switch to a workflow without a stored notebook can't leave the previous
        // workflow's highlights active. This is local cleanup only: it must never
        // delete from the backend, or switching workflows would erase notebooks.

@mengw15 mengw15 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for addressing the comments

@mengw15
mengw15 added this pull request to the merge queue Jul 31, 2026
Merged via the queue into apache:main with commit 0f7a426 Jul 31, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend Changes related to the frontend GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Notebook Migration] Add Jupyter notebook iframe panel component

4 participants