Skip to content

[ZEPPELIN-6727] Mount the React notebook on the existing URL with entry-time Angular fallback - #5608

Open
xhaktm00 wants to merge 5 commits into
apache:masterfrom
xhaktm00:ZEPPELIN-6727
Open

xhaktm00 wants to merge 5 commits into
apache:masterfrom
xhaktm00:ZEPPELIN-6727

Conversation

@xhaktm00

@xhaktm00 xhaktm00 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

What is this PR for?

Mount the React notebook read skeleton on the existing notebook URL, with an entry-time fallback to the ZEPPELIN-6737 Angular read view on the same host.

  • Opt-in for development and testing: ?notebookCoreRead&notebookCoreReact, also under /revision/<revisionId>
  • Without notebookCoreReact: the private route shows the ZEPPELIN-6737 view as before
  • Without notebookCoreRead: the editable notebook route is unchanged
  • The user-facing ?react path is not published

Typed read port

  • NotebookReadPort (zeppelin-notebook-core): a NotebookPort whose snapshot is NotebookReadSnapshot
  • NotebookCoreReadHost.port now has this type. It is the same frozen object and still fits wherever a NotebookPort is expected
  • NotebookReadProvider passes that exact object to NotebookCoreProvider (ZEPPELIN-6726) and exposes it to useNotebookReadSnapshot(), so the remote reads the read state without a cast
  • The import boundary check allows the two new types, not the read store behind them

React skeleton (./NotebookReadSkeleton)

  • Shows the read status (loading, ready, not-found, access-denied, failed), note id, revision id and paragraph anchor
  • Renders no paragraphs, results or charts
  • Calls onMounted once after its first commit. This means the mount completed, not that the notebook is ready
  • Errors go to the existing ReactErrorBoundary and onError
  • Test-only failure: 'mount' | 'after-mount' throws during the first render, or on the first render after the mount completed

Entry and fallback (NotebookCoreEntryComponent)

  • Takes the private route's place behind the existing canMatch guard (ZEPPELIN-6737)
  • Owns one NotebookCoreReadHost for the route lifetime: opens the note or revision from the route parameters and disposes the host once
  • NotebookCoreReadComponent now receives that host as a required input and no longer opens or disposes it
  • Chooses the renderer once, on entry, and shows the Angular read view or the React mount (ReactMountDirective with the shared loader and timeout), never both
  • Passes the remote the host port, the paragraph query parameter, the failure value, onMounted and onError. The React view does not read the URL itself
data-renderer Meaning
angular No notebookCoreReact: the Angular read view
react-loading The remote is loading or has not committed its first render
react-mounted The React view committed its first render
angular-fallback The remote did not load (failed request, 404, loader timeout) or its first render threw. The React mount is torn down and the Angular read view takes over on the same host and port, without reading the note again
runtime-error The React view failed after its first render. The error is only reported; the entry does not switch
  • runtime-error leaves the view empty on purpose: the issue rules out an automatic switch after the mount
  • The fallback keeps paragraph in the URL but does not scroll to it, because the ZEPPELIN-6737 view has no anchor handling
  • No renderer sends run, save or other write messages. The E2E asserts that only reads are sent
  • Dropping notebookCoreRead destroys the entry, which disposes the host and its subscriptions, and the editable notebook loads the note itself. This is not the same-port fallback

What type of PR is it?

Feature

Todos

  • - Type the host read port as NotebookReadPort and read it in React through NotebookReadProvider and useNotebookReadSnapshot
  • - Add the ./NotebookReadSkeleton remote with a single mount report and test-only failure injection
  • - Mount it from an entry that owns the read host, behind a private notebookCoreReact flag
  • - Fall back to the ZEPPELIN-6737 Angular read view on the same host before the first commit, and only report a failure after it
  • - Cover normal entry, note and revision changes, access denial, remote failure, 404 and timeout, mount and after-mount failures, the anchor and leaving private mode in real-route E2E
  • - Document the private React read view in the core-contract README

What is the Jira issue?

https://issues.apache.org/jira/browse/ZEPPELIN-6727

How should this be tested?

From zeppelin-web-angular:

npm run test:shell          # 342 passed
npm run test:react          # 393 passed
npm run test:notebook-core  # 167 passed
npm run build:notebook-core-port-proof && npm run test:notebook-route-boundary
npm run e2e:core-contract   # 13 passed
npm run build:projects && npm run build:angular && npm run build:react
  • The skeleton spec runs the actual read store through the production React adapter
  • Mutation testing: each of 25 breaks of the skeleton, the read adapter and the entry failed at least one unit spec. Both E2E breaks of the fallback decision (no fallback before the first commit, a fallback on every error) failed the matching E2E tests

Real-route Playwright (e2e/tests/notebook/core-read/notebook-core-entry.spec.ts, 13 tests; Chromium, one worker, local server with Git notebook storage):

  • Anonymous: 11 passed, 2 access-denied tests skipped (they need two Shiro accounts)
  • Authenticated (conf/shiro.ini.template with admin added to user1, as in CI): 13 passed, and 39 passed over three repeats
  • Existing notebook-core-read.spec.ts: 7 passed and 1 skipped anonymous, 8 passed authenticated
  • Remote failures route remoteEntry.js to an abort, a 404 or a request that never answers. Each fallback test waits for that request, so the Angular view is a fallback rather than the no-flag view

Not run locally:

  • read-fixtures.spec.ts (@live, needs a capture server)
  • Firefox and WebKit
  • The full E2E suite

Revision tests on CI:

  • The two new revision tests and the existing ZEPPELIN-6737 one skip on the main CI leg (VFSNotebookRepo). The Git storage step runs only revision-isolation.spec.ts
  • Locally they pass on Git storage with one worker. With parallel workers they can fail on the notebook/.git/index lock, the ZEPPELIN-6737 test as well
  • Should they be added to the Git storage step, here or in a follow-up?

Screenshots (if appropriate)

Questions:

  • Does the license files need to update? No
  • Is there breaking changes for older versions? No
  • Does this needs documentation? The private React read view is documented in the core-contract README.

NotebookReadPort is a NotebookPort whose snapshot is NotebookReadSnapshot.
The private read host now declares its port with it, so a remote can read
the read status and note without a cast while it keeps the same port object.
@miinhho

miinhho commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

I reproduced the serial/parallel difference described in the PR using a Linux Java 11 backend with GitNotebookRepo and Chromium. I ran the two new revision scenarios and the existing Core read revision scenario three times each, with retries disabled:

  • 1 worker: all 9 runs passed.
  • 5 workers: 5 passed and 4 failed.

Three failures occurred during checkpoint creation (HTTP 500), with corresponding server logs showing a LockFailedException on .git/index.

Adding these scenarios to CI may involve more than extending the test selection—for example, a dedicated frontend CI job and server/Git repository isolation between parallel workers. Given that scope, I think this would be better addressed in a follow-up.

};

export const NotebookReadSkeleton = ({ core, onError, ...props }: NotebookReadSkeletonProps) => (
<ReactErrorBoundary onError={onError}>

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.

I confirmed that the view does not recover when navigating to another note after a post-mount error.

With note A displaying normally, I injected an error using the existing test-only failure prop, then cleared that prop. Navigating to note B within the same entry successfully fetched B and brought the host to ready, but the view remained empty. Leaving for the home page and reopening B restored the view.

Since this boundary receives no resetKey, it retains the previous error state across note changes. Even after the error condition is removed, opening another note does not recover the view; a fresh entry is required.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants