This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6945-3ea2346ccdf3b25041cbb23baa6e87fe68c37f4b in repository https://gitbox.apache.org/repos/asf/texera.git
commit ee0a01e8fec19fbe6fd405e011213faa72d7280a Author: Ryan Zhang <[email protected]> AuthorDate: Mon Jul 27 14:55:44 2026 -0700 feat(python-notebook-migration): make embedded jupyter notebook read-only (#6945) ### What changes were proposed in this PR? The notebook migration service generates a Jupyter notebook and embeds it in the Texera app as a reference view of the migrated workflow. It is meant for reading, not editing. This PR configures the embedded Jupyter container so the notebook renders as a clean, read-only view. Changes, all scoped to the notebook migration service container assets: 1. `custom.css` (new): hides the classic Notebook chrome (header with logo and logout, kernel and trust indicators, the File/Edit/View menu, and the toolbar), and fixes a second, non-functional scrollbar by making `#site` the single scroll container. 2. `Dockerfile`: copies `custom.css` into `/home/jovyan/.jupyter/custom/custom.css`, next to the existing `custom.js`. 3. `custom.js`: read-only hardening. Sets every code cell's CodeMirror editor to `readOnly: "nocursor"`, calls `keyboard_manager.disable()` so cells cannot be edited or executed, and overrides `MarkdownCell.prototype.unrender` so markdown cells stay rendered. The existing click-to-highlight postMessage integration is unchanged. This does not touch the Texera frontend or backend. ### UI Comparison Before this PR: <img width="787" height="684" alt="2026-07-27_13-38-52" src="https://github.com/user-attachments/assets/65cc08bd-ffcf-4343-b245-344830b6b59c" /> After this PR: <img width="787" height="684" alt="image" src="https://github.com/user-attachments/assets/41440d7d-af4e-4212-993b-0c4b1a05ea2f" /> ### Any related issues, documentation, discussions? Closes #6943 Parent issue #4301 ### How was this PR tested? Tested manually by building the container and opening the embedded notebook in the Texera app: 1. The classic Notebook chrome (header, menu, toolbar, kernel and trust indicators) is hidden; only the notebook cells are visible. 2. Clicking into a code cell does not put it in edit mode, typing does nothing, and cells cannot be run. 3. Double-clicking a markdown cell keeps it rendered instead of dropping into its editable source. 4. Only one working scrollbar is present. No automated tests were added: the change is static container assets (CSS and client-side JS in the classic Notebook UI) with no code path exercisable by the existing unit test suites. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 4.8) --- .../src/main/resources/Dockerfile | 3 +- .../src/main/resources/custom.css | 47 ++++++++++++++++++++++ .../src/main/resources/custom.js | 33 +++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/notebook-migration-service/src/main/resources/Dockerfile b/notebook-migration-service/src/main/resources/Dockerfile index 699b271943..bea79793f2 100644 --- a/notebook-migration-service/src/main/resources/Dockerfile +++ b/notebook-migration-service/src/main/resources/Dockerfile @@ -17,8 +17,9 @@ FROM jupyter/base-notebook:notebook-6.5.4 -# Copy custom JavaScript for Jupyter and the startup script +# Copy custom JavaScript/CSS for Jupyter and the startup script COPY custom.js /home/jovyan/.jupyter/custom/custom.js +COPY custom.css /home/jovyan/.jupyter/custom/custom.css COPY start-texera-jupyter.sh /usr/local/bin/start-texera-jupyter.sh # Ensure correct permissions. custom.js must stay writable by jovyan so the diff --git a/notebook-migration-service/src/main/resources/custom.css b/notebook-migration-service/src/main/resources/custom.css new file mode 100644 index 0000000000..5e20e01eb2 --- /dev/null +++ b/notebook-migration-service/src/main/resources/custom.css @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * The notebook is embedded in the Texera app as a read-only view of the + * generated workflow. Hide the classic Notebook chrome (logo/name/logout, + * kernel and trust indicators, the File/Edit/View menu, and the toolbar) so + * only the notebook cells remain. + */ +#header, +#menubar-container, +#maintoolbar-container { + display: none !important; +} + +/* Classic Notebook scrolls inside #site, but the document also scrolls, which + shows a second, non-functional scrollbar. Stop the document from scrolling so + only #site's scrollbar remains. */ +html, +body { + height: 100% !important; + overflow: hidden !important; +} + +/* The notebook offsets #site by the (now-hidden) header height; make it fill the + iframe (its own overflow provides the single, working scrollbar). */ +#site { + top: 0 !important; + height: 100vh !important; + overflow-y: auto !important; +} diff --git a/notebook-migration-service/src/main/resources/custom.js b/notebook-migration-service/src/main/resources/custom.js index 70fc4ce931..6bee424368 100644 --- a/notebook-migration-service/src/main/resources/custom.js +++ b/notebook-migration-service/src/main/resources/custom.js @@ -47,6 +47,39 @@ require(["base/js/events"], function (events) { }); }); +// Read-only viewing: the embedded notebook is a reference view of the generated +// workflow, not an editing surface. Make every code cell's editor read-only and +// disable Jupyter's keyboard shortcuts so cells cannot be edited or executed. +// (The chrome hidden via custom.css already removes the run/save/menu controls.) +require(["base/js/events"], function (events) { + function makeReadOnly() { + if (!window.Jupyter || !Jupyter.notebook) { + return; + } + // "nocursor" also blocks focus, so a click can't put a cell into edit mode. + Jupyter.notebook.get_cells().forEach(function (cell) { + if (cell.code_mirror) { + cell.code_mirror.setOption("readOnly", "nocursor"); + } + }); + if (Jupyter.keyboard_manager) { + Jupyter.keyboard_manager.disable(); + } + } + + // notebook_loaded fires once cells (and their editors) exist; kernel_ready + // fires on every (re)start and re-applies in case the manager was re-enabled. + events.on("notebook_loaded.Notebook", makeReadOnly); + events.on("kernel_ready.Kernel", makeReadOnly); +}); + +// Keep markdown cells rendered: overriding unrender() stops a double-click (or +// Enter) from dropping a markdown cell into its editable source view. The +// prototype override applies to all existing and future markdown cells. +require(["notebook/js/textcell"], function (textcell) { + textcell.MarkdownCell.prototype.unrender = function () {}; +}); + // Listen for messages from the Texera app (or parent window) window.addEventListener("message", function (event) { // Verify the message origin
