Skip to content

Commit

Permalink
Enhance tour version (#81)
Browse files Browse the repository at this point in the history
* Hide empty value for select

* Use config to preserve tour states

* Fix code and tests
  • Loading branch information
fcollonval authored Nov 8, 2023
1 parent 02261db commit fd7eecc
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 132 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@jupyterlab/apputils": "^4.0.0",
"@jupyterlab/mainmenu": "^4.0.0",
"@jupyterlab/notebook": "^4.0.0",
"@jupyterlab/services": "^7.0.0",
"@jupyterlab/settingregistry": "^4.0.0",
"@jupyterlab/statedb": "^4.0.0",
"@jupyterlab/translation": "^4.0.0",
Expand Down
6 changes: 6 additions & 0 deletions schema/user-tours.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
"description": "Other options for the tour",
"$ref": "#/definitions/Props"
},
"version": {
"type": "integer",
"title": "The tour version",
"description": "The tour version (prefer calendar versioning YYYYMMDD) to determine if an user should see it again or not.",
"minimum": 0
},
"translation": {
"description": "Translation domain containing strings for this tour",
"type": "string"
Expand Down
19 changes: 6 additions & 13 deletions src/__tests__/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
NotebookPanel
} from '@jupyterlab/notebook';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { StateDB } from '@jupyterlab/statedb';
import { CommandRegistry } from '@lumino/commands';
import { ReadonlyJSONObject } from '@lumino/coreutils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
Expand Down Expand Up @@ -90,8 +89,7 @@ describe(corePlugin.id, () => {
describe('activation', () => {
it('should create add-tour command', () => {
const app = mockApp();
const stateDB = new StateDB();
corePlugin.activate(app as any, stateDB);
corePlugin.activate(app as any);

expect(app.commands?.hasCommand(CommandIDs.addTour)).toEqual(true);
});
Expand All @@ -101,8 +99,7 @@ describe(corePlugin.id, () => {
describe(`${CommandIDs.addTour}`, () => {
it('should add a tour command', async () => {
const app = mockApp();
const stateDB = new StateDB();
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
const manager = corePlugin.activate(app as any) as ITourManager;
expect(manager.tours.size).toEqual(0);

const tour = await app.commands?.execute(CommandIDs.addTour, {
Expand All @@ -121,8 +118,7 @@ describe(userPlugin.id, () => {
describe('activation', () => {
it('should have userTours', async () => {
const app = mockApp();
const stateDB = new StateDB();
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
const manager = corePlugin.activate(app as any) as ITourManager;
const settings = mockSettingRegistry();
const userManager = userPlugin.activate(
app as any,
Expand All @@ -137,8 +133,7 @@ describe(userPlugin.id, () => {
describe('settings', () => {
it('should react to settings', async () => {
const app = mockApp();
const stateDB = new StateDB();
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
const manager = corePlugin.activate(app as any) as ITourManager;
const settingsRegistry = mockSettingRegistry();
const userManager = userPlugin.activate(
app as any,
Expand All @@ -158,8 +153,7 @@ describe(notebookPlugin.id, () => {
describe('activation', () => {
it('should activate', async () => {
const app = mockApp();
const stateDB = new StateDB();
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
const manager = corePlugin.activate(app as any) as ITourManager;
const nbTracker = mockNbTracker();
const notebookTourManager = notebookPlugin.activate(
app as any,
Expand Down Expand Up @@ -195,8 +189,7 @@ describe(defaultsPlugin.id, () => {
describe('activation', () => {
it('should activate', () => {
const app = mockApp();
const stateDB = new StateDB();
const manager = corePlugin.activate(app as any, stateDB) as ITourManager;
const manager = corePlugin.activate(app as any) as ITourManager;
defaultsPlugin.activate(app as any, manager);
expect(manager.tours.size).toEqual(DEFAULT_TOURS_SIZE);
});
Expand Down
39 changes: 20 additions & 19 deletions src/defaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ namespace DefaultTours {
): void {
const trans = manager.translator;

const welcomeTour = manager.createTour(
WELCOME_ID,
trans.__('Welcome Tour'),
true,
undefined,
defaultTourIcon
);
const welcomeTour = manager.createTour({
id: WELCOME_ID,
label: trans.__('Welcome Tour'),
hasHelpEntry: true,
icon: defaultTourIcon,
version: 20231107
});

welcomeTour.options = {
...welcomeTour.options,
Expand Down Expand Up @@ -292,13 +292,13 @@ namespace DefaultTours {
appName = 'JupyterLab'
): void {
const trans = manager.translator;
const notebookTour = manager.createTour(
NOTEBOOK_ID,
trans.__('Notebook Tour'),
true,
undefined,
defaultNotebookTourIcon
);
const notebookTour = manager.createTour({
id: NOTEBOOK_ID,
label: trans.__('Notebook Tour'),
hasHelpEntry: true,
icon: defaultNotebookTourIcon,
version: 20231107
});

notebookTour.options = {
...notebookTour.options,
Expand Down Expand Up @@ -541,11 +541,12 @@ namespace DefaultTours {
): void {
const trans = manager.translator;

const welcomeTour = manager.createTour(
WELCOME_ID,
trans.__('Welcome Tour'),
true
);
const welcomeTour = manager.createTour({
id: WELCOME_ID,
label: trans.__('Welcome Tour'),
hasHelpEntry: true,
version: 20231107
});

welcomeTour.options = {
...welcomeTour.options,
Expand Down
2 changes: 1 addition & 1 deletion src/notebookButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class TourButton extends ReactWidget {
title={title}
value=""
>
<option value=""></option>
<option style={{ display: 'none' }} value=""></option>
{errors.length ? (
<option>
{trans.__('Tour metadata is not valid: see the browser console!')}
Expand Down
47 changes: 39 additions & 8 deletions src/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { IMainMenu, MainMenu } from '@jupyterlab/mainmenu';
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { IStateDB } from '@jupyterlab/statedb';
import { ConfigSection } from '@jupyterlab/services';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';

import { Widget } from '@lumino/widgets';
Expand All @@ -24,6 +24,8 @@ import {
INotebookTourManager,
ITourHandler,
ITourManager,
ITourState,
ITourTracker,
IUserTourManager,
NOTEBOOK_PLUGIN_ID,
NS,
Expand All @@ -33,6 +35,7 @@ import {
import { TourHandler } from './tour';
import { TourManager } from './tourManager';
import { UserTourManager } from './userTourManager';
import { PromiseDelegate } from '@lumino/coreutils';

/**
* Initialization data for the jupyterlab-tour extension.
Expand All @@ -41,24 +44,50 @@ const corePlugin: JupyterFrontEndPlugin<ITourManager> = {
id: PLUGIN_ID,
autoStart: true,
activate,
requires: [IStateDB],
optional: [ICommandPalette, IMainMenu, ITranslator],
provides: ITourManager
};

function activate(
app: JupyterFrontEnd,
stateDB: IStateDB,
palette?: ICommandPalette,
menu?: MainMenu,
translator?: ITranslator
palette: ICommandPalette | null,
menu: MainMenu | null,
translator: ITranslator | null
): ITourManager {
const CONFIG_SECTION_NAME = corePlugin.id.replace(/[^\w]/g, '');
const { commands } = app;

translator = translator ?? nullTranslator;

const restoreState = new PromiseDelegate<ITourState[]>();
const configReady = ConfigSection.create({
name: CONFIG_SECTION_NAME
}).catch(error => {
console.error('Failed to fetch state for jupyterlab-tour.', error);
});
const tracker: ITourTracker = Object.freeze({
restored: restoreState.promise,
save: async (state: ITourState[]) => {
await restoreState.promise;
(await configReady)?.update({ state } as any);
}
});

// Create tour manager
const manager = new TourManager(stateDB, translator, menu);
const manager = new TourManager({ helpMenu: menu?.helpMenu, tracker, translator });
void Promise.all([
app.restored,
// Use config instead of state to store independently of the workspace
// if a tour has been displayed or not.
configReady
])
.then(async ([_, config]) => {
restoreState.resolve((config?.data['state'] ?? []) as any);
})
.catch(error => {
console.error('Failed to load tour states.', error);
restoreState.reject(error);
});
const trans = manager.translator;

commands.addCommand(CommandIDs.launch, {
Expand Down Expand Up @@ -93,6 +122,8 @@ function activate(
}
}

await manager.ready;

manager.launch([id], force);
}
});
Expand Down Expand Up @@ -212,7 +243,7 @@ function activateDefaults(
});
}

app.restored.then(() => {
Promise.all([app.restored, tourManager.ready]).then(() => {
if (
tourManager.tours.has(WELCOME_ID) &&
(app.name !== 'Jupyter Notebook' ||
Expand Down
Loading

0 comments on commit fd7eecc

Please sign in to comment.