Skip to content
Merged
31 changes: 31 additions & 0 deletions notebook-migration-service/src/main/resources/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
103 changes: 103 additions & 0 deletions notebook-migration-service/src/main/resources/custom.js
Original file line number Diff line number Diff line change
@@ -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
);
Comment thread
mengw15 marked this conversation as resolved.
});
Comment thread
mengw15 marked this conversation as resolved.
});
});

// 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);
44 changes: 44 additions & 0 deletions notebook-migration-service/src/main/resources/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
Comment thread
mengw15 marked this conversation as resolved.
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
Original file line number Diff line number Diff line change
@@ -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
Loading