Skip to content

Commit 7452d14

Browse files
authored
Merge pull request #22 from mwakaba2/notify-only-last-cell
Option to only get notified for the last selected cell execution
2 parents 63500d8 + 68e296b commit 7452d14

File tree

5 files changed

+172
-82
lines changed

5 files changed

+172
-82
lines changed

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,31 @@ Use the following settings to update cell execution time for a notification and
4949
// Settings for the Notifications extension
5050
// ****************************************
5151

52+
// Cell Number Type
53+
// Type of cell number to display when the report_cell_number is true. Select from 'cell_index' or ‘cell_execution_count'.
54+
"cell_number_type": "cell_index",
55+
5256
// Enabled Status
5357
// Enable the extension or not.
5458
"enabled": true,
5559

60+
// Trigger only for the last selected notebook cell execution.
61+
// Trigger a notification only for the last selected executed notebook cell.
62+
// NOTE: Only Available in version >= v0.3.0
63+
"last_cell_only": false,
64+
5665
// Minimum Notebook Cell Execution Time
5766
// The minimum execution time to send out notification for a particular notebook cell (in seconds).
5867
"minimum_cell_execution_time": 60,
5968

6069
// Report Notebook Cell Execution Time
61-
// Display notebook cell execution time in the notification.
70+
// Display notebook cell execution time in the notification.
71+
// If last_cell_only is set to true, the total duration of the selected cells will be displayed.
6272
"report_cell_execution_time": true,
6373

6474
// Report Notebook Cell Number
6575
// Display notebook cell number in the notification.
66-
"report_cell_number": true,
67-
68-
// Cell Number Type
69-
// Type of cell number to display when the report_cell_number is true. Select from 'cell_index' or ‘cell_execution_count'.
70-
"cell_number_type": "cell_index"
76+
"report_cell_number": true
7177
}
7278
```
7379

binder/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ channels:
44
dependencies:
55
- jupyterlab=3
66
- pip:
7-
- jupyterlab-notifications==0.2.2
7+
- jupyterlab-notifications==0.3.0

schema/plugin.json

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"report_cell_execution_time": {
1919
"type": "boolean",
2020
"title": "Report Notebook Cell Execution Time",
21-
"description": "Display notebook cell execution time in the notification.",
21+
"description": "Display notebook cell execution time in the notification. If last_cell_only is set to true, the total duration of the selected cells will be displayed.",
2222
"default": true
2323
},
2424
"report_cell_number": {
@@ -28,11 +28,17 @@
2828
"default": true
2929
},
3030
"cell_number_type": {
31-
"type": "string",
32-
"title": "Cell Number Type",
33-
"description": "Type of cell number to display when the report_cell_number is true. Select from 'cell_index' or ‘cell_execution_count'.",
34-
"enum": ["cell_index", "cell_execution_count"],
35-
"default": "cell_index"
36-
}
31+
"type": "string",
32+
"title": "Cell Number Type",
33+
"description": "Type of cell number to display when the report_cell_number is true. Select from 'cell_index' or ‘cell_execution_count'.",
34+
"enum": ["cell_index", "cell_execution_count"],
35+
"default": "cell_index"
36+
},
37+
"last_cell_only": {
38+
"type": "boolean",
39+
"title": "Trigger only for the last selected notebook cell execution.",
40+
"description": "Trigger a notification only for the last selected executed notebook cell.",
41+
"default": false
42+
}
3743
}
3844
}

src/index.ts

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import { checkBrowserNotificationSettings } from './settings';
1313
interface ICellExecutionMetadata {
1414
index: number;
1515
scheduledTime: Date;
16-
endTime?: Date;
17-
startTime?: Date;
1816
}
1917

2018
/**
@@ -27,7 +25,8 @@ function displayNotification(
2725
reportCellNumber: boolean,
2826
reportCellExecutionTime: boolean,
2927
failedExecution: boolean,
30-
error: KernelError | null
28+
error: KernelError | null,
29+
lastCellOnly: boolean
3130
): void {
3231
const notificationPayload = {
3332
icon: '/static/favicon.ico',
@@ -40,6 +39,8 @@ function displayNotification(
4039

4140
if (failedExecution) {
4241
message = error ? `${error.errorName} ${error.errorValue}` : '';
42+
} else if (lastCellOnly) {
43+
message = `Total Duration: ${cellDuration}`;
4344
} else if (reportCellNumber && reportCellExecutionTime) {
4445
message = `Cell[${cellNumber}] Duration: ${cellDuration}`;
4546
} else if (reportCellNumber) {
@@ -58,36 +59,58 @@ function displayNotification(
5859
function triggerNotification(
5960
cell: Cell,
6061
notebook: Notebook,
61-
executionMetadata: ICellExecutionMetadata,
62+
cellExecutionMetadataTable: LRU<string, ICellExecutionMetadata>,
63+
recentNotebookExecutionTimes: LRU<string, Date>,
6264
minimumCellExecutionTime: number,
6365
reportCellNumber: boolean,
6466
reportCellExecutionTime: boolean,
6567
cellNumberType: string,
6668
failedExecution: boolean,
67-
error: KernelError | null
69+
error: KernelError | null,
70+
lastCellOnly: boolean
6871
) {
69-
const { startTime, endTime, index: cellIndex } = executionMetadata;
72+
const cellEndTime = new Date();
7073
const codeCellModel = cell.model as ICodeCellModel;
71-
const cellDuration = moment
72-
.utc(moment(endTime).diff(startTime))
73-
.format('HH:mm:ss');
74-
const diffSeconds = moment.duration(cellDuration).asSeconds();
75-
console.log(cellDuration, diffSeconds);
76-
if (diffSeconds >= minimumCellExecutionTime) {
77-
const cellNumber =
78-
cellNumberType === 'cell_index'
79-
? cellIndex
80-
: codeCellModel.executionCount;
81-
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
82-
displayNotification(
83-
cellDuration,
84-
cellNumber,
85-
notebookName,
86-
reportCellNumber,
87-
reportCellExecutionTime,
88-
failedExecution,
89-
error
90-
);
74+
const codeCell = codeCellModel.type === 'code';
75+
const nonEmptyCell = codeCellModel.value.text.length > 0;
76+
if (codeCell && nonEmptyCell) {
77+
const cellId = codeCellModel.id;
78+
const notebookId = notebook.id;
79+
const cellExecutionMetadata = cellExecutionMetadataTable.get(cellId);
80+
const scheduledTime = cellExecutionMetadata.scheduledTime;
81+
// Get the cell's execution scheduled time if the recent notebook execution state doesn't exist.
82+
// This happens commonly for first time notebook executions or notebooks that haven't been executed for a while.
83+
const recentExecutedCellTime =
84+
recentNotebookExecutionTimes.get(notebookId) || scheduledTime;
85+
86+
// Multiple cells can be scheduled at the same time, and the schedule time doesn't necessarily equate to the actual start time.
87+
// If another cell has been executed more recently than the current cell's scheduled time, treat the recent execution as the cell's start time.
88+
const cellStartTime =
89+
scheduledTime >= recentExecutedCellTime
90+
? scheduledTime
91+
: recentExecutedCellTime;
92+
recentNotebookExecutionTimes.set(notebookId, cellEndTime);
93+
const cellDuration = moment
94+
.utc(moment(cellEndTime).diff(cellStartTime))
95+
.format('HH:mm:ss');
96+
const diffSeconds = moment.duration(cellDuration).asSeconds();
97+
if (diffSeconds >= minimumCellExecutionTime) {
98+
const cellNumber =
99+
cellNumberType === 'cell_index'
100+
? cellExecutionMetadata.index
101+
: codeCellModel.executionCount;
102+
const notebookName = notebook.title.label.replace(/\.[^/.]+$/, '');
103+
displayNotification(
104+
cellDuration,
105+
cellNumber,
106+
notebookName,
107+
reportCellNumber,
108+
reportCellExecutionTime,
109+
failedExecution,
110+
error,
111+
lastCellOnly
112+
);
113+
}
91114
}
92115
}
93116

@@ -102,6 +125,7 @@ const extension: JupyterFrontEndPlugin<void> = {
102125
let reportCellExecutionTime = true;
103126
let reportCellNumber = true;
104127
let cellNumberType = 'cell_index';
128+
let lastCellOnly = false;
105129
const cellExecutionMetadataTable: LRU<
106130
string,
107131
ICellExecutionMetadata
@@ -123,6 +147,7 @@ const extension: JupyterFrontEndPlugin<void> = {
123147
reportCellNumber = setting.get('report_cell_number')
124148
.composite as boolean;
125149
cellNumberType = setting.get('cell_number_type').composite as string;
150+
lastCellOnly = setting.get('last_cell_only').composite as boolean;
126151
};
127152
updateSettings();
128153
setting.changed.connect(updateSettings);
@@ -139,42 +164,41 @@ const extension: JupyterFrontEndPlugin<void> = {
139164
});
140165

141166
NotebookActions.executed.connect((_, args) => {
142-
if (enabled) {
143-
const cellEndTime = new Date();
167+
if (enabled && !lastCellOnly) {
144168
const { cell, notebook, success, error } = args;
145-
const codeCell = cell.model.type === 'code';
146-
const nonEmptyCell = cell.model.value.text.length > 0;
147-
if (codeCell && nonEmptyCell) {
148-
const cellId = cell.model.id;
149-
const notebookId = notebook.id;
150-
const cellExecutionMetadata = cellExecutionMetadataTable.get(cellId);
151-
const scheduledTime = cellExecutionMetadata.scheduledTime;
152-
// Get the cell's execution scheduled time if the recent notebook execution state doesn't exist.
153-
// This happens commonly for first time notebook executions or notebooks that haven't been executed for a while.
154-
const recentExecutedCellTime =
155-
recentNotebookExecutionTimes.get(notebookId) || scheduledTime;
156-
157-
// Multiple cells can be scheduled at the same time, and the schedule time doesn't necessarily equate to the actual start time.
158-
// If another cell has been executed more recently than the current cell's scheduled time, treat the recent execution as the cell's start time.
159-
cellExecutionMetadata.startTime =
160-
scheduledTime >= recentExecutedCellTime
161-
? scheduledTime
162-
: recentExecutedCellTime;
163-
cellExecutionMetadata.endTime = cellEndTime;
164-
recentNotebookExecutionTimes.set(notebookId, cellEndTime);
169+
triggerNotification(
170+
cell,
171+
notebook,
172+
cellExecutionMetadataTable,
173+
recentNotebookExecutionTimes,
174+
minimumCellExecutionTime,
175+
reportCellNumber,
176+
reportCellExecutionTime,
177+
cellNumberType,
178+
!success,
179+
error,
180+
lastCellOnly
181+
);
182+
}
183+
});
165184

166-
triggerNotification(
167-
cell,
168-
notebook,
169-
cellExecutionMetadata,
170-
minimumCellExecutionTime,
171-
reportCellNumber,
172-
reportCellExecutionTime,
173-
cellNumberType,
174-
!success,
175-
error
176-
);
177-
}
185+
NotebookActions.selectionExecuted.connect((_, args) => {
186+
if (enabled && lastCellOnly) {
187+
const { lastCell, notebook } = args;
188+
const failedExecution = false;
189+
triggerNotification(
190+
lastCell,
191+
notebook,
192+
cellExecutionMetadataTable,
193+
recentNotebookExecutionTimes,
194+
minimumCellExecutionTime,
195+
reportCellNumber,
196+
reportCellExecutionTime,
197+
cellNumberType,
198+
failedExecution,
199+
null,
200+
lastCellOnly
201+
);
178202
}
179203
});
180204
}

tutorial/py3_demo.ipynb

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,74 @@
5252
}
5353
],
5454
"source": [
55-
"!sleep 66\n",
55+
"!sleep 1\n",
5656
"print(\"hello world!\")"
5757
]
5858
},
5959
{
6060
"cell_type": "code",
61-
"execution_count": 99,
61+
"execution_count": 62,
6262
"metadata": {
6363
"execution": {
64-
"iopub.execute_input": "2021-07-25T19:55:35.654800Z",
65-
"iopub.status.busy": "2021-07-25T19:55:35.654567Z",
66-
"iopub.status.idle": "2021-07-25T19:55:38.881008Z",
67-
"shell.execute_reply": "2021-07-25T19:55:38.879628Z",
68-
"shell.execute_reply.started": "2021-07-25T19:55:35.654769Z"
64+
"iopub.execute_input": "2021-07-18T19:49:34.088207Z",
65+
"iopub.status.busy": "2021-07-18T19:49:34.087802Z",
66+
"iopub.status.idle": "2021-07-18T19:49:36.219369Z",
67+
"shell.execute_reply": "2021-07-18T19:49:36.218766Z",
68+
"shell.execute_reply.started": "2021-07-18T19:49:34.088134Z"
69+
},
70+
"tags": []
71+
},
72+
"outputs": [
73+
{
74+
"name": "stdout",
75+
"output_type": "stream",
76+
"text": [
77+
"hello world again!\n"
78+
]
79+
}
80+
],
81+
"source": [
82+
"!sleep 2\n",
83+
"print(\"hello world again!\")"
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": 63,
89+
"metadata": {
90+
"execution": {
91+
"iopub.execute_input": "2021-07-18T19:49:36.221581Z",
92+
"iopub.status.busy": "2021-07-18T19:49:36.221322Z",
93+
"iopub.status.idle": "2021-07-18T19:49:41.353081Z",
94+
"shell.execute_reply": "2021-07-18T19:49:41.352358Z",
95+
"shell.execute_reply.started": "2021-07-18T19:49:36.221557Z"
96+
},
97+
"tags": []
98+
},
99+
"outputs": [
100+
{
101+
"name": "stdout",
102+
"output_type": "stream",
103+
"text": [
104+
"bye world!\n"
105+
]
106+
}
107+
],
108+
"source": [
109+
"!sleep 5\n",
110+
"print(\"bye world!\")"
111+
]
112+
},
113+
{
114+
"cell_type": "code",
115+
"execution_count": 40,
116+
"metadata": {
117+
"execution": {
118+
"iopub.execute_input": "2021-07-18T19:41:58.007998Z",
119+
"iopub.status.busy": "2021-07-18T19:41:58.007728Z",
120+
"iopub.status.idle": "2021-07-18T19:42:01.145486Z",
121+
"shell.execute_reply": "2021-07-18T19:42:01.144436Z",
122+
"shell.execute_reply.started": "2021-07-18T19:41:58.007971Z"
69123
},
70124
"tags": []
71125
},
@@ -77,7 +131,7 @@
77131
"traceback": [
78132
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
79133
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
80-
"\u001b[0;32m<ipython-input-99-d820d165a50b>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'sleep 3'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'hello world!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
134+
"\u001b[0;32m<ipython-input-40-d820d165a50b>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'sleep 3'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'hello world!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
81135
"\u001b[0;31mException\u001b[0m: hello world!"
82136
]
83137
}

0 commit comments

Comments
 (0)