Skip to content

Commit

Permalink
Add run first enabled (#2)
Browse files Browse the repository at this point in the history
* Add new `nebari:run-first-enabled` command

* Bump version to 0.3.0
  • Loading branch information
krassowski authored Mar 20, 2024
1 parent 22770cb commit adf0c3e
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 25 deletions.
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,38 @@ Nebari customizations for JupyterLab.

- `jupyterlab-nebari-mode:logo`: replaces `@jupyterlab/application-extension:logo`, adding clickable Nebari logo:
![](https://raw.githubusercontent.com/nebari-dev/jupyterlab-nebari-mode/main/ui-tests/tests/jupyterlab_nebari_mode.spec.ts-snapshots/top-panel-linux.png)
- `jupyterlab-nebari-mode:commands` adds `nebari:open-proxy` command for opening proxied processes, such as VSCode. This command can be used to add a menu entry, e.g.:
```json
{
"command": "nebari:open-proxy",
"rank": 1,
"args": {
"name": "vscode"
- `jupyterlab-nebari-mode:commands` adds the following commands:
- `nebari:open-proxy` which opens proxied processes, such as VSCode; this command can be used to add a menu entry, e.g.:
```json
{
"command": "nebari:open-proxy",
"rank": 1,
"args": {
"name": "vscode"
}
}
}
```
```
- `nebari:run-first-enabled` which runs the first available and enabled command; it differs from the built-in `apputils:run-first-enabled` command in that it takes a list of objects representing the commands, allowing to customise the `label`, `iconClass`, `caption`, `usage`, and `className` properties. An example usage for menu customization would be adding a menu entry labelled `Import numpy in File Editor` when user has the File Editor open and `Import numpy in Notebook` when user has a Notebook open:
```json
{
"command": "nebari:run-first-enabled",
"rank": 1,
"args": {
"commands": [
{
"id": "fileeditor:replace-selection",
"label": "Import numpy in File Editor",
"args": { "text": "import numpy as np" }
},
{
"id": "notebook:replace-selection",
"label": "Import numpy in Notebook",
"args": { "text": "import numpy as np" }
}
]
}
}
```

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jupyterlab-nebari-mode",
"version": "0.2.0",
"version": "0.3.0",
"description": "Nebari customizations for JupyterLab.",
"keywords": [
"jupyter",
Expand Down
72 changes: 72 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { ServerConnection } from '@jupyterlab/services';
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
import { Widget } from '@lumino/widgets';
import { CommandRegistry } from '@lumino/commands';
import { nebariIcon } from './icons';

class NebariLogo extends Widget {
Expand Down Expand Up @@ -54,6 +55,11 @@ namespace CommandIDs {
* Opens a process proxied by jupyter-server-proxy (such as VSCode).
*/
export const openProxy = 'nebari:open-proxy';
/**
* Run first enabled command, similar to `apputils:run-first-enabled`,
* but assumes all properties of the first enabled command.
*/
export const runFirstEnabled = 'nebari:run-first-enabled';
}

interface IOpenProxyArgs {
Expand All @@ -63,6 +69,25 @@ interface IOpenProxyArgs {
name?: string;
}

interface ICommandDescription
extends Omit<CommandRegistry.ICommandOptions, 'execute'> {
/**
* The identifier of the command.
*/
id: string;
/**
* The arguments for the command.
*/
args: any;
}

interface IRunFirstEnabledArgs {
/**
* Name of the server process to open.
*/
commands?: ICommandDescription[];
}

const commandsPlugin: JupyterFrontEndPlugin<void> = {
id: 'jupyterlab-nebari-mode:commands',
description: 'Adds additional commands used by nebari.',
Expand Down Expand Up @@ -122,6 +147,53 @@ const commandsPlugin: JupyterFrontEndPlugin<void> = {
: 'Open Proxied Process';
}
});

const returnFirstEnabled = (
args: IRunFirstEnabledArgs,
method: 'label' | 'iconClass' | 'caption' | 'usage' | 'className'
): string | undefined => {
const commands = args.commands ?? [];
for (const command of commands) {
if (
app.commands.hasCommand(command.id) &&
app.commands.isEnabled(command.id, command.args)
) {
return (
(command[method] as string | undefined) ??
app.commands[method](command.id, command.args)
);
}
}
};

app.commands.addCommand(CommandIDs.runFirstEnabled, {
execute: (args: IRunFirstEnabledArgs) => {
const commands = args.commands ?? [];
for (const command of commands) {
if (
app.commands.hasCommand(command.id) &&
app.commands.isEnabled(command.id, command.args)
) {
return app.commands.execute(command.id, command.args);
}
}
},
label: (args: IRunFirstEnabledArgs) => {
return returnFirstEnabled(args, 'label') ?? 'Run First Enabled';
},
iconClass: (args: IRunFirstEnabledArgs) => {
return returnFirstEnabled(args, 'iconClass') ?? '';
},
caption: (args: IRunFirstEnabledArgs) => {
return returnFirstEnabled(args, 'caption') ?? '';
},
usage: (args: IRunFirstEnabledArgs) => {
return returnFirstEnabled(args, 'usage') ?? '';
},
className: (args: IRunFirstEnabledArgs) => {
return returnFirstEnabled(args, 'className') ?? '';
}
});
}
};

Expand Down
89 changes: 74 additions & 15 deletions ui-tests/tests/jupyterlab_nebari_mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,81 @@ test('should swap Jupyter logo with clickable Nebari logo', async ({
expect(await link.screenshot()).toMatchSnapshot('nebari-logo-focus.png');
});

test('should register custom commands', async ({ page }) => {
const openVScodeProxy = await page.evaluate(async () => {
const registry = window.jupyterapp.commands;
const id = 'nebari:open-proxy';
const args = { name: 'vscode' };

return {
id,
label: registry.label(id, args),
isEnabled: registry.isEnabled(id, args)
};
test.describe('should register custom commands', () => {
test('nebari:open-proxy command works', async ({ page }) => {
const openVScodeProxy = await page.evaluate(async () => {
const registry = window.jupyterapp.commands;
const id = 'nebari:open-proxy';
const args = { name: 'vscode' };

return {
id,
label: registry.label(id, args),
isEnabled: registry.isEnabled(id, args)
};
});

// Should set correct label for given command
expect(openVScodeProxy.label).toBe('Open VS Code');

// Should be enabled when `jupyter-vscode-proxy` is installed
expect(openVScodeProxy.isEnabled).toBe(true);
});

// Should set correct label for given command
expect(openVScodeProxy.label).toBe('Open VS Code');
test('nebari:run-first-enabled command returns default label of first enabled command', async ({
page
}) => {
const runFirstEnabled = await page.evaluate(async () => {
const registry = window.jupyterapp.commands;
const id = 'nebari:run-first-enabled';
const args = {
commands: [
{
id: 'this-command-does-not-exist'
},
{
id: 'nebari:open-proxy',
args: { name: 'vscode' }
}
]
};

// Should be enabled when `jupyter-vscode-proxy` is installed
expect(openVScodeProxy.isEnabled).toBe(true);
return {
id,
label: registry.label(id, args),
isEnabled: registry.isEnabled(id, args)
};
});
// Should set correct label for given command
expect(runFirstEnabled.label).toBe('Open VS Code');
});

test('nebari:run-first-enabled command returns custom label if given', async ({
page
}) => {
const runFirstEnabled = await page.evaluate(async () => {
const registry = window.jupyterapp.commands;
const id = 'nebari:run-first-enabled';
const args = {
commands: [
{
id: 'this-command-does-not-exist'
},
{
id: 'nebari:open-proxy',
args: { name: 'vscode' },
label: 'Open Service VSCode'
}
]
};

return {
id,
label: registry.label(id, args),
isEnabled: registry.isEnabled(id, args)
};
});
// Should set correct label for given command
expect(runFirstEnabled.label).toBe('Open Service VSCode');
});
});

0 comments on commit adf0c3e

Please sign in to comment.