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

Add run first enabled #2

Merged
merged 2 commits into from
Mar 20, 2024
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
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');
});
});
Loading