Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear state when switching away, add blank state indicator #178

Merged
merged 7 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions jupyter_resource_usage/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
try:
import ipykernel

USAGE_IS_SUPPORTED = version.parse("6.9.0") <= version.parse(ipykernel.__version__)
IPYKERNEL_VERSION = ipykernel.__version__
USAGE_IS_SUPPORTED = version.parse("6.9.0") <= version.parse(IPYKERNEL_VERSION)
except ImportError:
USAGE_IS_SUPPORTED = False
IPYKERNEL_VERSION = None


class ApiHandler(APIHandler):
Expand Down Expand Up @@ -92,7 +94,16 @@ class KernelUsageHandler(APIHandler):
@web.authenticated
async def get(self, matched_part=None, *args, **kwargs):
if not USAGE_IS_SUPPORTED:
self.write(json.dumps({}))
self.write(
json.dumps(
{
"content": {
"reason": "not_supported",
"kernel_version": IPYKERNEL_VERSION,
}
}
)
)
return

kernel_id = matched_part
Expand All @@ -108,15 +119,23 @@ async def get(self, matched_part=None, *args, **kwargs):
poller = zmq.asyncio.Poller()
control_socket = control_channel.socket
poller.register(control_socket, zmq.POLLIN)
# previous behavior was 3 retries: 1 + 2 + 3 = 6 seconds
timeout_ms = 6_000
timeout_ms = 10_000
events = dict(await poller.poll(timeout_ms))
if control_socket not in events:
self.write(json.dumps({}))
self.write(
json.dumps(
{
"content": {"reason": "timeout", "timeout_ms": timeout_ms},
"kernel_id": kernel_id,
}
)
)
else:
res = client.control_channel.get_msg(timeout=0)
if isawaitable(res):
# control_channel.get_msg may return a Future,
# depending on configured KernelManager class
res = await res
if res:
res["kernel_id"] = kernel_id
self.write(json.dumps(res, default=date_default))
1 change: 1 addition & 0 deletions packages/labextension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"dependencies": {
"@jupyterlab/application": "^3.5.1",
"@jupyterlab/apputils": "^3.5.1",
"@jupyterlab/console": "^3.5.1",
"@jupyterlab/coreutils": "^5.5.1",
"@jupyterlab/notebook": "^3.5.1",
"@jupyterlab/services": "^6.5.1",
Expand Down
20 changes: 15 additions & 5 deletions packages/labextension/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import { INotebookTracker } from '@jupyterlab/notebook';
import { LabIcon } from '@jupyterlab/ui-components';
import { ICommandPalette } from '@jupyterlab/apputils';
import { IConsoleTracker } from '@jupyterlab/console';
import { KernelUsagePanel } from './panel';
import tachometer from '../style/tachometer.svg';

Expand All @@ -13,6 +15,7 @@ import { IStatusBar } from '@jupyterlab/statusbar';
import { ITranslator } from '@jupyterlab/translation';

import { MemoryUsage } from './memoryUsage';
import { KernelWidgetTracker } from './tracker';

namespace CommandIDs {
export const getKernelUsage = 'kernel-usage:get';
Expand Down Expand Up @@ -46,13 +49,15 @@ const memoryStatusPlugin: JupyterFrontEndPlugin<void> = {
const kernelUsagePlugin: JupyterFrontEndPlugin<void> = {
id: '@jupyter-server/resource-usage:kernel-panel-item',
autoStart: true,
optional: [ICommandPalette],
optional: [ICommandPalette, ILabShell, IConsoleTracker],
requires: [ITranslator, INotebookTracker],
activate: (
app: JupyterFrontEnd,
translator: ITranslator,
notebookTracker: INotebookTracker,
palette: ICommandPalette | null
palette: ICommandPalette | null,
labShell: ILabShell | null,
consoleTracker: IConsoleTracker | null
) => {
const trans = translator.load('jupyter-resource-usage');

Expand All @@ -62,10 +67,15 @@ const kernelUsagePlugin: JupyterFrontEndPlugin<void> = {
let panel: KernelUsagePanel | null = null;

function createPanel() {
if ((!panel || panel.isDisposed) && notebookTracker) {
if (!panel || panel.isDisposed) {
const tracker = new KernelWidgetTracker({
notebookTracker,
labShell,
consoleTracker,
});

panel = new KernelUsagePanel({
widgetAdded: notebookTracker.widgetAdded,
currentNotebookChanged: notebookTracker.currentChanged,
currentChanged: tracker.currentChanged,
trans: trans,
});
shell.add(panel, 'right', { rank: 200 });
Expand Down
11 changes: 6 additions & 5 deletions packages/labextension/src/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { ISignal } from '@lumino/signaling';
import { TranslationBundle } from '@jupyterlab/translation';
import { StackedPanel } from '@lumino/widgets';
import { LabIcon } from '@jupyterlab/ui-components';
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
import { KernelUsageWidget } from './widget';
import { IWidgetWithSession } from './types';
import { KernelWidgetTracker } from './tracker';

import tachometer from '../style/tachometer.svg';

const PANEL_CLASS = 'jp-KernelUsage-view';

export class KernelUsagePanel extends StackedPanel {
constructor(props: {
widgetAdded: ISignal<INotebookTracker, NotebookPanel | null>;
currentNotebookChanged: ISignal<INotebookTracker, NotebookPanel | null>;
currentChanged: ISignal<KernelWidgetTracker, IWidgetWithSession | null>;
trans: TranslationBundle;
}) {
super();
Expand All @@ -24,9 +25,9 @@ export class KernelUsagePanel extends StackedPanel {
svgstr: tachometer,
});
this.title.closable = true;

const widget = new KernelUsageWidget({
widgetAdded: props.widgetAdded,
currentNotebookChanged: props.currentNotebookChanged,
currentChanged: props.currentChanged,
panel: this,
trans: props.trans,
});
Expand Down
62 changes: 62 additions & 0 deletions packages/labextension/src/tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ILabShell } from '@jupyterlab/application';
import { INotebookTracker } from '@jupyterlab/notebook';
import { IConsoleTracker } from '@jupyterlab/console';
import { ISignal, Signal } from '@lumino/signaling';
import { IWidgetWithSession, hasKernelSession } from './types';

/**
* Tracks widgets with kernels as well as possible given the available tokens.
*/
export class KernelWidgetTracker {
constructor(options: KernelWidgetTracker.IOptions) {
const { labShell, notebookTracker, consoleTracker } = options;
this._currentChanged = new Signal(this);
if (labShell) {
labShell.currentChanged.connect((_, update) => {
const widget = update.newValue;
if (widget && hasKernelSession(widget)) {
this._currentChanged.emit(widget);
} else {
this._currentChanged.emit(null);
}
});
} else {
notebookTracker.currentChanged.connect((_, widget) => {
this._currentChanged.emit(widget);
});
if (consoleTracker) {
consoleTracker.currentChanged.connect((_, widget) => {
this._currentChanged.emit(widget);
});
}
}
}

/**
* Emits on any change of active widget. The value is a known widget with
* kernel session or null if user switched to a widget which does not support
* kernel sessions.
*/
get currentChanged(): ISignal<
KernelWidgetTracker,
IWidgetWithSession | null
> {
return this._currentChanged;
}

private _currentChanged: Signal<
KernelWidgetTracker,
IWidgetWithSession | null
>;
}

/**
* Namespace for kernel widget tracker.
*/
export namespace KernelWidgetTracker {
export interface IOptions {
notebookTracker: INotebookTracker;
labShell: ILabShell | null;
consoleTracker: IConsoleTracker | null;
}
}
26 changes: 26 additions & 0 deletions packages/labextension/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Widget } from '@lumino/widgets';
import { ISessionContext } from '@jupyterlab/apputils';
import { NotebookPanel } from '@jupyterlab/notebook';
import { ConsolePanel } from '@jupyterlab/console';

/**
* Interface used to abstract away widgets with kernel to avoid introducing
* spurious dependencies on properties specific to e.g. notebook or console.
*/
export interface IWidgetWithSession extends Widget {
/**
* Session context providing kernel access.
*/
sessionContext: ISessionContext;
}

/**
* Check if given widget is one of the widgets known to have kernel session.
*
* Note: we could switch to duck-typing in future.
*/
export function hasKernelSession(
widget: Widget
): widget is ConsolePanel | NotebookPanel {
return widget instanceof ConsolePanel || widget instanceof NotebookPanel;
}
Loading