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

Configurable default layout #248

Merged
merged 13 commits into from
Oct 29, 2022
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ repos:
files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
types: [file]
additional_dependencies:
- '@typescript-eslint/eslint-plugin@2.27.0'
- '@typescript-eslint/parser@2.27.0'
- '@typescript-eslint/eslint-plugin@4.18.0'
- '@typescript-eslint/parser@4.18.0'
- eslint@^6.0.0
- eslint-config-prettier@6.10.1
- eslint-plugin-prettier@3.1.4
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@jupyterlab/codeeditor": "^3.0.0",
"@jupyterlab/console": "^3.0.0",
"@jupyterlab/coreutils": "^5.0.0",
"@jupyterlab/docregistry": "^3.4.3",
"@jupyterlab/mainmenu": "^3.0.0",
"@jupyterlab/nbformat": "^3.0.0",
"@jupyterlab/notebook": "^3.0.0",
Expand All @@ -75,8 +76,8 @@
"@jupyterlab/builder": "^3.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.4",
Expand Down
41 changes: 39 additions & 2 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,46 @@
},
"browserDashboardCheck": {
"type": "boolean",
"title": "Determine whether to validate dashboards via browser check.",
"description": "If set to true, the test dashboard will be validated within the browser environment. This is useful for testing the dashboard when behind a browser-cookie based authentication.",
"title": "Check dashboards via browser.",
"description": "If set to true, the extension will check for the Dask dashboard from the user's browser. This is useful for testing the dashboard when behind a browser-cookie based authentication.",
"default": false
},
"defaultLayout": {
"type": "object",
"title": "Default layout for Dask Dashboard panels",
"description": "This allows you to store a default layout for your Dask dashboard panels. It is stored as an object keyed by the individual chart names. Each value in the object is an object storing `mode`, which specifies how the chart is to be added to the main area, and `ref`, which is an (optional) reference to another chart. If `ref` is supplied, then `mode` will be with respect to that chart, if it is not supplied, then `mode` will be with respect to the main area. The default value for this option provides a relatively simple example specification.",
"properties": {},
"additionalProperties": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"split-top",
"split-left",
"split-right",
"split-bottom",
"tab-before",
"tab-after"
]
},
"ref": { "type": ["string", "null"], "default": null }
}
},
"default": {
"individual-task-stream": {
"mode": "split-right",
"ref": null
},
"individual-workers-memory": {
"mode": "split-bottom",
"ref": "individual-task-stream"
},
"individual-progress": {
"mode": "split-right",
"ref": "individual-workers-memory"
}
}
}
},
"type": "object"
Expand Down
157 changes: 142 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { CodeEditor } from '@jupyterlab/codeeditor';

import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';

import { DocumentRegistry } from '@jupyterlab/docregistry';

import { IMainMenu } from '@jupyterlab/mainmenu';

import { ISettingRegistry } from '@jupyterlab/settingregistry';
Expand All @@ -30,6 +32,8 @@ import {

import { Kernel, KernelMessage, Session } from '@jupyterlab/services';

import { topologicSort } from '@lumino/algorithm';

import { Signal } from '@lumino/signaling';

import { IClusterModel, DaskClusterManager } from './clusters';
Expand All @@ -46,6 +50,21 @@ namespace CommandIDs {
*/
export const launchPanel = 'dask:launch-dashboard';

/**
* Launch a dask custom layout.
*/
export const launchLayout = 'dask:launch-layout';

/**
* Attempt to find an active dask cluster.
*/
export const populateDashboardUrl = 'dask:populate-dashboard-url';

/**
* Attempt to find an active dask cluster.
*/
export const populateAndLaunchLayout = 'dask:populate-and-launch-layout';

/**
* Inject client code into the active editor.
*/
Expand Down Expand Up @@ -147,7 +166,7 @@ async function activate(
// Create the Dask sidebar panel.
const sidebar = new DaskSidebar({
launchDashboardItem: (item: IDashboardItem) => {
void app.commands.execute(CommandIDs.launchPanel, item);
void app.commands.execute(CommandIDs.launchPanel, { item });
},
linkFinder,
clientCodeInjector,
Expand All @@ -168,7 +187,7 @@ async function activate(
restorer.add(sidebar, id);
void restorer.restore(tracker, {
command: CommandIDs.launchPanel,
args: widget => widget.item || {},
args: widget => ({ item: widget.item } || {}),
name: widget => (widget.item && widget.item.route) || ''
});

Expand Down Expand Up @@ -286,6 +305,9 @@ async function activate(
// with default behavior.
let browserDashboardCheck: boolean = false;

// The default layout for dashboards.
let defaultLayout: { [x: string]: { mode: string; ref: string } };

// Update the existing trackers and signals in light of a change to the
// settings system. In particular, this reacts to a change in the setting
// for auto-starting cluster client.
Expand Down Expand Up @@ -356,6 +378,11 @@ async function activate(
hideClusterManager = settings.get('hideClusterManager')
.composite as boolean;
sidebar.clusterManager.setHidden(hideClusterManager);

// Get the default layout
defaultLayout = settings.get('defaultLayout').composite as {
[x: string]: { mode: string; ref: string };
};
};
onSettingsChanged();
// React to a change in the settings.
Expand All @@ -371,14 +398,18 @@ async function activate(

// Add the command for launching a new dashboard item.
app.commands.addCommand(CommandIDs.launchPanel, {
label: args => `Launch Dask ${(args['label'] as string) || ''} Dashboard`,
label: args =>
`Launch Dask ${
((args.item as IDashboardItem)['label'] as string) || ''
} Dashboard`,
caption: 'Launch a Dask dashboard',
execute: args => {
// Construct the url for the dashboard.
const urlInfo = sidebar.dashboardLauncher.input.urlInfo;
const dashboardUrl = urlInfo.effectiveUrl || urlInfo.url;
const active = urlInfo.isActive;
const dashboardItem = args as IDashboardItem;
const dashboardItem = args.item as IDashboardItem;
const addOptions = args?.options as DocumentRegistry.IOpenOptions;

// If we already have a dashboard open to this url, activate it
// but don't create a duplicate.
Expand All @@ -387,7 +418,7 @@ async function activate(
});
if (w) {
if (!w.isAttached) {
labShell.add(w, 'main');
labShell.add(w, 'main', addOptions);
}
labShell.activateById(w.id);
return;
Expand All @@ -402,12 +433,105 @@ async function activate(
dashboard.title.label = `${dashboardItem.label}`;
dashboard.title.icon = 'dask-DaskLogo';

labShell.add(dashboard, 'main');
labShell.add(dashboard, 'main', addOptions);
void tracker.add(dashboard); // no need to wait on this
return dashboard;
}
});

const _normalize_ref = (r: string) => {
if (r.startsWith('/')) {
r = r.slice(1);
}
if (!r.startsWith('individual-')) {
r = 'individual-' + r;
}
return r;
};

app.commands.addCommand(CommandIDs.launchLayout, {
label: 'Launch Dask Dashboard Layout',
caption: 'Launch a pre-configured Dask Dashboard Layout',
isEnabled: () => sidebar.dashboardLauncher.input.urlInfo.isActive,
execute: async () => {
const dashboards = sidebar.dashboardLauncher.items;

// Compute the order that we have to add the panes so that the refs
// exist when we need them.
const dependencies: Array<[string, string]> = [];
for (let k of Object.keys(defaultLayout)) {
dependencies.push([defaultLayout[k].ref || null, k]);
}
const order = topologicSort(dependencies).filter(d => d); // sort and remove nulls
const initial = app.shell.currentWidget;

for (let k of order) {
const opts = defaultLayout[k];

const dashboard = dashboards.find(
d => _normalize_ref(d.route) === _normalize_ref(k)
);
if (!dashboard) {
console.warn(`Non-existent dashboard found in Dask layout spec ${k}`);
continue;
}

const options: { mode: string; ref?: string } = { mode: opts.mode };
if (opts.ref) {
const ref = tracker.find(w => {
return !!(
w &&
w.item &&
_normalize_ref(w.item.route) === _normalize_ref(opts.ref)
);
});
if (!ref) {
console.warn(
`Non-existent dashboard found in Dask layout spec ${opts.ref}`
);
options.ref = null;
} else {
options.ref = ref.id;
}
} else {
options.ref = null;
}
await app.commands.execute(CommandIDs.launchPanel, {
item: dashboard,
options: options
});
}
app.shell.activateById(initial.id);
}
});

app.commands.addCommand(CommandIDs.populateDashboardUrl, {
label: 'Populate Dask Dashboard URL',
caption: 'Attempt to populate the URL for an active Dask cluster',
execute: async args => {
let url = (args.url as string) || (await linkFinder());
if (url) {
sidebar.dashboardLauncher.input.url = url;
}
return url;
}
});

app.commands.addCommand(CommandIDs.populateAndLaunchLayout, {
label: 'Populate Dask Dashboard URL and launch the default layout',
caption:
'Attempt to populate the URL for an active Dask cluster and then launch the default layout',
execute: async args => {
const url = await app.commands.execute(
CommandIDs.populateDashboardUrl,
args
);
if (url) {
await app.commands.execute(CommandIDs.launchLayout);
}
}
});

// Add a command to inject client connection code for a given cluster model.
// This looks for a cluster model in the application context menu,
// and looks for an editor among the currently active notebooks and consoles.
Expand Down Expand Up @@ -480,18 +604,21 @@ async function activate(
});

// Add some commands to the menu and command palette.
mainMenu.fileMenu.addGroup([{ command: CommandIDs.launchLayout }], 50);
mainMenu.settingsMenu.addGroup([
{ command: CommandIDs.toggleAutoStartClient }
]);
[CommandIDs.launchCluster, CommandIDs.toggleAutoStartClient].forEach(
command => {
commandPalette.addItem({
category: 'Dask',
command,
args: { isPalette: true }
});
}
);
[
CommandIDs.launchCluster,
CommandIDs.launchLayout,
CommandIDs.toggleAutoStartClient
].forEach(command => {
commandPalette.addItem({
category: 'Dask',
command,
args: { isPalette: true }
});
});

// Add a context menu items.
app.contextMenu.addItem({
Expand Down
Loading