Skip to content

Commit 176ca54

Browse files
authored
Merge pull request #19 from mwakaba2/fix-errored-cell-notification
Fix errored cell notification
2 parents ae237a2 + ad3312a commit 176ca54

File tree

7 files changed

+1138
-504
lines changed

7 files changed

+1138
-504
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ Notebook Cell Completion Browser Notifications for JupyterLab.
1414

1515
* Web Browser that supports the Notification Web API (See [Browser Compatibility Chart](https://developer.mozilla.org/en-US/docs/Web/API/notification#browser_compatibility))
1616
* JupyterLab >= 3.0
17-
* Notebook Cell Timing needs to be enabled for Jupyterlab Notifications to work. Please go to Settings -> Advanced Settings Editor -> Notebook and update setting to:
17+
* Notebook Cell Timing needs to be enabled for Jupyterlab Notifications with version < `0.3.0` to work. Please go to Settings -> Advanced Settings Editor -> Notebook and update setting to:
1818
```json5
1919
{
2020
// Recording timing
2121
// Should timing data be recorded in cell metadata
2222
"recordTiming": true
2323
}
2424
```
25+
* [BETA] the cell timing doesn't need to be enabled for Jupyterlab >= 3.1 and Jupyterlab notification version 0.3.0.
2526

2627
## Install
2728

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"dependencies": {
4848
"@jupyterlab/application": "^3.0.6",
49-
"@jupyterlab/notebook": "^3.0.6",
49+
"@jupyterlab/notebook": "^3.1.0-beta.1",
5050
"@jupyterlab/settingregistry": "^3.0.3"
5151
},
5252
"devDependencies": {

src/index.ts

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,13 @@ import {
22
JupyterFrontEnd,
33
JupyterFrontEndPlugin
44
} from '@jupyterlab/application';
5-
import { NotebookActions } from '@jupyterlab/notebook';
6-
import { IObservableJSON } from '@jupyterlab/observables';
5+
import { KernelError, Notebook, NotebookActions } from '@jupyterlab/notebook';
6+
import { Cell } from '@jupyterlab/cells';
77
import { ISettingRegistry } from '@jupyterlab/settingregistry';
88
import { ICodeCellModel } from '@jupyterlab/cells';
99

1010
import { checkBrowserNotificationSettings } from './settings';
1111

12-
/**
13-
* Extracts Code Cell Start and End Time
14-
*/
15-
function extractExecutionMetadata(metadata: IObservableJSON): [Date, Date] {
16-
const executionMetadata = Object.assign({}, metadata.get('execution') as any);
17-
const cellStartTime = new Date(
18-
executionMetadata['shell.execute_reply.started'] ||
19-
executionMetadata['iopub.execute_input']
20-
);
21-
const cellEndTime = new Date(executionMetadata['shell.execute_reply']);
22-
return [cellStartTime, cellEndTime];
23-
}
24-
2512
/**
2613
* Constructs notification message and displays it.
2714
*/
@@ -30,14 +17,22 @@ function displayNotification(
3017
cellNumber: number,
3118
notebookName: string,
3219
reportCellNumber: boolean,
33-
reportCellExecutionTime: boolean
20+
reportCellExecutionTime: boolean,
21+
failedExecution: boolean,
22+
error: KernelError | null
3423
): void {
3524
const notificationPayload = {
3625
icon: '/static/favicon.ico',
3726
body: ''
3827
};
28+
const title = failedExecution
29+
? `${notebookName} Failed!`
30+
: `${notebookName} Completed!`;
3931
let message = '';
40-
if (reportCellNumber && reportCellExecutionTime) {
32+
33+
if (failedExecution) {
34+
message = error ? `${error.errorName} ${error.errorValue}` : '';
35+
} else if (reportCellNumber && reportCellExecutionTime) {
4136
message = `Cell[${cellNumber}] Duration: ${cellDuration}`;
4237
} else if (reportCellNumber) {
4338
message = `Cell Number: ${cellNumber}`;
@@ -46,20 +41,62 @@ function displayNotification(
4641
}
4742

4843
notificationPayload.body = message;
49-
new Notification(`${notebookName}`, notificationPayload);
44+
new Notification(title, notificationPayload);
45+
}
46+
47+
/**
48+
* Trigger notification.
49+
*/
50+
function triggerNotification(
51+
cell: Cell,
52+
notebook: Notebook,
53+
cellStartTime: Date,
54+
cellEndTime: Date,
55+
minimumCellExecutionTime: number,
56+
reportCellNumber: boolean,
57+
reportCellExecutionTime: boolean,
58+
cellNumberType: string,
59+
failedExecution: boolean,
60+
error: KernelError | null
61+
) {
62+
const codeCell = cell.model.type === 'code';
63+
const nonEmptyCell = cell.model.value.text.length > 0;
64+
if (codeCell && nonEmptyCell) {
65+
const codeCellModel = cell.model as ICodeCellModel;
66+
const diff = new Date(<any>cellEndTime - <any>cellStartTime);
67+
const diffSeconds = Math.floor(diff.getTime() / 1000);
68+
if (diffSeconds >= minimumCellExecutionTime) {
69+
const cellDuration = diff.toISOString().substr(11, 8);
70+
const cellNumber =
71+
cellNumberType === 'cell_index'
72+
? notebook.activeCellIndex
73+
: codeCellModel.executionCount;
74+
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
75+
displayNotification(
76+
cellDuration,
77+
cellNumber,
78+
notebookName,
79+
reportCellNumber,
80+
reportCellExecutionTime,
81+
failedExecution,
82+
error
83+
);
84+
}
85+
}
5086
}
5187

5288
const extension: JupyterFrontEndPlugin<void> = {
5389
id: 'jupyterlab-notifications:plugin',
5490
autoStart: true,
55-
optional: [ISettingRegistry],
91+
requires: [ISettingRegistry],
5692
activate: async (app: JupyterFrontEnd, settingRegistry: ISettingRegistry) => {
5793
checkBrowserNotificationSettings();
5894
let enabled = true;
5995
let minimumCellExecutionTime = 60;
6096
let reportCellExecutionTime = true;
6197
let reportCellNumber = true;
6298
let cellNumberType = 'cell_index';
99+
63100
if (settingRegistry) {
64101
const setting = await settingRegistry.load(extension.id);
65102
const updateSettings = (): void => {
@@ -76,45 +113,30 @@ const extension: JupyterFrontEndPlugin<void> = {
76113
setting.changed.connect(updateSettings);
77114
}
78115

116+
let cellStartTime = new Date();
117+
118+
NotebookActions.executionScheduled.connect((_, args) => {
119+
if (enabled) {
120+
cellStartTime = new Date();
121+
}
122+
});
123+
79124
NotebookActions.executed.connect((_, args) => {
80125
if (enabled) {
81-
const { cell, notebook } = args;
82-
const codeCell = cell.model.type === 'code';
83-
const nonEmptyCell = cell.model.value.text.length > 0;
84-
const metadata = cell.model.metadata;
85-
if (codeCell && nonEmptyCell) {
86-
const codeCellModel = cell.model as ICodeCellModel;
87-
if (metadata.has('execution')) {
88-
const [cellStartTime, cellEndTime] = extractExecutionMetadata(
89-
metadata
90-
);
91-
const diff = new Date(<any>cellEndTime - <any>cellStartTime);
92-
const diffSeconds = Math.floor(diff.getTime() / 1000);
93-
if (diffSeconds >= minimumCellExecutionTime) {
94-
const cellDuration = diff.toISOString().substr(11, 8);
95-
const cellNumber =
96-
cellNumberType === 'cell_index'
97-
? notebook.activeCellIndex
98-
: codeCellModel.executionCount;
99-
const notebookName = notebook.title.label.replace(
100-
/\.[^/.]+$/,
101-
''
102-
);
103-
displayNotification(
104-
cellDuration,
105-
cellNumber,
106-
notebookName,
107-
reportCellNumber,
108-
reportCellExecutionTime
109-
);
110-
}
111-
} else {
112-
alert(
113-
'Notebook Cell Timing needs to be enabled for Jupyterlab Notifications to work. ' +
114-
'Please go to Settings -> Advanced Settings Editor -> Notebook and update setting to {"recordTiming": true}'
115-
);
116-
}
117-
}
126+
const { cell, notebook, success, error } = args;
127+
const cellEndTime = new Date();
128+
triggerNotification(
129+
cell,
130+
notebook,
131+
cellStartTime,
132+
cellEndTime,
133+
minimumCellExecutionTime,
134+
reportCellNumber,
135+
reportCellExecutionTime,
136+
cellNumberType,
137+
!success,
138+
error
139+
);
118140
}
119141
});
120142
}
51.1 KB
Loading

tutorial/notebook_notification_py3_demo.ipynb

Lines changed: 0 additions & 172 deletions
This file was deleted.

0 commit comments

Comments
 (0)