Skip to content

Commit

Permalink
Merge pull request #1237 from plotly/connection-status
Browse files Browse the repository at this point in the history
API connection status notifications
  • Loading branch information
alexcjohnson authored May 14, 2020
2 parents 7043c13 + 441a9ac commit 79c6f30
Show file tree
Hide file tree
Showing 23 changed files with 402 additions and 312 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- [#1240](https://github.com/plotly/dash/pull/1240) Adds `callback_context` to clientside callbacks (e.g. `dash_clientside.callback_context.triggered`). Supports `triggered`, `inputs`, `inputs_list`, `states`, and `states_list`, all of which closely resemble their serverside cousins.

### Changed
- [#1237](https://github.com/plotly/dash/pull/1237) Closes [#920](https://github.com/plotly/dash/issues/920): Converts hot reload fetch failures into a server status indicator showing whether the latest fetch succeeded or failed. Callback fetch failures still appear as errors but have a clearer message.

## [1.12.0] - 2020-05-05
### Added
- [#1228](https://github.com/plotly/dash/pull/1228) Adds control over firing callbacks on page (or layout chunk) load. Individual callbacks can have their initial calls disabled in their definition `@app.callback(..., prevent_initial_call=True)` and similar for `app.clientside_callback`. The app-wide default can also be changed with `app=Dash(prevent_initial_callbacks=True)`, then individual callbacks may disable this behavior.
Expand Down
72 changes: 45 additions & 27 deletions dash-renderer/src/actions/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,43 +30,61 @@ const request = {GET, POST};

export default function apiThunk(endpoint, method, store, id, body) {
return (dispatch, getState) => {
const config = getState().config;
const {config} = getState();
const url = `${urlBase(config)}${endpoint}`;

function setConnectionStatus(connected) {
if (getState().error.backEndConnected !== connected) {
dispatch({
type: 'SET_CONNECTION_STATUS',
payload: connected,
});
}
}

dispatch({
type: store,
payload: {id, status: 'loading'},
});
return request[method](url, config.fetch, body)
.then(res => {
const contentType = res.headers.get('content-type');
if (
contentType &&
contentType.indexOf('application/json') !== -1
) {
return res.json().then(json => {
dispatch({
type: store,
payload: {
status: res.status,
content: json,
id,
},
.then(
res => {
setConnectionStatus(true);
const contentType = res.headers.get('content-type');
if (
contentType &&
contentType.indexOf('application/json') !== -1
) {
return res.json().then(json => {
dispatch({
type: store,
payload: {
status: res.status,
content: json,
id,
},
});
return json;
});
return json;
}
logWarningOnce(
'Response is missing header: content-type: application/json'
);
return dispatch({
type: store,
payload: {
id,
status: res.status,
},
});
},
() => {
// fetch rejection - this means the request didn't return,
// we don't get here from 400/500 errors, only network
// errors or unresponsive servers.
setConnectionStatus(false);
}
logWarningOnce(
'Response is missing header: content-type: application/json'
);
return dispatch({
type: store,
payload: {
id,
status: res.status,
},
});
})
)
.catch(err => {
const message = 'Error from API call: ' + endpoint;
handleAsyncError(err, message, dispatch);
Expand Down
48 changes: 28 additions & 20 deletions dash-renderer/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,29 +495,37 @@ function handleServerside(config, payload, hooks) {
headers: getCSRFHeader(),
body: JSON.stringify(payload),
})
).then(res => {
const {status} = res;
if (status === STATUS.OK) {
return res.json().then(data => {
const {multi, response} = data;
if (hooks.request_post !== null) {
hooks.request_post(payload, response);
}
).then(
res => {
const {status} = res;
if (status === STATUS.OK) {
return res.json().then(data => {
const {multi, response} = data;
if (hooks.request_post !== null) {
hooks.request_post(payload, response);
}

if (multi) {
return response;
}
if (multi) {
return response;
}

const {output} = payload;
const id = output.substr(0, output.lastIndexOf('.'));
return {[id]: response.props};
});
}
if (status === STATUS.PREVENT_UPDATE) {
return {};
const {output} = payload;
const id = output.substr(0, output.lastIndexOf('.'));
return {[id]: response.props};
});
}
if (status === STATUS.PREVENT_UPDATE) {
return {};
}
throw res;
},
() => {
// fetch rejection - this means the request didn't return,
// we don't get here from 400/500 errors, only network
// errors or unresponsive servers.
throw new Error('Callback failed: the server did not respond.');
}
throw res;
});
);
}

const getVals = input =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ class FrontEndErrorContainer extends Component {
}

render() {
const errorsLength = this.props.errors.length;
const {errors, connected} = this.props;
const errorsLength = errors.length;
if (errorsLength === 0) {
return null;
}

const inAlertsTray = this.props.inAlertsTray;
let cardClasses = 'dash-error-card dash-error-card--container';

const errorElements = this.props.errors.map((error, i) => {
const errorElements = errors.map((error, i) => {
return <FrontEndError e={error} isListItem={true} key={i} />;
});
if (inAlertsTray) {
Expand All @@ -31,7 +32,7 @@ class FrontEndErrorContainer extends Component {
<strong className="test-devtools-error-count">
{errorsLength}
</strong>
)
){connected ? null : '\u00a0 🚫 Server Unavailable'}
</div>
</div>
<div className="dash-error-card__list">{errorElements}</div>
Expand All @@ -42,6 +43,7 @@ class FrontEndErrorContainer extends Component {

FrontEndErrorContainer.propTypes = {
errors: PropTypes.array,
connected: PropTypes.bool,
inAlertsTray: PropTypes.any,
};

Expand Down
10 changes: 8 additions & 2 deletions dash-renderer/src/components/error/GlobalErrorContainer.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ class UnconnectedGlobalErrorContainer extends Component {
}

render() {
const {error, graphs, children} = this.props;
const {config, error, graphs, children} = this.props;
return (
<div id="_dash-global-error-container">
<DebugMenu error={error} graphs={graphs}>
<DebugMenu
error={error}
graphs={graphs}
hotReload={Boolean(config.hot_reload)}
>
<div id="_dash-app-content">{children}</div>
</DebugMenu>
</div>
Expand All @@ -23,11 +27,13 @@ class UnconnectedGlobalErrorContainer extends Component {

UnconnectedGlobalErrorContainer.propTypes = {
children: PropTypes.object,
config: PropTypes.object,
error: PropTypes.object,
graphs: PropTypes.object,
};

const GlobalErrorContainer = connect(state => ({
config: state.config,
error: state.error,
graphs: state.graphs,
}))(Radium(UnconnectedGlobalErrorContainer));
Expand Down
13 changes: 9 additions & 4 deletions dash-renderer/src/components/error/GlobalErrorOverlay.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ export default class GlobalErrorOverlay extends Component {
}

render() {
const {visible, error, toastsEnabled} = this.props;
const {visible, error, errorsOpened} = this.props;

let frontEndErrors;
if (toastsEnabled) {
if (errorsOpened) {
const errors = concat(error.frontEnd, error.backEnd);

frontEndErrors = <FrontEndErrorContainer errors={errors} />;
frontEndErrors = (
<FrontEndErrorContainer
errors={errors}
connected={error.backEndConnected}
/>
);
}
return (
<div>
Expand All @@ -36,5 +41,5 @@ GlobalErrorOverlay.propTypes = {
children: PropTypes.object,
visible: PropTypes.bool,
error: PropTypes.object,
toastsEnabled: PropTypes.any,
errorsOpened: PropTypes.any,
};
2 changes: 1 addition & 1 deletion dash-renderer/src/components/error/icons/BellIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/BellIconGrey.svg

This file was deleted.

3 changes: 3 additions & 0 deletions dash-renderer/src/components/error/icons/CheckIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions dash-renderer/src/components/error/icons/ClockIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/CloseIcon.svg

This file was deleted.

3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/ErrorIconWhite.svg

This file was deleted.

3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/GraphIconGrey.svg

This file was deleted.

3 changes: 3 additions & 0 deletions dash-renderer/src/components/error/icons/OffIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/WarningIconWhite.svg

This file was deleted.

3 changes: 0 additions & 3 deletions dash-renderer/src/components/error/icons/WhiteCloseIcon.svg

This file was deleted.

44 changes: 0 additions & 44 deletions dash-renderer/src/components/error/menu/DebugAlertContainer.css

This file was deleted.

This file was deleted.

Loading

0 comments on commit 79c6f30

Please sign in to comment.