Skip to content

Commit 7c47b05

Browse files
committed
trap ID errors during callback dispatch
so we can clear the pendingCallbacks queue and show errors in the devtools
1 parent 90eb451 commit 7c47b05

File tree

2 files changed

+52
-25
lines changed

2 files changed

+52
-25
lines changed

dash-renderer/src/actions/index.js

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -244,24 +244,31 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
244244
const {output, inputs, state, clientside_function} = cb.callback;
245245
const {requestId, resolvedId} = cb;
246246
const {allOutputs, allPropIds} = outputStash[requestId];
247-
const outputs = allOutputs.map((out, i) =>
248-
unwrapIfNotMulti(
249-
paths,
250-
map(pick(['id', 'property']), out),
251-
cb.callback.outputs[i],
252-
cb.anyVals,
253-
'Output'
254-
)
255-
);
256247

257-
const payload = {
258-
output,
259-
outputs: isMultiOutputProp(output) ? outputs : outputs[0],
260-
inputs: fillVals(paths, layout, cb, inputs, 'Input'),
261-
changedPropIds: keys(cb.changedPropIds),
262-
};
263-
if (cb.callback.state.length) {
264-
payload.state = fillVals(paths, layout, cb, state, 'State');
248+
let payload;
249+
try {
250+
const outputs = allOutputs.map((out, i) =>
251+
unwrapIfNotMulti(
252+
paths,
253+
map(pick(['id', 'property']), out),
254+
cb.callback.outputs[i],
255+
cb.anyVals,
256+
'Output'
257+
)
258+
);
259+
260+
payload = {
261+
output,
262+
outputs: isMultiOutputProp(output) ? outputs : outputs[0],
263+
inputs: fillVals(paths, layout, cb, inputs, 'Input'),
264+
changedPropIds: keys(cb.changedPropIds),
265+
};
266+
if (cb.callback.state.length) {
267+
payload.state = fillVals(paths, layout, cb, state, 'State');
268+
}
269+
} catch (e) {
270+
handleError(e);
271+
return fireNext();
265272
}
266273

267274
function updatePending(pendingCallbacks, skippedProps) {
@@ -361,10 +368,10 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
361368
// that have other changed inputs will still fire.
362369
updatePending(pendingCallbacks, allPropIds);
363370
}
364-
let message = `Callback error updating ${map(
365-
combineIdAndProp,
366-
flatten([payload.outputs])
367-
).join(', ')}`;
371+
const outputs = payload
372+
? map(combineIdAndProp, flatten([payload.outputs])).join(', ')
373+
: output;
374+
let message = `Callback error updating ${outputs}`;
368375
if (clientside_function) {
369376
const {namespace: ns, function_name: fn} = clientside_function;
370377
message += ` via clientside function ${ns}.${fn}`;

tests/integration/devtools/test_callback_validation.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def check_errors(dash_duo, specs):
1919
for i in range(cnt):
2020
msg = dash_duo.find_elements(".dash-fe-error__title")[i].text
2121
dash_duo.find_elements(".test-devtools-error-toggle")[i].click()
22-
txt = dash_duo.wait_for_element(".dash-backend-error").text
22+
txt = dash_duo.wait_for_element(".dash-backend-error,.dash-fe-error__info").text
2323
dash_duo.find_elements(".test-devtools-error-toggle")[i].click()
2424
dash_duo.wait_for_no_elements(".dash-backend-error")
2525
found.append((msg, txt))
@@ -47,6 +47,9 @@ def check_errors(dash_duo, specs):
4747
)
4848
)
4949

50+
# ensure the errors didn't leave items in the pendingCallbacks queue
51+
assert dash_duo.driver.execute_script('return document.title') == 'Dash'
52+
5053

5154
def test_dvcv001_blank(dash_duo):
5255
app = Dash(__name__)
@@ -412,6 +415,24 @@ def h(a):
412415
return app
413416

414417

418+
# These ones are raised by bad_id_app whether suppressing callback exceptions or not
419+
dispatch_specs = [
420+
[
421+
"A nonexistent object was used in an `Input` of a Dash callback. "
422+
"The id of this object is `yeah-no` and the property is `value`. "
423+
"The string ids in the current layout are: "
424+
"[main, outer-div, inner-div, inner-input, outer-input]", []
425+
],
426+
[
427+
"A nonexistent object was used in an `Output` of a Dash callback. "
428+
"The id of this object is `nope` and the property is `children`. "
429+
"The string ids in the current layout are: "
430+
"[main, outer-div, inner-div, inner-input, outer-input]", []
431+
]
432+
]
433+
434+
435+
415436
def test_dvcv008_wrong_callback_id(dash_duo):
416437
dash_duo.start_server(bad_id_app(), **debugging)
417438

@@ -461,14 +482,13 @@ def test_dvcv008_wrong_callback_id(dash_duo):
461482
],
462483
],
463484
]
464-
check_errors(dash_duo, specs)
485+
check_errors(dash_duo, dispatch_specs + specs)
465486

466487

467488
def test_dvcv009_suppress_callback_exceptions(dash_duo):
468489
dash_duo.start_server(bad_id_app(suppress_callback_exceptions=True), **debugging)
469490

470-
dash_duo.find_element(".dash-debug-menu")
471-
dash_duo.wait_for_no_elements(".test-devtools-error-count")
491+
check_errors(dash_duo, dispatch_specs)
472492

473493

474494
def test_dvcv010_bad_props(dash_duo):

0 commit comments

Comments
 (0)