This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new b2d216a2ad feat(python-notebook-migration): add JupyterLab docker for 
notebook migration tool (#5256)
b2d216a2ad is described below

commit b2d216a2ad728454d354a45de4ea5de1e391f81f
Author: Ryan Zhang <[email protected]>
AuthorDate: Tue Jun 16 16:58:59 2026 -0700

    feat(python-notebook-migration): add JupyterLab docker for notebook 
migration tool (#5256)
    
    ### What changes were proposed in this PR?
    Introduces the local JupyterLab docker that the upcoming
    notebook-migration microservice will talk to. Three files are added
    under `notebook-migration-service/src/main/resources/`:
    
    - **`Dockerfile`** — `FROM jupyter/base-notebook:notebook-6.5.4`;
    `COPY`s `custom.js` into `/home/jovyan/.jupyter/custom/custom.js` and
    fixes ownership.
    - **`docker-compose.yml`** — runs JupyterLab as `texera-jupyter` on host
    port `9100`. Token/password auth disabled, XSRF check disabled, CSP set
    to allow `frame-ancestors http://localhost:*` so Texera can embed it in
    an iframe. Default URL is
       `/tree`.
    - **`custom.js`** — JupyterLab iframe customization. Posts `cellClicked`
    messages (with cell UUID) to `window.parent` and listens for
    `triggerCellClick` to scroll/highlight target cells.
    
    
    ### Any related issues, documentation, discussions?
    Closes #5255
    Parent-issue #4301
    
    
    ### How was this PR tested?
    Verified locally that the stack comes up cleanly and Jupyter is
    reachable.
    
    
    ### Was this PR authored or co-authored using generative AI tooling?
    Generated-by: Claude Code (Claude Opus 4.7)
---
 .../src/main/resources/Dockerfile                  |  31 +++++++
 .../src/main/resources/custom.js                   | 103 +++++++++++++++++++++
 .../src/main/resources/docker-compose.yml          |  44 +++++++++
 .../src/main/resources/start-texera-jupyter.sh     |  38 ++++++++
 4 files changed, 216 insertions(+)

diff --git a/notebook-migration-service/src/main/resources/Dockerfile 
b/notebook-migration-service/src/main/resources/Dockerfile
new file mode 100644
index 0000000000..699b271943
--- /dev/null
+++ b/notebook-migration-service/src/main/resources/Dockerfile
@@ -0,0 +1,31 @@
+# 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.
+
+FROM jupyter/base-notebook:notebook-6.5.4
+
+# Copy custom JavaScript for Jupyter and the startup script
+COPY custom.js /home/jovyan/.jupyter/custom/custom.js
+COPY start-texera-jupyter.sh /usr/local/bin/start-texera-jupyter.sh
+
+# Ensure correct permissions. custom.js must stay writable by jovyan so the
+# startup script can substitute the origin placeholder at runtime.
+USER root
+RUN mkdir -p /home/jovyan/.jupyter/custom && \
+    chown -R jovyan:users /home/jovyan/.jupyter && \
+    chmod +x /usr/local/bin/start-texera-jupyter.sh
+
+USER jovyan
diff --git a/notebook-migration-service/src/main/resources/custom.js 
b/notebook-migration-service/src/main/resources/custom.js
new file mode 100644
index 0000000000..70fc4ce931
--- /dev/null
+++ b/notebook-migration-service/src/main/resources/custom.js
@@ -0,0 +1,103 @@
+/**
+ * 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 Texera app origin. The "__TEXERA_ORIGIN__" placeholder is substituted at
+// container startup by start-texera-jupyter.sh from the TEXERA_ORIGIN env var
+// (defaults to http://localhost:4200), so deployments under a real hostname 
work
+// without editing this file.
+const TEXERA_ORIGIN = "__TEXERA_ORIGIN__";
+
+// Use Jupyter's event system to ensure the notebook is fully loaded
+require(["base/js/events"], function (events) {
+  events.on("kernel_ready.Kernel", function () {
+
+    // Attach click event listener to cells. kernel_ready.Kernel fires on every
+    // kernel (re)start, so remove any previously bound handler first to avoid
+    // stacking duplicate listeners that would post N messages per click.
+    $("#notebook-container").off("click", ".cell").on("click", ".cell", 
function (event) {
+      const cell = $(this);
+      const index = $(".cell").index(cell);
+      const cellContent = cell.find(".input_area").text();
+
+      // Get the UUID from the cell's metadata, or use "N/A" if it doesn't 
exist
+      const cellUUID = Jupyter.notebook.get_cell(index).metadata.uuid || 'N/A';
+
+      // Send a message to the parent window (Texera app)
+      window.parent.postMessage(
+        { action: "cellClicked", cellIndex: index, cellContent: cellContent, 
cellUUID: cellUUID },
+        TEXERA_ORIGIN
+      );
+    });
+  });
+});
+
+// Listen for messages from the Texera app (or parent window)
+window.addEventListener("message", function (event) {
+  // Verify the message origin
+  if (event.origin !== TEXERA_ORIGIN) {
+    console.warn("Message received from unrecognized origin:", event.origin);
+    return;
+  }
+
+  if (event.data.action === "triggerCellClick") {
+    const operatorCellUUIDs = event.data.operators || [];
+
+    if (!operatorCellUUIDs.length) {
+      console.error("No valid operator UUIDs provided in the message.");
+      return; // Exit if no UUIDs are provided
+    }
+
+    operatorCellUUIDs.forEach((cellUUID) => {
+      // Search for the cell by UUID
+      const allCells = Jupyter.notebook.get_cells();
+      const targetCell = allCells.find((cell) => cell.metadata.uuid === 
cellUUID);
+
+      if (targetCell) {
+        const cellIndex = Jupyter.notebook.find_cell_index(targetCell);
+
+        // Scroll to and highlight the cell
+        let cell = document.querySelectorAll(".cell")[cellIndex];
+        if (cell) {
+          cell.scrollIntoView({ behavior: 'smooth', block: 'center' });
+          cell.classList.add("highlighted");
+
+          // Remove the highlight after 3 seconds
+          setTimeout(() => {
+            cell.classList.remove("highlighted");
+          }, 3000);
+        } else {
+          console.error(`Cell not found in the DOM for index ${cellIndex}.`);
+        }
+      } else {
+        console.error(`No cell found with UUID: ${cellUUID}`);
+      }
+    });
+  } else {
+    console.warn("Received unknown action:", event.data.action);
+  }
+}, false);
+
+// Add custom CSS for highlighted cells
+const style = document.createElement('style');
+style.innerHTML = `
+  .cell.highlighted {
+    background-color: lightyellow;
+  }
+`;
+document.head.appendChild(style);
diff --git a/notebook-migration-service/src/main/resources/docker-compose.yml 
b/notebook-migration-service/src/main/resources/docker-compose.yml
new file mode 100644
index 0000000000..d442a57386
--- /dev/null
+++ b/notebook-migration-service/src/main/resources/docker-compose.yml
@@ -0,0 +1,44 @@
+# 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.
+
+name: texera-jupyter
+services:
+
+  jupyter:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    container_name: texera-jupyter
+    restart: unless-stopped
+    ports:
+      - "9100:8888"
+    environment:
+      # Texera app origin, used for the iframe CSP frame-ancestors and the
+      # postMessage origin checks in custom.js. Override for non-local 
deployments.
+      - TEXERA_ORIGIN=http://localhost:4200
+      # Weak default token so the server is not fully open. The Texera-side 
iframe
+      # URL must pass this through ?token=<value>.
+      - JUPYTER_TOKEN=texera
+    command: ["start-texera-jupyter.sh"]
+    healthcheck:
+      # /api returns the server version without requiring the token, so it is a
+      # reliable liveness probe even with auth enabled.
+      test: ["CMD", "python", "-c", "import urllib.request; 
urllib.request.urlopen('http://localhost:8888/api')"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 15s
diff --git 
a/notebook-migration-service/src/main/resources/start-texera-jupyter.sh 
b/notebook-migration-service/src/main/resources/start-texera-jupyter.sh
new file mode 100644
index 0000000000..2bfb5a3baf
--- /dev/null
+++ b/notebook-migration-service/src/main/resources/start-texera-jupyter.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# 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.
+
+set -euo pipefail
+
+# Texera app origin used by custom.js (postMessage targetOrigin + inbound 
origin
+# check) and by the iframe CSP frame-ancestors. Override TEXERA_ORIGIN for
+# deployments under a real hostname; defaults to the local dev origin.
+TEXERA_ORIGIN="${TEXERA_ORIGIN:-http://localhost:4200}";
+
+# Weak default token so the server is not fully open to anyone reachable on the
+# published port. The Texera-side iframe URL must pass this through 
?token=<value>.
+JUPYTER_TOKEN="${JUPYTER_TOKEN:-texera}"
+
+# Substitute the origin placeholder in custom.js before the server starts 
serving it.
+sed -i "s|__TEXERA_ORIGIN__|${TEXERA_ORIGIN}|g" 
/home/jovyan/.jupyter/custom/custom.js
+
+exec start-notebook.sh \
+  --NotebookApp.token="${JUPYTER_TOKEN}" \
+  --NotebookApp.password='' \
+  --NotebookApp.disable_check_xsrf=True \
+  --NotebookApp.tornado_settings="{'headers': {'Content-Security-Policy': 
'frame-ancestors ${TEXERA_ORIGIN}'}}" \
+  --NotebookApp.default_url=/tree

Reply via email to