Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Loading states api #93

Merged
merged 48 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
762b0ba
Pass loading prop down to components instead of rendering div
valentijnnieman Oct 12, 2018
1fb8376
First pass at providing 'loading' prop to dash components
valentijnnieman Oct 30, 2018
5940bb4
Add componentName and propName props to loading object, cleanup
valentijnnieman Nov 6, 2018
5bf3d30
Cleanup
valentijnnieman Nov 6, 2018
a571882
Updated core components tarball
valentijnnieman Nov 7, 2018
1777a94
Change prop names for loading state to be more Pythonic
valentijnnieman Nov 13, 2018
1af20c9
Use latest dcc that fixes Input reliant tests
valentijnnieman Nov 15, 2018
4ddc4b7
Left-over from rebasing
valentijnnieman Nov 15, 2018
02e992a
Update everything to it's latest version expect html-components
valentijnnieman Nov 15, 2018
6acc394
Add tarball back in
valentijnnieman Nov 15, 2018
3264f4b
Change props.status to props.loading_state to be more specific
valentijnnieman Nov 15, 2018
a51dcf9
Pass loading prop down to components instead of rendering div
valentijnnieman Oct 12, 2018
d38fec9
First pass at providing 'loading' prop to dash components
valentijnnieman Oct 30, 2018
96aebf4
Add componentName and propName props to loading object, cleanup
valentijnnieman Nov 6, 2018
72b579d
Cleanup
valentijnnieman Nov 6, 2018
8d5e4af
Updated core components tarball
valentijnnieman Nov 7, 2018
95ed504
Change prop names for loading state to be more Pythonic
valentijnnieman Nov 13, 2018
e1917bf
Rebuild bundles
valentijnnieman Nov 15, 2018
cfe4c40
Fixed simple.py regression
valentijnnieman Nov 15, 2018
6c34ac9
Replace dcc tarball with rc1 version
valentijnnieman Nov 16, 2018
2153923
Try with new componentWillRecieveProps changes in dcc tarball
valentijnnieman Nov 16, 2018
e48348b
Revert everything in dev-requirements except dcc tarball
valentijnnieman Nov 16, 2018
48e0dd4
Revert back to old dev-requirements.txt completely
valentijnnieman Nov 16, 2018
4b43a54
Refactored requestQueue map into forEach
valentijnnieman Nov 27, 2018
8f1cc81
Add tarball back in dev-requirements
valentijnnieman Nov 27, 2018
05955ea
Revert back to old dev-requirements
valentijnnieman Nov 27, 2018
807d6bc
Fix tests by checking if controllerId is null
valentijnnieman Nov 29, 2018
bf97e2b
Remove newlines from NotifyObservers parameters
valentijnnieman Nov 29, 2018
99ef59a
Version bump to 0.16.0
valentijnnieman Nov 29, 2018
cd4a507
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Nov 30, 2018
c9f28d2
Change const to let
valentijnnieman Nov 30, 2018
08c4646
Update version to 0.16.0rc1
valentijnnieman Dec 4, 2018
622e912
Revert dcc upgrade in deps
valentijnnieman Dec 17, 2018
91e0a25
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Dec 17, 2018
75592f7
Calculate loading_state in TreeContainer so children have access to i…
valentijnnieman Jan 23, 2019
b7f3770
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Jan 30, 2019
8c0771b
Add confirm dialog test from DCC
valentijnnieman Jan 31, 2019
a2220fb
Remove dcc test again
valentijnnieman Jan 31, 2019
461d515
Cleanup
valentijnnieman Jan 31, 2019
a0ed549
Only rerender TreeContainer if props are new
valentijnnieman Jan 31, 2019
f692a8d
Release 0.18.0rc3 with more dcc test fixes
valentijnnieman Feb 1, 2019
a1a4531
Remove loading prop from APIcontroller causing unneccesary re-renders
valentijnnieman Feb 7, 2019
e1fb848
Bump rc version
valentijnnieman Feb 7, 2019
8fb0bda
Update package.json as well
valentijnnieman Feb 13, 2019
8751dd4
Reset simple.py
valentijnnieman Feb 21, 2019
94b9259
Refactored recursivelyRender call in TreeContainer
valentijnnieman Feb 21, 2019
0850d4b
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Feb 28, 2019
763c5bd
Fix formatting
valentijnnieman Feb 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added dash_core_components-0.38.0rc1.tar.gz
Binary file not shown.
2,919 changes: 1,454 additions & 1,465 deletions dash_renderer/dash_renderer.dev.js

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions dash_renderer/dash_renderer.min.js

Large diffs are not rendered by default.

Binary file added dash_renderer/favicon.ico
Binary file not shown.
176 changes: 162 additions & 14 deletions simple.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,171 @@
from dash import Dash
# -*- coding: utf-8 -*-
import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import time

app = Dash(__name__)
app.scripts.config.serve_locally=True
app.layout = html.Div([
dcc.Input(id='my-input', value='initial-input'),
html.Div(id='my-output')
])
from dash.dependencies import Input, Output, State

external_stylesheets = ["https://unpkg.com/tachyons@4.10.0/css/tachyons.min.css"]

@app.callback(
dash.dependencies.Output('my-output', 'children'),
[dash.dependencies.Input('my-input', 'value')]
)
def update(value):
return 'Output: {}'.format(value)
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

card_style = {
"box-shadow": "0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3)"
}

if __name__ == '__main__':
card_style_greyed = {
"box-shadow": "0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3)",
"backgroundColor": "#e0e0e0",
}

app.scripts.config.serve_locally = True

app.layout = dcc.Loading(children=[html.Div(
className="sans-serif",
children=[
html.Div(
[
html.H1(
className="tc f1 hot-pink mv6", children=["Dash Loading States API"]
)
]
),
html.Div(
className="w-60 center pt4",
children=[
dcc.Tabs(
id="tabs",
value="tab-1",
children=[
dcc.Tab(label="Normal Graph", value="tab-1", style={'backgroundColor': '#f5f5f5'}),
dcc.Tab(label="Funky Graph", value="tab-2", style={'backgroundColor': '#f5f5f5'}),
],
colors={
"primary": "white",
"background": "white",
"border": "#d2d2d2",
},
parent_style=card_style,
),
html.Div(
style={
"border": "1px solid #d2d2d2",
"borderTop": "none",
"paddingTop": "32px",
"box-shadow": "0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3)",
"marginTop": "32px",
"height": "498px"
},
children=[
dcc.Loading(id='tabs-content', type='graph')
]
),
],
style={},
),
html.Div(
className="w-80 center",
children=[
dcc.Loading(id='output-1')
],
),
html.Div(
className="w-80 center",
children=[
dcc.Input(
className="db center mv4 ph2 pv1", id="input-1", value="Type here!"
)
],
),
],
)], type='cube', fullscreen=True)


@app.callback(Output('tabs-content', 'children'),
[Input('tabs', 'value')])
def render_content(tab):
time.sleep(2)
if tab == 'tab-1':
return html.Div(id='loading-1', children=[
dcc.Graph(
id='graph-2-tabs',
figure=go.Figure(
data=[
go.Bar(
x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
350, 430, 474, 526, 488, 537, 500, 439],
name='Rest of world',
marker=go.bar.Marker(
color='rgb(55, 83, 109)'
)
),
go.Bar(
x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
299, 340, 403, 549, 499],
name='China',
marker=go.bar.Marker(
color='rgb(26, 118, 255)'
)
)
],
layout=go.Layout(
title='US Export of Plastic Scrap',
showlegend=True,
legend=go.layout.Legend(
x=0,
y=1.0
),
margin=go.layout.Margin(l=40, r=0, t=40, b=30)
)
),
)
])
elif tab == 'tab-2':
return html.Div(id='loading-2', children=[
dcc.Graph(
id='graph-1-tabs',
figure=go.Figure(
data=[
go.Bar(
x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
350, 430, 474, 526, 488, 537, 500, 439],
name='Rest of world',
marker=go.bar.Marker(
color='hotpink'
)
),
go.Bar(
x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
299, 340, 403, 549, 499],
name='China',
marker=go.bar.Marker(
color='gold'
)
)
],
layout=go.Layout(
title='US Export of Plastic Scrap',
showlegend=True,
legend=go.layout.Legend(
x=0,
y=1.0
),
margin=go.layout.Margin(l=40, r=0, t=40, b=30)
)
),
)
])


if __name__ == "__main__":
app.run_server(debug=True)
18 changes: 13 additions & 5 deletions src/APIController.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,13 @@ class UnconnectedContainer extends Component {
dispatch(hydrateInitialOutputs());
}
}

render() {
const {
appLifecycle,
dependenciesRequest,
layoutRequest,
layout,
} = this.props;

if (
layoutRequest.status &&
!contains(layoutRequest.status, [STATUS.OK, 'loading'])
Expand All @@ -99,12 +97,22 @@ class UnconnectedContainer extends Component {
} else if (appLifecycle === getAppState('HYDRATED')) {
return (
<div id="_dash-app-content">
<TreeContainer layout={layout} />
<TreeContainer layout={layout} loading={false} />
</div>
);
}

return <div className="_dash-loading">{'Loading...'}</div>;
if (isEmpty(layout)) {
return (
<div id="_dash-app-content">
<TreeContainer layout={layout} loading={true} />
</div>
);
}
return (
<div id="_dash-app-content">
<TreeContainer layout={layout} loading={true} />
</div>
);
}
}
UnconnectedContainer.propTypes = {
Expand Down
17 changes: 12 additions & 5 deletions src/TreeContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,26 @@ export default class TreeContainer extends Component {
}

render() {
return render(this.props.layout);
return recursivelyRender(this.props.layout, this.props.loading);
}
}

TreeContainer.propTypes = {
layout: PropTypes.object,
loading: PropTypes.bool
};

function render(component) {
function recursivelyRender(component, loading = false) {
if (
R.contains(R.type(component), ['String', 'Number', 'Null', 'Boolean'])
) {
return component;
}

if(R.isEmpty(component)) {
return null;
}

valentijnnieman marked this conversation as resolved.
Show resolved Hide resolved
// Create list of child elements
let children;

Expand Down Expand Up @@ -55,7 +60,9 @@ function render(component) {
children = (Array.isArray(componentProps.children)
? componentProps.children
: [componentProps.children]
).map(render);
).map(child => {
return recursivelyRender(child, loading);
});
}

if (!component.type) {
Expand All @@ -78,9 +85,9 @@ function render(component) {
...children
);

return <NotifyObservers id={componentProps.id}>{parent}</NotifyObservers>;
return <NotifyObservers id={componentProps.id} loading={loading}>{parent}</NotifyObservers>;
}

render.propTypes = {
recursivelyRender.propTypes = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐱

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does 🐱 mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah never mind, I found this again 😸

children: PropTypes.object,
};
35 changes: 34 additions & 1 deletion src/components/core/NotifyObservers.react.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {connect} from 'react-redux';
import {isEmpty} from 'ramda';
import {contains, isEmpty} from 'ramda';
import {notifyObservers, updateProps} from '../../actions';
import React from 'react';
import PropTypes from 'prop-types';
import {STATUS} from '../../constants/constants';

/*
* NotifyObservers passes a connected `setProps` handler down to
Expand All @@ -13,6 +14,7 @@ function mapStateToProps(state) {
return {
dependencies: state.dependenciesRequest.content,
paths: state.paths,
requestQueue: state.requestQueue,
};
}

Expand All @@ -27,6 +29,8 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
loading: ownProps.loading,
requestQueue: stateProps.requestQueue,

fireEvent: function fireEvent({event}) {
// Update this component's observers with the updated props
Expand Down Expand Up @@ -56,9 +60,30 @@ function NotifyObserversComponent({

dependencies,

loading,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why extra newlines at 60, 62, 64?

fireEvent,
setProps,
requestQueue,
}) {
// loading prop coming from TreeContainer
let isLoading = loading;
let loadingProp;
let loadingComponent;

requestQueue.map(r => {
if (r.status === 'loading' && contains(id, r.controllerId)) {
isLoading = true;
loadingComponent = r.controllerId.split('.')[0];
loadingProp = r.controllerId.split('.')[1];
Copy link
Contributor

@T4rk1n T4rk1n Nov 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can split only one time: [loadingProp, loadingComponent] = r.controllerId.split('.').

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a hard time figuring this out, that is not a proper map. It should be a for loop or a filter or a find.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the most appropriate would be a .forEach()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have been using rambda elsewhere in dash-renderer even when a core javascript function exists for a task, so https://ramdajs.com/docs/#forEach would fit better

}
});

const thisRequest = requestQueue.filter(r => contains(id, r.controllerId));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filter is here.

if (thisRequest.status === STATUS.OK) {
isLoading = false;
}

const thisComponentTriggersEvents =
dependencies &&
dependencies.find(dependency =>
Expand Down Expand Up @@ -102,6 +127,13 @@ function NotifyObserversComponent({
extraProps.fireEvent = fireEvent;
}

// Set loading state
extraProps.loading_state = {
is_loading: isLoading,
prop_name: loadingProp,
component_name: loadingComponent,
};

if (!isEmpty(extraProps)) {
return React.cloneElement(children, extraProps);
}
Expand All @@ -112,6 +144,7 @@ NotifyObserversComponent.propTypes = {
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
path: PropTypes.array.isRequired,
loading: PropTypes.bool,
};

export default connect(
Expand Down
2 changes: 1 addition & 1 deletion src/components/core/Reloader.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Reloader extends React.Component {
) {
// Look if it was a css file.
let was_css = false;
for (let a of reloadRequest.content.files) {
for (const a of reloadRequest.content.files) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is an error, make sure it works because I kinda remember putting const and it failed when there was multiple files. There can be multiple files if multiple assets files are saved at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be some artifact from a merge with master, I haven't touched any of the reloading code as far as I remember.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had a chance to rebase yet, because I'm still working on fixing the tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's prettier that changes it, I remember it changed it also when I ran it, maybe add an exclude here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to change this back.

if (a.is_css) {
was_css = true;
const nodesToDisable = [];
Expand Down