Skip to content

[ZEPPELIN-6718] Carry every persisted field into a personalized user note - #5494

Merged
tbonelee merged 1 commit into
apache:masterfrom
tbonelee:ZEPPELIN-6718
Sep 21, 2026
Merged

tbonelee merged 1 commit into
apache:masterfrom
tbonelee:ZEPPELIN-6718

Conversation

@tbonelee

Copy link
Copy Markdown
Contributor

What is this PR for?

Reading a personalized note does not return the note itself. NotebookService.getNote hands back a per-user copy built by Note.getUserNote(user), and that copy is assembled by moving fields across one at a time, covering 5 of the 11 persisted fields.

path, defaultInterpreterGroup, version, info, noteParams and noteForms are left null. Gson omits null fields, so those keys disappear from every response for a personalized note, over REST and WebSocket alike.

path is the one that breaks the UI. It is not stored in the note file: the path comes from where the file sits and is reattached by NoteManager after load, so a copy that loses it has nowhere to recover it from. The front end reads it to detect the trash folder, note.path.split('/') throws on undefined, and the exception aborts change detection so the notebook action bar is never rendered. That includes the button that turns personalized mode back off, which means a note switched to personalized mode cannot be switched back from the UI.

The server defect has been there since getUserNote was introduced in ZEPPELIN-1594. The classic UI guarded against the missing value:

// zeppelin-web/src/app/notebook/notebook.controller.js:267
return note && note.path ? note.path.split('/')[1] === TRASH_FOLDER_ID : false;

The Angular UI carries the same check without that guard, which is what finally exposed the server side.

This PR copies the six missing fields. Because the copy is still written field by field, it also adds a regression test that walks every persisted field of Note by reflection and compares the copy against the original, so a field added to Note later and not copied here fails by name.

The front end is left alone on purpose. path is a value the server must always send; typing it as optional or guarding at the call site would let the same class of server defect pass unnoticed again, which is exactly why this one went unnoticed for so long.

What type of PR is it?

Bug Fix

Todos

  • Copy the six fields getUserNote was dropping
  • Add a reflection regression test that catches a future missing field

What is the Jira issue?

How should this be tested?

./mvnw test -pl zeppelin-server --am -Dtest=NoteTest

10 tests pass. Reverting the change to Note.java while keeping the tests makes the two new ones fail and name the cause:

[ERROR] Tests run: 10, Failures: 2, Errors: 0, Skipped: 0
[ERROR]   NoteTest.userNoteKeepsEveryPersistedField:254 getUserNote dropped Note.defaultInterpreterGroup ==> expected: <spark> but was: <null>
[ERROR]   NoteTest.userNoteKeepsThePath:233 expected: </folder/my note> but was: <null>

Checked by hand as well. Create a note, switch it to personal mode, and open it. Before the change, GET /api/notebook/{noteId} comes back without path and the console repeats the split error while the action bar never renders. After it, path is back, no errors are logged, the action bar renders, and Switch to collaboration mode completes the switch (personalizedMode: false).

Screenshots (if appropriate)

N/A

Questions:

  • Does the license files need to update? No
  • Is there breaking changes for older versions? No
  • Does this needs documentation? No

…note

Reading a personalized note returns Note.getUserNote(user) rather than the
note itself, and that copy carried only name, id, config and angularObjects.
path, defaultInterpreterGroup, version, info, noteParams and noteForms were
left null, Gson omits null fields, and so those keys vanished from every
response for a personalized note, over REST and WebSocket alike.

path is the one that breaks the UI. It is excluded from the note file and
reattached by NoteManager after load, so a copy that loses it cannot recover
it from anywhere, and the front end reads it to detect the trash folder:
note.path.split('/') throws on undefined, which aborts change detection and
leaves the action bar unrendered -- including the button that turns
personalized mode back off.

The reflection test fails on any future field added to Note and not copied
here, which is how this one was introduced.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Assisted-By: Claude <noreply@anthropic.com>

@voidmatcha voidmatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM 👍

@ParkGyeongTae ParkGyeongTae left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tested locally. Personalized notes now preserve path and other persisted fields after reload. LGTM!

@tbonelee
tbonelee merged commit bd1ecc6 into apache:master Sep 21, 2026
30 of 32 checks passed
@tbonelee

Copy link
Copy Markdown
Contributor Author

Merged into master

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.

3 participants