@jupyterlab/notebook
Version:
JupyterLab - Notebook
412 lines • 17.6 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { translateKernelStatuses } from '@jupyterlab/apputils';
import { nullTranslator } from '@jupyterlab/translation';
import React from 'react';
import { ProgressCircle } from '@jupyterlab/statusbar';
import { circleIcon, offlineBoltIcon, VDomModel, VDomRenderer } from '@jupyterlab/ui-components';
import { KernelMessage } from '@jupyterlab/services';
/**
* A react functional component for rendering execution indicator.
*/
export function ExecutionIndicatorComponent(props) {
const translator = props.translator || nullTranslator;
const kernelStatuses = translateKernelStatuses(translator);
const trans = translator.load('jupyterlab');
const state = props.state;
const showOnToolBar = props.displayOption.showOnToolBar;
const showProgress = props.displayOption.showProgress;
const tooltipClass = showOnToolBar ? 'down' : 'up';
const emptyDiv = React.createElement("div", null);
if (!state) {
return emptyDiv;
}
const kernelStatus = state.kernelStatus;
const circleIconProps = {
alignSelf: 'normal',
height: '24px'
};
const time = state.totalTime;
const scheduledCellNumber = state.scheduledCellNumber || 0;
const remainingCellNumber = state.scheduledCell.size || 0;
const executedCellNumber = scheduledCellNumber - remainingCellNumber;
let percentage = (100 * executedCellNumber) / scheduledCellNumber;
let displayClass = showProgress ? '' : 'hidden';
if (!showProgress && percentage < 100) {
percentage = 0;
}
const progressBar = (percentage) => (React.createElement(ProgressCircle, { progress: percentage, width: 16, height: 24, label: trans.__('Kernel status') }));
const titleFactory = (translatedStatus) => trans.__('Kernel status: %1', translatedStatus);
const reactElement = (status, circle, popup) => (React.createElement("div", { className: 'jp-Notebook-ExecutionIndicator', title: showProgress ? '' : titleFactory(kernelStatuses[status]), "data-status": status },
circle,
React.createElement("div", { className: `jp-Notebook-ExecutionIndicator-tooltip ${tooltipClass} ${displayClass}` },
React.createElement("span", null,
" ",
titleFactory(kernelStatuses[status]),
" "),
popup)));
if (state.kernelStatus === 'connecting' ||
state.kernelStatus === 'disconnected' ||
state.kernelStatus === 'unknown') {
return reactElement(kernelStatus, React.createElement(offlineBoltIcon.react, { ...circleIconProps }), []);
}
if (state.kernelStatus === 'starting' ||
state.kernelStatus === 'terminating' ||
state.kernelStatus === 'restarting' ||
state.kernelStatus === 'initializing') {
return reactElement(kernelStatus, React.createElement(circleIcon.react, { ...circleIconProps }), []);
}
if (state.executionStatus === 'busy') {
return reactElement('busy', progressBar(percentage), [
React.createElement("span", { key: 0 }, trans.__(`Executed ${executedCellNumber}/${scheduledCellNumber} cells`)),
React.createElement("span", { key: 1 }, trans._n('Elapsed time: %1 second', 'Elapsed time: %1 seconds', time))
]);
}
else {
// No cell is scheduled, fall back to the status of kernel
const progress = state.kernelStatus === 'busy' ? 0 : 100;
const popup = state.kernelStatus === 'busy' || time === 0
? []
: [
React.createElement("span", { key: 0 }, trans._n('Executed %1 cell', 'Executed %1 cells', scheduledCellNumber)),
React.createElement("span", { key: 1 }, trans._n('Elapsed time: %1 second', 'Elapsed time: %1 seconds', time))
];
return reactElement(state.kernelStatus, progressBar(progress), popup);
}
}
/**
* A VDomRenderer widget for displaying the execution status.
*/
export class ExecutionIndicator extends VDomRenderer {
/**
* Construct the kernel status widget.
*/
constructor(translator, showProgress = true) {
super(new ExecutionIndicator.Model());
this.translator = translator || nullTranslator;
this.addClass('jp-mod-highlighted');
}
/**
* Render the execution status item.
*/
render() {
if (this.model === null || !this.model.renderFlag) {
return React.createElement("div", null);
}
else {
const nb = this.model.currentNotebook;
if (!nb) {
return (React.createElement(ExecutionIndicatorComponent, { displayOption: this.model.displayOption, state: undefined, translator: this.translator }));
}
return (React.createElement(ExecutionIndicatorComponent, { displayOption: this.model.displayOption, state: this.model.executionState(nb), translator: this.translator }));
}
}
}
/**
* A namespace for ExecutionIndicator statics.
*/
(function (ExecutionIndicator) {
/**
* A VDomModel for the execution status indicator.
*/
class Model extends VDomModel {
constructor() {
super();
/**
* A weak map to hold execution status of multiple notebooks.
*/
this._notebookExecutionProgress = new WeakMap();
this._displayOption = { showOnToolBar: true, showProgress: true };
this._renderFlag = true;
}
/**
* Attach a notebook with session context to model in order to keep
* track of multiple notebooks. If a session context is already
* attached, only set current activated notebook to input.
*
* @param data - The notebook and session context to be attached to model
*/
attachNotebook(data) {
var _a, _b, _c, _d;
if (data && data.content && data.context) {
const nb = data.content;
const context = data.context;
this._currentNotebook = nb;
if (!this._notebookExecutionProgress.has(nb)) {
this._notebookExecutionProgress.set(nb, {
executionStatus: 'idle',
kernelStatus: 'idle',
totalTime: 0,
interval: 0,
timeout: 0,
scheduledCell: new Set(),
scheduledCellNumber: 0,
needReset: true
});
const state = this._notebookExecutionProgress.get(nb);
const contextStatusChanged = (ctx) => {
if (state) {
state.kernelStatus = ctx.kernelDisplayStatus;
}
this.stateChanged.emit(void 0);
};
context.statusChanged.connect(contextStatusChanged, this);
const contextConnectionStatusChanged = (ctx) => {
if (state) {
state.kernelStatus = ctx.kernelDisplayStatus;
}
this.stateChanged.emit(void 0);
};
context.connectionStatusChanged.connect(contextConnectionStatusChanged, this);
context.disposed.connect(ctx => {
ctx.connectionStatusChanged.disconnect(contextConnectionStatusChanged, this);
ctx.statusChanged.disconnect(contextStatusChanged, this);
});
const handleKernelMsg = (sender, msg) => {
const message = msg.msg;
const msgId = message.header.msg_id;
if (message.header.msg_type === 'execute_request') {
// A cell code is scheduled for executing
this._cellScheduledCallback(nb, msgId);
}
else if (KernelMessage.isStatusMsg(message) &&
message.content.execution_state === 'idle') {
// Idle status message case.
const parentId = message.parent_header
.msg_id;
this._cellExecutedCallback(nb, parentId);
}
else if (KernelMessage.isStatusMsg(message) &&
message.content.execution_state === 'restarting') {
this._restartHandler(nb);
}
else if (message.header.msg_type === 'execute_input') {
// A cell code starts executing.
this._startTimer(nb);
}
};
(_b = (_a = context.session) === null || _a === void 0 ? void 0 : _a.kernel) === null || _b === void 0 ? void 0 : _b.anyMessage.connect(handleKernelMsg);
(_d = (_c = context.session) === null || _c === void 0 ? void 0 : _c.kernel) === null || _d === void 0 ? void 0 : _d.disposed.connect(kernel => kernel.anyMessage.disconnect(handleKernelMsg));
const kernelChangedSlot = (_, kernelData) => {
if (state) {
this._resetTime(state);
this.stateChanged.emit(void 0);
if (kernelData.newValue) {
kernelData.newValue.anyMessage.connect(handleKernelMsg);
}
}
};
context.kernelChanged.connect(kernelChangedSlot);
context.disposed.connect(ctx => ctx.kernelChanged.disconnect(kernelChangedSlot));
}
}
}
/**
* The current activated notebook in model.
*/
get currentNotebook() {
return this._currentNotebook;
}
/**
* The display options for progress bar and elapsed time.
*/
get displayOption() {
return this._displayOption;
}
/**
* Set the display options for progress bar and elapsed time.
*
* @param options - Options to be used
*/
set displayOption(options) {
this._displayOption = options;
}
/**
* Get the execution state associated with a notebook.
*
* @param nb - The notebook used to identify execution
* state.
*
* @returns - The associated execution state.
*/
executionState(nb) {
return this._notebookExecutionProgress.get(nb);
}
/**
* Schedule switch to idle status and clearing of the timer.
*
* ### Note
*
* To keep track of cells executed under 1 second,
* the execution state is marked as `needReset` 1 second after executing
* these cells. This `Timeout` will be cleared if there is any cell
* scheduled after that.
*/
_scheduleSwitchToIdle(state) {
window.setTimeout(() => {
state.executionStatus = 'idle';
clearInterval(state.interval);
this.stateChanged.emit(void 0);
}, 150);
state.timeout = window.setTimeout(() => {
state.needReset = true;
}, 1000);
}
/**
* The function is called on kernel's idle status message.
* It is used to keep track of number of executed
* cells or Comm custom messages and the status of kernel.
*
* @param nb - The notebook which contains the executed code cell.
* @param msg_id - The id of message.
*/
_cellExecutedCallback(nb, msg_id) {
const state = this._notebookExecutionProgress.get(nb);
if (state && state.scheduledCell.has(msg_id)) {
state.scheduledCell.delete(msg_id);
if (state.scheduledCell.size === 0) {
this._scheduleSwitchToIdle(state);
}
}
}
/**
* The function is called on kernel's restarting status message.
* It is used to clear the state tracking the number of executed
* cells.
*
* @param nb - The notebook which contains the executed code cell.
*/
_restartHandler(nb) {
const state = this._notebookExecutionProgress.get(nb);
if (state) {
state.scheduledCell.clear();
this._scheduleSwitchToIdle(state);
}
}
/**
* This function is called on kernel's `execute_input` message to start
* the elapsed time counter.
*
* @param nb - The notebook which contains the scheduled execution request.
*/
_startTimer(nb) {
const state = this._notebookExecutionProgress.get(nb);
if (!state) {
return;
}
if (state.scheduledCell.size > 0) {
if (state.executionStatus !== 'busy') {
state.executionStatus = 'busy';
clearTimeout(state.timeout);
this.stateChanged.emit(void 0);
state.interval = window.setInterval(() => {
this._tick(state);
}, 1000);
}
}
else {
this._resetTime(state);
}
}
/**
* The function is called on kernel's `execute_request` message or Comm message, it is
* used to keep track number of scheduled cell or Comm execution message
* and the status of kernel.
*
* @param nb - The notebook which contains the scheduled code.
* cell
* @param msg_id - The id of message.
*/
_cellScheduledCallback(nb, msg_id) {
const state = this._notebookExecutionProgress.get(nb);
if (state && !state.scheduledCell.has(msg_id)) {
if (state.needReset) {
this._resetTime(state);
}
state.scheduledCell.add(msg_id);
state.scheduledCellNumber += 1;
}
}
/**
* Increment the executed time of input execution state
* and emit `stateChanged` signal to re-render the indicator.
*
* @param data - the state to be updated.
*/
_tick(data) {
data.totalTime += 1;
this.stateChanged.emit(void 0);
}
/**
* Reset the input execution state.
*
* @param data - the state to be rested.
*/
_resetTime(data) {
data.totalTime = 0;
data.scheduledCellNumber = 0;
data.executionStatus = 'idle';
data.scheduledCell = new Set();
clearTimeout(data.timeout);
clearInterval(data.interval);
data.needReset = false;
}
get renderFlag() {
return this._renderFlag;
}
updateRenderOption(options) {
if (this.displayOption.showOnToolBar) {
if (!options.showOnToolBar) {
this._renderFlag = false;
}
else {
this._renderFlag = true;
}
}
this.displayOption.showProgress = options.showProgress;
this.stateChanged.emit(void 0);
}
}
ExecutionIndicator.Model = Model;
function createExecutionIndicatorItem(panel, translator, loadSettings) {
const toolbarItem = new ExecutionIndicator(translator);
toolbarItem.model.displayOption = {
showOnToolBar: true,
showProgress: true
};
toolbarItem.model.attachNotebook({
content: panel.content,
context: panel.sessionContext
});
if (loadSettings) {
loadSettings
.then(settings => {
const updateSettings = (newSettings) => {
toolbarItem.model.updateRenderOption(getSettingValue(newSettings));
};
settings.changed.connect(updateSettings);
updateSettings(settings);
toolbarItem.disposed.connect(() => {
settings.changed.disconnect(updateSettings);
});
})
.catch((reason) => {
console.error(reason.message);
});
}
return toolbarItem;
}
ExecutionIndicator.createExecutionIndicatorItem = createExecutionIndicatorItem;
function getSettingValue(settings) {
let showOnToolBar = true;
let showProgress = true;
const configValues = settings.get('kernelStatus').composite;
if (configValues) {
showOnToolBar = !configValues.showOnStatusBar;
showProgress = configValues.showProgress;
}
return { showOnToolBar, showProgress };
}
ExecutionIndicator.getSettingValue = getSettingValue;
})(ExecutionIndicator || (ExecutionIndicator = {}));
//# sourceMappingURL=executionindicator.js.map