Skip to content

Commit 0cdf671

Browse files
committed
Add progress indicator to featurization
1 parent 5d326be commit 0cdf671

File tree

6 files changed

+114
-1
lines changed

6 files changed

+114
-1
lines changed

cesium_app/handlers/feature.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from baselayer.app.access import auth_or_token
99
from ..models import DBSession, Dataset, Featureset, Project
1010

11+
from .progressbar import WebSocketProgressBar
12+
1113
from os.path import join as pjoin
1214
import uuid
1315
import datetime
@@ -32,7 +34,12 @@ async def _await_featurization(self, future, fset):
3234
That said, we can push notifications through to the frontend
3335
using flow.
3436
"""
37+
def progressbar_update(payload):
38+
payload.update({'fsetID': fset.id})
39+
self.action('cesium/FEATURIZE_PROGRESS', payload=payload)
40+
3541
try:
42+
WebSocketProgressBar([future], progressbar_update, interval=2)
3643
result = await future
3744

3845
fset = DBSession().merge(fset)

cesium_app/handlers/progressbar.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Derived from `distributed.diagnostics.progressbar` which is
2+
3+
# Copyright (c) 2015-2017, Anaconda, Inc. and contributors
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions
8+
# are met:
9+
#
10+
# Redistributions of source code must retain the above copyright
11+
# notice, this list of conditions and the following disclaimer.
12+
#
13+
# Redistributions in binary form must reproduce the above copyright
14+
# notice, this list of conditions and the following disclaimer in the
15+
# documentation and/or other materials provided with the distribution.
16+
#
17+
# Neither the name of Anaconda nor the names of any contributors may
18+
# be used to endorse or promote products derived from this software
19+
# without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31+
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
# POSSIBILITY OF SUCH DAMAGE.
33+
34+
35+
from contextlib import contextmanager
36+
37+
from distributed.diagnostics.progressbar import ProgressBar
38+
from distributed.utils import LoopRunner
39+
40+
from tornado.ioloop import IOLoop
41+
42+
import sys
43+
44+
45+
def format_time(t):
46+
"Format seconds into a human readable form."
47+
m, s = divmod(t, 60)
48+
h, m = divmod(m, 60)
49+
return f'{int(h):02d}:{int(m):02d}:{int(s):02d}'
50+
51+
52+
class WebSocketProgressBar(ProgressBar):
53+
def __init__(self, keys, update_callback, scheduler=None, interval=1,
54+
loop=None, complete=True, start=True):
55+
super(WebSocketProgressBar, self).__init__(keys, scheduler, interval,
56+
complete)
57+
self.update_callback = update_callback
58+
self.loop = loop or IOLoop.current()
59+
60+
if start:
61+
self.loop.add_callback(self.listen)
62+
63+
def _draw_bar(self, remaining, all, **kwargs):
64+
frac = (1 - remaining / all) if all else 1.0
65+
percent = frac * 100
66+
elapsed = format_time(self.elapsed)
67+
self.update_callback({'percent': f'{percent:2.1f}', 'elapsed': elapsed})

static/js/CesiumMessageHandler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ let CesiumMessageHandler = dispatch => {
2323
case Action.FETCH_PREDICTIONS:
2424
dispatch(Action.fetchPredictions());
2525
break;
26+
case Action.FEATURIZE_PROGRESS:
27+
let time_update = message.payload;
28+
dispatch(Action.featurizeUpdateProgress(time_update));
29+
break;
2630
default:
2731
console.log('Unknown message received through flow:',
2832
message);

static/js/actions.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export const CLICK_FEATURE_TAG_CHECKBOX = 'cesium/CLICK_FEATURE_TAG_CHECKBOX';
4949
export const FETCH_USER_PROFILE = 'cesium/FETCH_USER_PROFILE';
5050
export const RECEIVE_USER_PROFILE = 'cesium/FETCH_USER_PROFILE';
5151

52+
export const FEATURIZE_PROGRESS = 'cesium/FEATURIZE_PROGRESS';
53+
5254
import { showNotification, reduceNotifications } from 'baselayer/components/Notifications';
5355
import promiseAction from './action_tools';
5456
import { objectType } from './utils';
@@ -294,6 +296,13 @@ function receiveFeaturesets(featuresets) {
294296
};
295297
}
296298

299+
// Receive updates on featurization
300+
export function featurizeUpdateProgress(time_update) {
301+
return {
302+
type: FEATURIZE_PROGRESS,
303+
payload: time_update
304+
};
305+
}
297306

298307
export function createModel(form) {
299308
return dispatch =>

static/js/components/Features.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,19 @@ export let FeatureTable = props => (
224224
</td>
225225
</tr>);
226226

227-
const status = done ? <td>Completed {reformatDatetime(featureset.finished)}</td> : <td>In progress</td>;
227+
let elapsed = "", percent = "";
228+
if (featureset.progress) {
229+
({ elapsed, percent } = { ...featureset.progress });
230+
}
231+
232+
let status;
233+
if (done) {
234+
status = <td>Completed { reformatDatetime(featureset.finished) }</td>;
235+
} else if (elapsed == "") {
236+
status = <td>In progress...</td>;
237+
} else {
238+
status = <td>In progress: { percent }%, { elapsed }s</td>;
239+
}
228240

229241
return (
230242
<FoldableRow key={idx}>

static/js/reducers.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ function featuresets(state=[], action) {
4040
switch (action.type) {
4141
case Action.RECEIVE_FEATURESETS:
4242
return action.payload;
43+
case Action.FEATURIZE_PROGRESS:
44+
const newState = [ ...state ];
45+
46+
const { percent, elapsed } = { ...action.payload };
47+
const featureIdx = newState.findIndex((element) => (
48+
element.id == action.payload.fsetID
49+
));
50+
51+
if (featureIdx != -1) {
52+
const feature = newState[featureIdx];
53+
feature.progress = {percent, elapsed};
54+
}
55+
56+
return newState;
4357
default:
4458
return state;
4559
}

0 commit comments

Comments
 (0)