From 40b5f75681eabbed5fdbce1cfa835876196d188e Mon Sep 17 00:00:00 2001 From: William Stein Date: Fri, 11 Oct 2024 16:14:51 +0000 Subject: [PATCH] jupyter: global progress bar -- fix #7928 - just really easy giving bugs I recently fixed... --- .../frontend/jupyter/browser-actions.ts | 3 ++ src/packages/frontend/jupyter/status.tsx | 27 +++++++++++ src/packages/jupyter/redux/actions.ts | 46 +++++++++++++++++++ src/packages/jupyter/redux/project-actions.ts | 19 +++++--- src/packages/jupyter/redux/store.ts | 2 + 5 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/packages/frontend/jupyter/browser-actions.ts b/src/packages/frontend/jupyter/browser-actions.ts index a4e1ec22d2..14ae05ebe2 100644 --- a/src/packages/frontend/jupyter/browser-actions.ts +++ b/src/packages/frontend/jupyter/browser-actions.ts @@ -79,12 +79,15 @@ export class JupyterActions extends JupyterActions0 { // first update this.syncdb.once("change", this.updateContentsNow); + this.syncdb.once("change", this.updateRunProgress); this.syncdb.on("change", () => { // And activity indicator this.activity(); // Update table of contents this.update_contents(); + // run progress + this.updateRunProgress(); }); // Load kernel (once ipynb file loads). diff --git a/src/packages/frontend/jupyter/status.tsx b/src/packages/frontend/jupyter/status.tsx index c1d60b89c7..a3522b9a39 100644 --- a/src/packages/frontend/jupyter/status.tsx +++ b/src/packages/frontend/jupyter/status.tsx @@ -100,6 +100,7 @@ export function Kernel({ // no redux_kernel or empty string (!) means there is no kernel const kernel: string | null = !redux_kernel ? null : redux_kernel; const kernels: undefined | immutable.List = useRedux([name, "kernels"]); + const runProgress = useRedux([name, "runProgress"]); const project_id: string = useRedux([name, "project_id"]); const kernel_info: undefined | immutable.Map = useRedux([ name, @@ -505,6 +506,7 @@ export function Kernel({ const style: CSS = { display: "flex", borderLeft: `1px solid ${COLORS.GRAY}`, + cursor: "pointer", }; const pstyle: CSS = { margin: "2px", @@ -546,6 +548,31 @@ export function Kernel({ return (
+ {runProgress != null && ( + + Percent of code cells that have been run since the last + kernel restart. + + } + > +
+ {is_fullscreen ? ( + Code + ) : ( + "" + )} + +
+
+ )}
{is_fullscreen ? CPU : ""} { const obj: any = { trust: !!record.get("trust"), // case to boolean backend_state: record.get("backend_state"), + last_backend_state: record.get("last_backend_state"), kernel_state: record.get("kernel_state"), metadata: record.get("metadata"), // extra custom user-specified metadata max_output_length: bounded_integer( @@ -763,6 +764,7 @@ export abstract class JupyterActions extends Actions { const obj: any = { trust: !!record.get("trust"), // case to boolean backend_state: record.get("backend_state"), + last_backend_state: record.get("last_backend_state"), kernel_state: record.get("kernel_state"), kernel_error: record.get("kernel_error"), metadata: record.get("metadata"), // extra custom user-specified metadata @@ -2794,6 +2796,50 @@ export abstract class JupyterActions extends Actions { return value; }; + + // Update run progress, which is a number between 0 and 100, + // giving the number of runnable cells that have been run since + // the kernel was last set to the running state. + // Currently only run in the browser, but could maybe be useful + // elsewhere someday. + updateRunProgress = () => { + if (this.store.get("backend_state") != "running") { + this.setState({ runProgress: 0 }); + return; + } + const cells = this.store.get("cells"); + if (cells == null) { + return; + } + const last = this.store.get("last_backend_state"); + if (last == null) { + // not supported yet, e.g., old backend, kernel never started + return; + } + // count of number of cells that are runnable and + // have start greater than last, and end set... + // count a currently running cell as 0.5. + let total = 0; + let ran = 0; + for (const [_, cell] of cells) { + if ( + cell.get("cell_type", "code") != "code" || + !cell.get("input")?.trim() + ) { + // not runnable + continue; + } + total += 1; + if ((cell.get("start") ?? 0) >= last) { + if (cell.get("end")) { + ran += 1; + } else { + ran += 0.5; + } + } + } + this.setState({ runProgress: total > 0 ? (100 * ran) / total : 100 }); + }; } function extractLabel(content: string): string { diff --git a/src/packages/jupyter/redux/project-actions.ts b/src/packages/jupyter/redux/project-actions.ts index 2c699a40ec..275b18cb43 100644 --- a/src/packages/jupyter/redux/project-actions.ts +++ b/src/packages/jupyter/redux/project-actions.ts @@ -91,7 +91,7 @@ export class JupyterActions extends JupyterActions0 { /|\ | |-----------------------------------------| - Going from ready to starting happens when a code execution is requested. + Going from ready to starting happens first when a code execution is requested. */ // Check just in case Typescript doesn't catch something: @@ -111,11 +111,18 @@ export class JupyterActions extends JupyterActions0 { this._backend_state = backend_state; if (this.isCellRunner()) { - this._set({ - type: "settings", - backend_state, - }); - this.save_asap(); + const stored_backend_state = this.syncdb + .get_one({ type: "settings" }) + ?.get("backend_state"); + + if (stored_backend_state != backend_state) { + this._set({ + type: "settings", + backend_state, + last_backend_state: Date.now(), + }); + this.save_asap(); + } // The following is to clear kernel_error if things are working only. if (backend_state == "running") { diff --git a/src/packages/jupyter/redux/store.ts b/src/packages/jupyter/redux/store.ts index 2e6331575e..82525e3f03 100644 --- a/src/packages/jupyter/redux/store.ts +++ b/src/packages/jupyter/redux/store.ts @@ -103,6 +103,8 @@ export interface JupyterStoreState { // computeServerId -- gets optionally set on the frontend (useful for react) computeServerId?: number; requestedComputeServerId?: number; + // run progress = Percent (0-100) of runnable cells that have been run since the last kernel restart. (Thus markdown and empty cells are excluded.) + runProgress?: number; } export const initial_jupyter_store_state: {