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

Custom hooks - March 1 #75

Merged
merged 29 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9005923
Made DashRenderer standalone with hooks argument
valentijnnieman Sep 4, 2018
3fd37f5
Tests for DashRenderer with custom hooks
valentijnnieman Sep 4, 2018
89da3a2
Remove console.logs
valentijnnieman Sep 4, 2018
7e3045e
Add request and response parameters to hooks
valentijnnieman Sep 5, 2018
4af1751
Use dash-0.26.4 tarball for now
valentijnnieman Sep 7, 2018
e098366
Clean up DashRenderer class
valentijnnieman Sep 14, 2018
ed8350b
Remove JSON.Stringify() from request_pre payload parameter
valentijnnieman Sep 14, 2018
3564574
Replace typeof with Ramda's type
valentijnnieman Sep 14, 2018
26784c7
:zap: then()
valentijnnieman Sep 14, 2018
3b4d36f
Update type check for Ramda's type()
valentijnnieman Sep 20, 2018
f379521
Check if hooks are set or empty before hydrating
valentijnnieman Oct 3, 2018
ad6bcf0
Fire setHooks in AppContainer instead
valentijnnieman Oct 11, 2018
407062f
Cleanup
valentijnnieman Oct 12, 2018
458e143
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Feb 15, 2019
9ab7ccc
Fixed tests with new Dash tar containing standalone dash-renderer
valentijnnieman Feb 15, 2019
7cb0fc5
Favour dash egg over tarball :egg
valentijnnieman Feb 15, 2019
8402201
Remove unneeded call_count from test
valentijnnieman Feb 15, 2019
bb91e78
Fix test with input clear() issue
valentijnnieman Feb 15, 2019
2d3ac1c
prettier & rebuild
alexcjohnson Feb 21, 2019
6c309d7
lint with both eslint and prettier
alexcjohnson Feb 21, 2019
86d5f62
Update test with payload param and add proptypes instead of type chec…
valentijnnieman Feb 27, 2019
c9e4386
Merge branch 'custom_hooks' of https://github.com/plotly/dash-rendere…
valentijnnieman Feb 27, 2019
fd3f4ab
Prettier fix
valentijnnieman Feb 27, 2019
d36daaa
Fire post before dispatches that use data.response
valentijnnieman Feb 27, 2019
d7c33d7
Update test with response data
valentijnnieman Feb 27, 2019
7bbfe5b
Fix formatting
valentijnnieman Feb 27, 2019
22b7275
Merge branch 'master' of https://github.com/plotly/dash-renderer into…
valentijnnieman Feb 28, 2019
a2cbcbe
Fix formatting
valentijnnieman Feb 28, 2019
aa6a365
Fix request hooks test
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-0.26.4.tar.gz
Binary file not shown.
3 changes: 2 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dash_core_components==0.12.0
dash_html_components==0.11.0rc5
dash==0.18.3
# dash==0.18.3
dash-0.26.4.tar.gz
percy
selenium
mock
Expand Down
30 changes: 23 additions & 7 deletions src/APIController.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
computeGraphs,
computePaths,
hydrateInitialOutputs,
setLayout
setLayout,
setHooks
} from './actions/index';
import {getDependencies, getLayout} from './actions/api';
import {APP_STATES} from './reducers/constants';
Expand Down Expand Up @@ -36,7 +37,9 @@ class UnconnectedContainer extends Component {
graphs,
layout,
layoutRequest,
paths
paths,
hooks,
storedHooks
} = props;

if (isEmpty(layoutRequest)) {
Expand All @@ -55,6 +58,12 @@ class UnconnectedContainer extends Component {
dispatch(computeGraphs(dependenciesRequest.content));
}

if(hooks.request_pre !== null || hooks.request_post !== null || !hooks.empty) {
dispatch(setHooks(hooks));
} else {
dispatch(setHooks({request_pre: null, request_post: null, empty: true}))
}

if (
// dependenciesRequest and its computed stores
dependenciesRequest.status === 200 &&
Expand All @@ -65,6 +74,9 @@ class UnconnectedContainer extends Component {
!isEmpty(layout) &&
!isNil(paths) &&

// Custom request hooks
(!isEmpty(storedHooks) || storedHooks.empty) &&

// Hasn't already hydrated
appLifecycle === APP_STATES('STARTED')
) {
Expand All @@ -77,7 +89,7 @@ class UnconnectedContainer extends Component {
appLifecycle,
dependenciesRequest,
layoutRequest,
layout
layout,
} = this.props;

if (layoutRequest.status &&
Expand All @@ -98,7 +110,7 @@ class UnconnectedContainer extends Component {
else if (appLifecycle === APP_STATES('HYDRATED')) {
return (
<div id="_dash-app-content">
<TreeContainer layout={layout}/>
<TreeContainer layout={layout} />
</div>
);
}
Expand All @@ -118,19 +130,23 @@ UnconnectedContainer.propTypes = {
layoutRequest: PropTypes.object,
layout: PropTypes.object,
paths: PropTypes.object,
history: PropTypes.array
history: PropTypes.array,
hooks: PropTypes.object,
setHooks: PropTypes.object
}

const Container = connect(
// map state to props
state => ({
(state, ownProps) => ({
appLifecycle: state.appLifecycle,
dependenciesRequest: state.dependenciesRequest,
layoutRequest: state.layoutRequest,
layout: state.layout,
graphs: state.graphs,
paths: state.paths,
history: state.history
history: state.history,
hooks: ownProps.hooks,
storedHooks: state.hooks
}),
dispatch => ({dispatch})
)(UnconnectedContainer);
Expand Down
17 changes: 7 additions & 10 deletions src/AppContainer.react.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import {connect} from 'react-redux';
import React from 'react';
import Authentication from './Authentication.react';
import APIController from './APIController.react';
import DocumentTitle from './components/core/DocumentTitle.react';
import Loading from './components/core/Loading.react';
import Toolbar from './components/core/Toolbar.react';
import PropTypes from 'prop-types';

function UnconnectedAppContainer() {
function UnconnectedAppContainer({hooks}) {
return (
<Authentication>
<div>
<Toolbar/>
<APIController/>
<APIController hooks={hooks}/>
<DocumentTitle/>
<Loading/>
</div>
</Authentication>
);
}

const AppContainer = connect(
state => ({
history: state.history
}),
dispatch => ({dispatch})
)(UnconnectedAppContainer);
UnconnectedAppContainer.propTypes = {
hooks: PropTypes.object
}

export default AppContainer;
export default UnconnectedAppContainer;
18 changes: 13 additions & 5 deletions src/AppProvider.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import {Provider} from 'react-redux'
import initializeStore from './store';
import AppContainer from './AppContainer.react';

import PropTypes from 'prop-types';

const store = initializeStore();

const AppProvider = () => (
<Provider store={store}>
<AppContainer/>
</Provider>
);
const AppProvider = ({hooks}) => {
return (
<Provider store={store}>
<AppContainer hooks={hooks}/>
</Provider>
);
}

AppProvider.propTypes = {
hooks: PropTypes.object
}

export default AppProvider;
19 changes: 19 additions & 0 deletions src/DashRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*eslint-env browser */

'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import AppProvider from './AppProvider.react';

class DashRenderer {
constructor(hooks = { request_pre: null, request_post: null}) {
// render Dash Renderer upon initialising!
ReactDOM.render(
<AppProvider hooks={hooks} />,
document.getElementById('react-entry-point')
);
}
}

export { DashRenderer };
3 changes: 2 additions & 1 deletion src/actions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export const ACTIONS = (action) => {
COMPUTE_PATHS: 'COMPUTE_PATHS',
SET_LAYOUT: 'SET_LAYOUT',
SET_APP_LIFECYCLE: 'SET_APP_LIFECYCLE',
READ_CONFIG: 'READ_CONFIG'
READ_CONFIG: 'READ_CONFIG',
SET_HOOKS: 'SET_HOOKS'
};
if (actionList[action]) return actionList[action];
else throw new Error(`${action} is not defined.`)
Expand Down
37 changes: 33 additions & 4 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const computePaths = createAction(ACTIONS('COMPUTE_PATHS'));
export const setLayout = createAction(ACTIONS('SET_LAYOUT'));
export const setAppLifecycle = createAction(ACTIONS('SET_APP_LIFECYCLE'));
export const readConfig = createAction(ACTIONS('READ_CONFIG'));
export const setHooks = createAction(ACTIONS('SET_HOOKS'));

export function hydrateInitialOutputs() {
return function (dispatch, getState) {
Expand Down Expand Up @@ -383,7 +384,8 @@ function updateOutput(
layout,
graphs,
paths,
dependenciesRequest
dependenciesRequest,
hooks
} = getState();
const {InputGraph} = graphs;

Expand Down Expand Up @@ -469,6 +471,19 @@ function updateOutput(
});
}

if(hooks.request_pre !== null) {
if(type(hooks.request_pre) === 'Function') {
hooks.request_pre(payload);
}
else {
/* eslint-disable no-console */
// Throwing an Error or TypeError etc here will cause an infinite loop for some reason
console.error(
"The request_pre hook provided was not of type function, preventing Dash from firing it. Please make sure the request_pre hook is a function"
)
/* eslint-enable no-console */
}
}
return fetch(`${urlBase(config)}_dash-update-component`, {
method: 'POST',
headers: {
Expand Down Expand Up @@ -590,6 +605,22 @@ function updateOutput(
props: data.response.props
}));

// Fire custom request_post hook if any
if(hooks.request_post !== null) {
if(type(hooks.request_post) === 'Function') {
hooks.request_post(payload, data.response);
}
else {
/* eslint-disable no-console */
// Throwing an Error or TypeError etc here will cause an infinite loop for some reason
console.error(
"The request_post hook provided was not of type function, preventing Dash from firing it. Please make sure the request_post hook is a function"
)
/* eslint-enable no-console */
}
}


/*
* If the response includes children, then we need to update our
* paths store.
Expand Down Expand Up @@ -732,9 +763,7 @@ function updateOutput(
}

});
});

}
})}

export function serialize(state) {
// Record minimal input state in the url
Expand Down
12 changes: 3 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
/*eslint-env browser */

'use strict';
import { DashRenderer } from './DashRenderer'

import React from 'react';
import ReactDOM from 'react-dom';
import AppProvider from './AppProvider.react';


ReactDOM.render(
<AppProvider/>,
document.getElementById('react-entry-point')
);
// make DashRenderer globally available
window.DashRenderer = DashRenderer;
11 changes: 11 additions & 0 deletions src/reducers/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

const customHooks = (state = {request_pre: null, request_post: null, empty: false}, action) => {
switch (action.type) {
case 'SET_HOOKS':
return (action.payload);
default:
return state;
}
}

export default customHooks;
4 changes: 3 additions & 1 deletion src/reducers/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import paths from './paths';
import requestQueue from './requestQueue';
import appLifecycle from './appLifecycle';
import history from './history';
import hooks from './hooks';
import * as API from './api';
import config from './config';

Expand All @@ -20,7 +21,8 @@ const reducer = combineReducers({
dependenciesRequest: API.dependenciesRequest,
layoutRequest: API.layoutRequest,
loginRequest: API.loginRequest,
history
history,
hooks
});


Expand Down
75 changes: 75 additions & 0 deletions tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -1866,3 +1866,78 @@ def update_output(*args):
self.assertTrue(timestamp_2.value > timestamp_1.value)
self.assertEqual(call_count.value, 4)
self.percy_snapshot('button-2 click again')

def test_request_hooks(self):
app = Dash(__name__)

app.index_string = '''
<!DOCTYPE html>
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
</head>
<body>
<div>Testing custom DashRenderer</div>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
<script id="_dash-renderer" type"application/json">
console.log('firing up a custom renderer!')
const renderer = new DashRenderer({
request_pre: () => {
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
var output = document.getElementById('output-pre')
if(output) {
output.innerHTML = 'request_pre changed this text!';
}
},
request_post: () => {
var output = document.getElementById('output-post')
if(output) {
output.innerHTML = 'request_post changed this text!';
}
}
})
</script>
</footer>
<div>With request hooks</div>
</body>
</html>
'''

app.layout = html.Div([
dcc.Input(
id='input',
value='initial value'
),
html.Div(
html.Div([
html.Div(id='output-1'),
html.Div(id='output-pre'),
html.Div(id='output-post')
])
)
])

call_count = Value('i', 0)

@app.callback(Output('output-1', 'children'), [Input('input', 'value')])
def update_output(value):
call_count.value = call_count.value + 1
return value
call_count = Value('i', 0)

self.startServer(app)

input1 = self.wait_for_element_by_css_selector('#input')
input1.clear()

input1.send_keys('fire request hooks')

self.wait_for_text_to_equal('#output-1', 'fire request hooks')
self.wait_for_text_to_equal('#output-pre', 'request_pre changed this text!')
self.wait_for_text_to_equal('#output-post', 'request_post changed this text!')
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
self.percy_snapshot(name='request-hooks')