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 user documentation #47

Merged
merged 13 commits into from
Sep 30, 2024
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
- name: Install the extension
run: |
set -eux
python -m pip install "jupyterlab>=4.0.0,<5" jupyterlab_gallery*.whl
python -m pip install "jupyterlab>=4.0.0,<5" jupyterlab_gallery*.whl jupyterlab-launchpad

- name: Install dependencies
working-directory: ui-tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

- name: Install dependencies
run: python -m pip install -U "jupyterlab>=4.0.0,<5"
run: python -m pip install -U "jupyterlab>=4.0.0,<5" jupyterlab-launchpad

- name: Install extension
run: |
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# jupyterlab-gallery

![Extension status](https://img.shields.io/badge/status-draft-critical 'Not yet working')
![Extension status](https://img.shields.io/badge/status-ready-success 'Ready to be used')
[![Github Actions Status](https://github.com/nebari-dev/jupyterlab-gallery/workflows/Build/badge.svg)](https://github.com/nebari-dev/jupyterlab-gallery/actions/workflows/build.yml)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/nebari-dev/jupyterlab-gallery/main?urlpath=lab)

Expand All @@ -10,7 +10,33 @@ This extension is composed of a Python package named `jupyterlab-gallery`
for the server extension and a NPM package named `jupyterlab-gallery`
for the frontend extension.

When [`jupyterlab-new-launcher`](https://github.com/nebari-dev/jupyterlab-new-launcher) is installed, the gallery will be added as a "Gallery" section in the launcher; otherwise it will be shown in the left sidebar.
When [`jupyterlab-launchpad`](https://github.com/nebari-dev/jupyterlab-launchpad) is installed, the gallery will be added as a "Gallery" section in the launcher:

![in launchpad][in-launchpad]

Otherwise it will be shown in the left sidebar.

[in-launchpad]: https://raw.githubusercontent.com/nebari-dev/jupyterlab-gallery/main/ui-tests/tests/jupyterlab_gallery.spec.ts-snapshots/in-launchpad-linux.png

## Usage

Hover over the tile with exhibit that you are interested in to reveal a "Download" button:

![hover before cloning][hover-fresh]

Clicking this button will start the download process which you can monitor by tracking the progress bar that shows up in the tile.
krassowski marked this conversation as resolved.
Show resolved Hide resolved

After cloning has completed, hover over the tile again to reveal "Open Folder" and "Update" buttons:

![hover after cloning][hover-cloned]

The update buttons becomes active once new version of the cloned repository becomes available (this is once new commits are pushed to the tracked branch):
krassowski marked this conversation as resolved.
Show resolved Hide resolved

![hover with updates][hover-update]

[hover-fresh]: https://raw.githubusercontent.com/nebari-dev/jupyterlab-gallery/main/ui-tests/tests/jupyterlab_gallery.spec.ts-snapshots/on-hover-fresh-linux.png
[hover-cloned]: https://raw.githubusercontent.com/nebari-dev/jupyterlab-gallery/main/ui-tests/tests/jupyterlab_gallery.spec.ts-snapshots/on-hover-cloned-linux.png
[hover-update]: https://raw.githubusercontent.com/nebari-dev/jupyterlab-gallery/main/ui-tests/tests/jupyterlab_gallery.spec.ts-snapshots/on-hover-updates-pending-linux.png

## Configuration

Expand Down Expand Up @@ -67,6 +93,12 @@ To run it as a single-user server in the JupyterHub context use:
jupyterhub-gallery
```

For additional help see the `GalleryManager` section in the output of:

```bash
jupyterlab-gallery --help-all
```

## Requirements

- JupyterLab >= 4.0.0
Expand Down
2 changes: 1 addition & 1 deletion src/gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ function Gallery(props: {
{props.exhibits.map(exhibit => (
<Exhibit
trans={props.trans}
key={exhibit.repository}
key={exhibit.homepage}
exhibit={exhibit}
actions={props.actions}
progressStream={props.progressStream}
Expand Down
10 changes: 5 additions & 5 deletions src/types.ts → src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ export interface IExhibitReply {

export interface IExhibit {
// from configuration file
repository: string;
homepage?: string;
title: string;
description: string;
icon: string;
description?: string;
icon?: string;
// state from server
id: number;
isCloned: boolean;
localPath: string;
revision: string;
lastUpdated: string;
revision?: string;
lastUpdated?: string;
updatesAvailable?: boolean;
}
3 changes: 2 additions & 1 deletion ui-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"devDependencies": {
"@jupyterlab/galata": "^5.0.5",
"@playwright/test": "^1.37.0"
"@playwright/test": "^1.37.0",
"jupyterlab-gallery": "link:../src/types"
}
}
258 changes: 243 additions & 15 deletions ui-tests/tests/jupyterlab_gallery.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,251 @@
import { expect, test } from '@jupyterlab/galata';
import { Page } from '@playwright/test';
import type {
IGalleryReply,
IExhibitReply,
IExhibit
} from 'jupyterlab-gallery';

/**
* Don't load JupyterLab webpage before running the tests.
* This is required to ensure we capture all log messages.
*/
test.use({ autoGoto: false });
interface IServerSideExhibit {
git: string;
homepage?: string;
title: string;
description?: string;
icon?: string;
branch?: string;
depth?: number;
}

test('should emit an activation console message', async ({ page }) => {
const logs: string[] = [];
const niceExhibitConfigs: IServerSideExhibit[] = [
{
git: 'https://github.com/numba/nvidia-cuda-tutorial.git',
homepage: 'https://github.com/numba/nvidia-cuda-tutorial',
title: 'Numba for CUDA',
description: 'Nvidia contributed CUDA tutorial for Numba'
},
{
git: 'https://github.com/yunjey/pytorch-tutorial.git',
homepage: 'https://github.com/yunjey/pytorch-tutorial',
title: 'PyTorch Tutorial',
description: 'PyTorch Tutorial for Deep Learning Researchers',
icon: 'https://github.com/yunjey/pytorch-tutorial/raw/master/logo/pytorch_logo_2018.svg'
},
{
git: 'https://github.com/jupyter-widgets/tutorial.git',
homepage: 'https://github.com/jupyter-widgets/tutorial',
title: 'Jupyter Widgets Tutorial',
description: 'The Jupyter Widget Ecosystem'
},
{
git: 'https://github.com/amueller/scipy-2018-sklearn.git',
homepage: 'https://github.com/amueller/scipy-2018-sklearn',
title: 'Scikit-learn Tutorial 2018',
description: 'SciPy 2018 Scikit-learn Tutorial'
},
{
git: 'https://github.com/nebari-dev/nebari.git',
homepage: 'https://github.com/nebari-dev/nebari',
title: 'Nebari',
description: 'Nebari - your open source data science platform',
icon: 'https://raw.githubusercontent.com/nebari-dev/nebari-design/main/logo-mark/horizontal/Nebari-Logo-Horizontal-Lockup.svg'
},
{
git: 'https://github.com/jupyterlab/jupyterlab.git',
homepage: 'https://github.com/jupyterlab/jupyterlab/',
title: 'JupyterLab',
description:
'JupyterLab is a highly extensible, feature-rich notebook authoring application and editing environment, and is a part of Project Jupyter',
icon: 'https://raw.githubusercontent.com/jupyterlab/jupyterlab/main/packages/ui-components/style/icons/jupyter/jupyter.svg'
}
];

page.on('console', message => {
logs.push(message.text());
const edgeCaseExhibitConfigs: IServerSideExhibit[] = [
{
git: 'https://github.com/krassowski/example-private-repository.git',
homepage: 'https://github.com/krassowski/example-private-repository/',
title: 'Private repository'
},
{
git: 'https://github.com/jupyter-widgets/tutorial.git',
title: 'Example without description'
},
{
git: 'https://gitlab.gnome.org/GNOME/atomix.git',
title: 'GNOME atomix',
description: 'Example without icon'
},
{
git: 'https://github.com/nebari-dev/nebari.git',
homepage: 'https://github.com/nebari-dev/nebari',
title: 'Empty icon',
description: 'Empty icon should show social card for GitHub repos',
icon: ''
}
];
function mockExhibit(config: IServerSideExhibit, id: number): IExhibit {
const git_url = config.git.split('/');
const repo_name = git_url.pop()!.split('.')[0];
const repo_owner = git_url.pop();

return {
homepage: config.homepage,
title: config.title,
description: config.description,
icon:
config.icon ??
`https://opengraph.githubassets.com/1/${repo_owner}/${repo_name}`,
id,
isCloned: false,
localPath: 'clone/path/' + repo_name,
revision: 'revision-id'
};
}

const niceExhibits: IExhibit[] = niceExhibitConfigs.map((config, index) => {
return mockExhibit(config, index);
});

const edgeCaseExhibits: IExhibit[] = edgeCaseExhibitConfigs.map(
(config, index) => {
return mockExhibit(config, index);
}
);

const defaultGalleryReplies: IGalleryReply = {
title: 'Gallery',
exhibitsConfigured: true,
hideGalleryWithoutExhibits: false,
apiVersion: '1.0'
};

const defaultExhibitsReply: IExhibitReply = {
exhibits: niceExhibits
};

async function mockGalleryEndpoint(
page: Page,
gallery: Partial<IGalleryReply> = {}
): Promise<void> {
await page.route(/\/jupyterlab-gallery\/gallery/, (route, request) => {
switch (request.method()) {
case 'GET':
return route.fulfill({
status: 200,
body: JSON.stringify({
...defaultGalleryReplies,
...gallery
})
});
default:
return route.continue();
}
});
}

async function mockExhibitsEndpoint(
page: Page,
exhibits: Partial<IExhibitReply> = {}
): Promise<void> {
await page.route(/\/jupyterlab-gallery\/exhibits/, (route, request) => {
switch (request.method()) {
case 'GET':
return route.fulfill({
status: 200,
body: JSON.stringify({
...defaultExhibitsReply,
...exhibits
})
});
default:
return route.continue();
}
});
}

test.describe('Integration with jupyterlab-launchpad', () => {
/**
* Don't load JupyterLab webpage before running the tests.
*/
test.use({ autoGoto: false });

const EXAMPLE_CARD = 1;

test('Launchpad integration', async ({ page }) => {
await mockGalleryEndpoint(page);
await mockExhibitsEndpoint(page);

await page.goto();

// collapse the "create empty" section
await page.locator('.jp-Launcher-openByType summary').click();
// wait for animations to complete
await page.waitForTimeout(400);

const launcher = page.locator('.jp-LauncherBody');

const mainMenu = page.locator('#jp-MainMenu');

await page.goto();
// move the mouse away from the summary button
await mainMenu.hover();
await page.waitForTimeout(100);

expect(
logs.filter(
s => s === 'JupyterLab extension jupyterlab-gallery is activated!'
)
).toHaveLength(1);
expect(await launcher.screenshot()).toMatchSnapshot('in-launchpad.png');
});

test('On hover - fresh', async ({ page }) => {
await mockGalleryEndpoint(page);
await mockExhibitsEndpoint(page, {
exhibits: [niceExhibits[EXAMPLE_CARD]]
});

await page.goto();

const card = page.locator('.jp-Exhibit').first();
await card.hover();
expect(await card.screenshot()).toMatchSnapshot('on-hover-fresh.png');
});

test('On hover - cloned', async ({ page }) => {
await mockGalleryEndpoint(page);
await mockExhibitsEndpoint(page, {
exhibits: [{ ...niceExhibits[EXAMPLE_CARD], isCloned: true }]
});

await page.goto();

const card = page.locator('.jp-Exhibit').first();
await card.hover();
expect(await card.screenshot()).toMatchSnapshot('on-hover-cloned.png');
});

test('On hover - updates pending', async ({ page }) => {
await mockGalleryEndpoint(page);
await mockExhibitsEndpoint(page, {
exhibits: [
{
...niceExhibits[EXAMPLE_CARD],
isCloned: true,
updatesAvailable: true
}
]
});

await page.goto();

const card = page.locator('.jp-Exhibit').first();
await card.hover();
expect(await card.screenshot()).toMatchSnapshot(
'on-hover-updates-pending.png'
);
});

test('Odd cases', async ({ page }) => {
await mockGalleryEndpoint(page, { title: 'Edge cases' });
await mockExhibitsEndpoint(page, { exhibits: edgeCaseExhibits });
await page.goto();
// wait for pictures to settle
await page.waitForTimeout(400);
const gallery = page.locator('.jp-Gallery');
expect(await gallery.screenshot()).toMatchSnapshot('odd-cases.png');
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading