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 isConnected to ServiceManager, use it in hub-extension #10156

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 27 additions & 3 deletions packages/application/src/lab.ts
Expand Up @@ -4,6 +4,7 @@
import { PageConfig } from '@jupyterlab/coreutils';
import { Base64ModelFactory } from '@jupyterlab/docregistry';
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
import { ServiceManager } from '@jupyterlab/services';
import { Token } from '@lumino/coreutils';
import { JupyterFrontEnd, JupyterFrontEndPlugin } from './frontend';
import { createRendermimePlugins } from './mimerenderers';
Expand All @@ -18,7 +19,17 @@ export class JupyterLab extends JupyterFrontEnd<ILabShell> {
* Construct a new JupyterLab object.
*/
constructor(options: JupyterLab.IOptions = { shell: new LabShell() }) {
super({ ...options, shell: options.shell || new LabShell() });
super({
...options,
shell: options.shell || new LabShell(),
serviceManager:
options.serviceManager ||
new ServiceManager({
standby: () => {
return !this._info.isConnected || 'when-hidden';
}
})
});
this.restored = this.shell.restored
.then(() => undefined)
.catch(() => undefined);
Expand Down Expand Up @@ -157,7 +168,7 @@ export class JupyterLab extends JupyterFrontEnd<ILabShell> {
});
}

private _info: JupyterLab.IInfo;
private _info: JupyterLab.IInfo = JupyterLab.defaultInfo;
private _paths: JupyterFrontEnd.IPaths;
}

Expand Down Expand Up @@ -207,6 +218,18 @@ export namespace JupyterLab {
* Whether files are cached on the server.
*/
readonly filesCached: boolean;

/**
* Every periodic network polling should be paused while this is set
* to `false`. Extensions should use this value to decide whether to proceed
* with the polling.
* The extensions may also set this value to `false` if there is no need to
* fetch anything from the server backend basing on some conditions
* (e.g. when an error message dialog is displayed).
* At the same time, the extensions are responsible for setting this value
* back to `true`.
*/
isConnected: boolean;
}

/**
Expand All @@ -217,7 +240,8 @@ export namespace JupyterLab {
deferred: { patterns: [], matches: [] },
disabled: { patterns: [], matches: [] },
mimeExtensions: [],
filesCached: PageConfig.getOption('cacheFiles').toLowerCase() === 'true'
filesCached: PageConfig.getOption('cacheFiles').toLowerCase() === 'true',
isConnected: true
};

/**
Expand Down
17 changes: 13 additions & 4 deletions packages/docmanager-extension/src/index.ts
Expand Up @@ -9,7 +9,8 @@ import {
ILabShell,
ILabStatus,
JupyterFrontEnd,
JupyterFrontEndPlugin
JupyterFrontEndPlugin,
JupyterLab
} from '@jupyterlab/application';
import {
Dialog,
Expand Down Expand Up @@ -89,7 +90,8 @@ const docManagerPlugin: JupyterFrontEndPlugin<IDocumentManager> = {
ICommandPalette,
ILabShell,
ISessionContextDialogs,
IDocumentProviderFactory
IDocumentProviderFactory,
JupyterLab.IInfo
],
activate: (
app: JupyterFrontEnd,
Expand All @@ -99,7 +101,8 @@ const docManagerPlugin: JupyterFrontEndPlugin<IDocumentManager> = {
palette: ICommandPalette | null,
labShell: ILabShell | null,
sessionDialogs: ISessionContextDialogs | null,
docProviderFactory: IDocumentProviderFactory | null
docProviderFactory: IDocumentProviderFactory | null,
info: JupyterLab.IInfo | null
): IDocumentManager => {
const trans = translator.load('jupyterlab');
const manager = app.serviceManager;
Expand Down Expand Up @@ -139,7 +142,13 @@ const docManagerPlugin: JupyterFrontEndPlugin<IDocumentManager> = {
sessionDialogs: sessionDialogs || undefined,
translator,
collaborative: true,
docProviderFactory: docProviderFactory ?? undefined
docProviderFactory: docProviderFactory ?? undefined,
isConnectedCallback: () => {
if (info) {
return info.isConnected;
}
return true;
}
});

// Register the file operations commands.
Expand Down
9 changes: 9 additions & 0 deletions packages/docmanager/src/manager.ts
Expand Up @@ -42,6 +42,7 @@ export class DocumentManager implements IDocumentManager {
this._collaborative = !!options.collaborative;
this._dialogs = options.sessionDialogs || sessionContextDialogs;
this._docProviderFactory = options.docProviderFactory;
this._isConnectedCallback = options.isConnectedCallback || (() => true);

this._opener = options.opener;
this._when = options.when || options.manager.ready;
Expand Down Expand Up @@ -477,6 +478,7 @@ export class DocumentManager implements IDocumentManager {
});
const handler = new SaveHandler({
context,
isConnectedCallback: this._isConnectedCallback,
saveInterval: this.autosaveInterval
});
Private.saveHandlerProperty.set(context, handler);
Expand Down Expand Up @@ -602,6 +604,7 @@ export class DocumentManager implements IDocumentManager {
private _dialogs: ISessionContext.IDialogs;
private _docProviderFactory: IDocumentProviderFactory | undefined;
private _collaborative: boolean;
private _isConnectedCallback: () => boolean;
}

/**
Expand Down Expand Up @@ -657,6 +660,12 @@ export namespace DocumentManager {
* If true, the context will connect through yjs_ws_server to share information if possible.
*/
collaborative?: boolean;

/**
* Autosaving should be paused while this callback function returns `false`.
* By default, it always returns `true`.
*/
isConnectedCallback?: () => boolean;
}

/**
Expand Down
12 changes: 11 additions & 1 deletion packages/docmanager/src/savehandler.ts
Expand Up @@ -17,6 +17,7 @@ export class SaveHandler implements IDisposable {
*/
constructor(options: SaveHandler.IOptions) {
this._context = options.context;
this._isConnectedCallback = options.isConnectedCallback || (() => true);
const interval = options.saveInterval || 120;
this._minInterval = interval * 1000;
this._interval = this._minInterval;
Expand Down Expand Up @@ -89,7 +90,9 @@ export class SaveHandler implements IDisposable {
return;
}
this._autosaveTimer = window.setTimeout(() => {
this._save();
if (this._isConnectedCallback()) {
this._save();
}
}, this._interval);
}

Expand Down Expand Up @@ -144,6 +147,7 @@ export class SaveHandler implements IDisposable {
private _minInterval = -1;
private _interval = -1;
private _context: DocumentRegistry.Context;
private _isConnectedCallback: () => boolean;
private _isActive = false;
private _inDialog = false;
private _isDisposed = false;
Expand All @@ -163,6 +167,12 @@ export namespace SaveHandler {
*/
context: DocumentRegistry.Context;

/**
* Autosaving should be paused while this callback function returns `false`.
* By default, it always returns `true`.
*/
isConnectedCallback?: () => boolean;

/**
* The minimum save interval in seconds (default is two minutes).
*/
Expand Down
19 changes: 16 additions & 3 deletions packages/filebrowser-extension/src/index.ts
Expand Up @@ -11,7 +11,8 @@ import {
IRouter,
ITreePathUpdater,
JupyterFrontEnd,
JupyterFrontEndPlugin
JupyterFrontEndPlugin,
JupyterLab
} from '@jupyterlab/application';
import {
Clipboard,
Expand Down Expand Up @@ -246,14 +247,20 @@ const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
id: '@jupyterlab/filebrowser-extension:factory',
provides: IFileBrowserFactory,
requires: [IDocumentManager, ITranslator],
optional: [IStateDB, IRouter, JupyterFrontEnd.ITreeResolver],
optional: [
IStateDB,
IRouter,
JupyterFrontEnd.ITreeResolver,
JupyterLab.IInfo
],
activate: async (
app: JupyterFrontEnd,
docManager: IDocumentManager,
translator: ITranslator,
state: IStateDB | null,
router: IRouter | null,
tree: JupyterFrontEnd.ITreeResolver | null
tree: JupyterFrontEnd.ITreeResolver | null,
info: JupyterLab.IInfo | null
): Promise<IFileBrowserFactory> => {
const { commands } = app;
const tracker = new WidgetTracker<FileBrowser>({ namespace });
Expand All @@ -267,6 +274,12 @@ const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
manager: docManager,
driveName: options.driveName || '',
refreshInterval: options.refreshInterval,
refreshStandby: () => {
if (info) {
return !info.isConnected || 'when-hidden';
}
return 'when-hidden';
},
state:
options.state === null
? undefined
Expand Down
7 changes: 6 additions & 1 deletion packages/filebrowser/src/model.ts
Expand Up @@ -103,7 +103,7 @@ export class FileBrowserModel implements IDisposable {
backoff: true,
max: 300 * 1000
},
standby: 'when-hidden'
standby: options.refreshStandby || 'when-hidden'
});
}

Expand Down Expand Up @@ -695,6 +695,11 @@ export namespace FileBrowserModel {
*/
refreshInterval?: number;

/**
* When the model stops polling the API. Defaults to `when-hidden`.
*/
refreshStandby?: Poll.Standby | (() => boolean | Poll.Standby);

/**
* An optional state database. If provided, the model will restore which
* folder was last opened when it is restored.
Expand Down
17 changes: 15 additions & 2 deletions packages/hub-extension/src/index.ts
Expand Up @@ -11,7 +11,8 @@ import {
ConnectionLost,
IConnectionLost,
JupyterFrontEnd,
JupyterFrontEndPlugin
JupyterFrontEndPlugin,
JupyterLab
} from '@jupyterlab/application';
import { Dialog, ICommandPalette, showDialog } from '@jupyterlab/apputils';
import { URLExt } from '@jupyterlab/coreutils';
Expand Down Expand Up @@ -125,10 +126,12 @@ const hubExtensionMenu: JupyterFrontEndPlugin<void> = {
const connectionlost: JupyterFrontEndPlugin<IConnectionLost> = {
id: '@jupyterlab/apputils-extension:connectionlost',
requires: [JupyterFrontEnd.IPaths, ITranslator],
optional: [JupyterLab.IInfo],
activate: (
app: JupyterFrontEnd,
paths: JupyterFrontEnd.IPaths,
translator: ITranslator
translator: ITranslator,
info: JupyterLab.IInfo | null
): IConnectionLost => {
const trans = translator.load('jupyterlab');
const hubPrefix = paths.urls.hubPrefix || '';
Expand All @@ -149,7 +152,12 @@ const connectionlost: JupyterFrontEndPlugin<IConnectionLost> = {
if (showingError) {
return;
}

showingError = true;
if (info) {
info.isConnected = false;
}

const result = await showDialog({
title: trans.__('Server unavailable or unreachable'),
body: trans.__(
Expand All @@ -161,7 +169,12 @@ const connectionlost: JupyterFrontEndPlugin<IConnectionLost> = {
Dialog.cancelButton({ label: trans.__('Dismiss') })
]
});

if (info) {
info.isConnected = true;
}
showingError = false;

if (result.button.accept) {
await app.commands.execute(CommandIDs.restart);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/services/src/kernel/manager.ts
Expand Up @@ -337,6 +337,6 @@ export namespace KernelManager {
/**
* When the manager stops polling the API. Defaults to `when-hidden`.
*/
standby?: Poll.Standby;
standby?: Poll.Standby | (() => boolean | Poll.Standby);
}
}
2 changes: 1 addition & 1 deletion packages/services/src/kernelspec/manager.ts
Expand Up @@ -147,6 +147,6 @@ export namespace KernelSpecManager {
/**
* When the manager stops polling the API. Defaults to `when-hidden`.
*/
standby?: Poll.Standby;
standby?: Poll.Standby | (() => boolean | Poll.Standby);
}
}
2 changes: 1 addition & 1 deletion packages/services/src/manager.ts
Expand Up @@ -254,6 +254,6 @@ export namespace ServiceManager {
/**
* When the manager stops polling the API. Defaults to `when-hidden`.
*/
standby?: Poll.Standby;
standby?: Poll.Standby | (() => boolean | Poll.Standby);
}
}
2 changes: 1 addition & 1 deletion packages/services/src/session/manager.ts
Expand Up @@ -355,7 +355,7 @@ export namespace SessionManager {
/**
* When the manager stops polling the API. Defaults to `when-hidden`.
*/
standby?: Poll.Standby;
standby?: Poll.Standby | (() => boolean | Poll.Standby);

/**
* Kernel Manager
Expand Down
2 changes: 1 addition & 1 deletion packages/services/src/terminal/manager.ts
Expand Up @@ -289,6 +289,6 @@ export namespace TerminalManager {
/**
* When the manager stops polling the API. Defaults to `when-hidden`.
*/
standby?: Poll.Standby;
standby?: Poll.Standby | (() => boolean | Poll.Standby);
}
}