@@ -6,9 +6,17 @@ import { KernelError, Notebook, NotebookActions } from '@jupyterlab/notebook';
66import { Cell } from '@jupyterlab/cells' ;
77import { ISettingRegistry } from '@jupyterlab/settingregistry' ;
88import { ICodeCellModel } from '@jupyterlab/cells' ;
9-
9+ import LRU from 'lru-cache' ;
10+ import moment from 'moment' ;
1011import { checkBrowserNotificationSettings } from './settings' ;
1112
13+ interface ICellExecutionMetadata {
14+ index : number ;
15+ scheduledTime : Date ;
16+ endTime ?: Date ;
17+ startTime ?: Date ;
18+ }
19+
1220/**
1321 * Constructs notification message and displays it.
1422 */
@@ -50,38 +58,36 @@ function displayNotification(
5058function triggerNotification (
5159 cell : Cell ,
5260 notebook : Notebook ,
53- cellStartTime : Date ,
54- cellEndTime : Date ,
61+ executionMetadata : ICellExecutionMetadata ,
5562 minimumCellExecutionTime : number ,
5663 reportCellNumber : boolean ,
5764 reportCellExecutionTime : boolean ,
5865 cellNumberType : string ,
5966 failedExecution : boolean ,
6067 error : KernelError | null
6168) {
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- }
69+ const { startTime, endTime, index : cellIndex } = executionMetadata ;
70+ 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+ ) ;
8591 }
8692}
8793
@@ -96,6 +102,15 @@ const extension: JupyterFrontEndPlugin<void> = {
96102 let reportCellExecutionTime = true ;
97103 let reportCellNumber = true ;
98104 let cellNumberType = 'cell_index' ;
105+ const cellExecutionMetadataTable : LRU <
106+ string ,
107+ ICellExecutionMetadata
108+ > = new LRU ( {
109+ max : 500 * 5 // to save 500 notebooks x 5 cells
110+ } ) ;
111+ const recentNotebookExecutionTimes : LRU < string , Date > = new LRU ( {
112+ max : 500
113+ } ) ;
99114
100115 if ( settingRegistry ) {
101116 const setting = await settingRegistry . load ( extension . id ) ;
@@ -113,30 +128,53 @@ const extension: JupyterFrontEndPlugin<void> = {
113128 setting . changed . connect ( updateSettings ) ;
114129 }
115130
116- let cellStartTime = new Date ( ) ;
117-
118131 NotebookActions . executionScheduled . connect ( ( _ , args ) => {
132+ const { cell, notebook } = args ;
119133 if ( enabled ) {
120- cellStartTime = new Date ( ) ;
134+ cellExecutionMetadataTable . set ( cell . model . id , {
135+ index : notebook . activeCellIndex ,
136+ scheduledTime : new Date ( )
137+ } ) ;
121138 }
122139 } ) ;
123140
124141 NotebookActions . executed . connect ( ( _ , args ) => {
125142 if ( enabled ) {
126- const { cell, notebook, success, error } = args ;
127143 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- ) ;
144+ 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 ) ;
165+
166+ triggerNotification (
167+ cell ,
168+ notebook ,
169+ cellExecutionMetadata ,
170+ minimumCellExecutionTime ,
171+ reportCellNumber ,
172+ reportCellExecutionTime ,
173+ cellNumberType ,
174+ ! success ,
175+ error
176+ ) ;
177+ }
140178 }
141179 } ) ;
142180 }
0 commit comments