Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalized persistence #903

Merged
merged 23 commits into from
Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7a21285
persistence initial commit
alexcjohnson Sep 1, 2019
b737a3f
flip the loop in recordUiEdit
alexcjohnson Sep 5, 2019
6509121
clarify persistence getProps usage
alexcjohnson Sep 5, 2019
42428a6
update persistence comment for nested prop IDs
alexcjohnson Sep 5, 2019
78df979
allow multiple pieces of persistence for one prop
alexcjohnson Sep 5, 2019
83b8cd3
fallback if web store isn't available, or is full from the start
alexcjohnson Sep 6, 2019
cb179c9
fix missed props propagation
alexcjohnson Sep 11, 2019
b4ebb51
clean up package.json and upgrade to babel 7
alexcjohnson Sep 11, 2019
3b56686
remove obsolete dash-renderer package test script
alexcjohnson Sep 11, 2019
5ff00cf
unit test (and fix bugs) storage & fallbacks
alexcjohnson Sep 11, 2019
690b0a3
Merge branch 'dev' into persistence
alexcjohnson Sep 11, 2019
3669df6
cleaner error when storage fills up
alexcjohnson Sep 11, 2019
15ca0e5
get rid of no-undefined eslint rule
alexcjohnson Sep 11, 2019
c9230f5
renderer persistence changelog
alexcjohnson Sep 11, 2019
abde83f
integration tests - and a fix - for persistence
alexcjohnson Sep 12, 2019
4b11f07
prevent ambiguous double-installation of the rest of core on CI
alexcjohnson Sep 12, 2019
0626ea9
linux flavor of sed?
alexcjohnson Sep 12, 2019
ba8d5ba
update build to corejs@3
alexcjohnson Sep 13, 2019
09b9393
store different persisted values for each persistence id
alexcjohnson Sep 16, 2019
5fa4d55
more "random" new column name
alexcjohnson Sep 16, 2019
53ab7c5
lint test_persistence
alexcjohnson Sep 16, 2019
118547f
fix unit tests and error handler for multi-ID persistence
alexcjohnson Sep 16, 2019
130a857
Merge branch 'dev' into persistence
alexcjohnson Sep 16, 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
3 changes: 2 additions & 1 deletion dash-renderer/src/APIController.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
hydrateInitialOutputs,
setLayout,
} from './actions/index';
import {applyPersistence} from './persistence';
import apiThunk from './actions/api';
import {getAppState} from './reducers/constants';
import {STATUS} from './constants/constants';
Expand Down Expand Up @@ -48,7 +49,7 @@ class UnconnectedContainer extends Component {
dispatch(apiThunk('_dash-layout', 'GET', 'layoutRequest'));
} else if (layoutRequest.status === STATUS.OK) {
if (isEmpty(layout)) {
dispatch(setLayout(layoutRequest.content));
dispatch(setLayout(applyPersistence(layoutRequest.content)));
} else if (isNil(paths)) {
dispatch(computePaths({subTree: layout, startingPath: []}));
}
Expand Down
6 changes: 6 additions & 0 deletions dash-renderer/src/TreeContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
type,
} from 'ramda';
import {notifyObservers, updateProps} from './actions';
import {recordUiEdit} from './persistence';
import ComponentErrorBoundary from './components/error/ComponentErrorBoundary.react';
import checkPropTypes from 'check-prop-types';

Expand Down Expand Up @@ -164,6 +165,7 @@ class TreeContainer extends Component {
_dashprivate_dependencies,
_dashprivate_dispatch,
_dashprivate_path,
_dashprivate_layout,
Copy link
Contributor

Choose a reason for hiding this comment

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

YAL yet another layout

} = this.props;

const id = this.getLayoutProps().id;
Expand All @@ -185,6 +187,10 @@ class TreeContainer extends Component {
)
)(keysIn(newProps));

// setProps here is triggered by the UI - record these changes
// for persistence
recordUiEdit(_dashprivate_layout, newProps);

// Always update this component's props
_dashprivate_dispatch(
updateProps({
Expand Down
119 changes: 66 additions & 53 deletions dash-renderer/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
lensPath,
mergeLeft,
mergeDeepRight,
path,
pluck,
propEq,
reject,
Expand All @@ -31,6 +32,7 @@ import {getAction} from './constants';
import cookie from 'cookie';
import {uid, urlBase, isMultiOutputProp, parseMultipleOutputs} from '../utils';
import {STATUS} from '../constants/constants';
import {applyPersistence, prunePersistence} from '../persistence';

export const updateProps = createAction(getAction('ON_PROP_CHANGE'));
export const setRequestQueue = createAction(getAction('SET_REQUEST_QUEUE'));
Expand Down Expand Up @@ -530,6 +532,32 @@ function updateOutput(
});
}

function doUpdateProps(id, updatedProps) {
const {layout, paths} = getState();
const itempath = paths[id];
if (!itempath) {
return false;
}

// This is a callback-generated update.
// Check if this invalidates existing persisted prop values,
prunePersistence(path(itempath, layout), updatedProps);

// In case the update contains whole components, see if any of
// those components have props to update to persist user edits.
const finalProps = applyPersistence(updatedProps);

dispatch(
updateProps({
itempath,
props: finalProps,
source: 'response',
})
);

return finalProps;
}

// Clientside hook
if (clientside_function) {
let returnValue;
Expand Down Expand Up @@ -587,24 +615,20 @@ function updateOutput(
updateRequestQueue(false, STATUS.OK);

// Update the layout with the new result
dispatch(
updateProps({
itempath: getState().paths[outputId],
props: updatedProps,
source: 'response',
})
);
const appliedProps = doUpdateProps(outputId, updatedProps);

/*
* This output could itself be a serverside or clientside input
* to another function
*/
dispatch(
notifyObservers({
id: outputId,
props: updatedProps,
})
);
if (appliedProps) {
dispatch(
notifyObservers({
id: outputId,
props: appliedProps,
})
);
}
}

if (isMultiOutputProp(payload.output)) {
Expand Down Expand Up @@ -717,20 +741,16 @@ function updateOutput(
const handleResponse = ([outputIdAndProp, props]) => {
// Backward compatibility
const pathKey = multi ? outputIdAndProp : outputComponentId;
const observerUpdatePayload = {
itempath: getState().paths[pathKey],
props,
source: 'response',
};
if (!observerUpdatePayload.itempath) {

const appliedProps = doUpdateProps(pathKey, props);
if (!appliedProps) {
return;
}
dispatch(updateProps(observerUpdatePayload));

dispatch(
notifyObservers({
id: pathKey,
props: props,
props: appliedProps,
})
);

Expand All @@ -739,10 +759,11 @@ function updateOutput(
* paths store.
* TODO - Do we need to wait for updateProps to finish?
*/
if (has('children', observerUpdatePayload.props)) {
if (has('children', appliedProps)) {
const newChildren = appliedProps.children;
dispatch(
computePaths({
subTree: observerUpdatePayload.props.children,
subTree: newChildren,
startingPath: concat(
getState().paths[pathKey],
['props', 'children']
Expand All @@ -756,11 +777,8 @@ function updateOutput(
* new children components
*/
if (
contains(
type(observerUpdatePayload.props.children),
['Array', 'Object']
) &&
!isEmpty(observerUpdatePayload.props.children)
contains(type(newChildren), ['Array', 'Object']) &&
!isEmpty(newChildren)
) {
/*
* TODO: We're just naively crawling
Expand All @@ -770,32 +788,27 @@ function updateOutput(
* to compute the subtree
*/
const newProps = {};
crawlLayout(
observerUpdatePayload.props.children,
function appendIds(child) {
if (hasId(child)) {
keys(child.props).forEach(childProp => {
const componentIdAndProp = `${child.props.id}.${childProp}`;
if (
has(
componentIdAndProp,
InputGraph.nodes
)
) {
newProps[componentIdAndProp] = {
id: child.props.id,
props: {
[childProp]:
child.props[
childProp
],
},
};
}
});
}
crawlLayout(newChildren, function appendIds(child) {
if (hasId(child)) {
keys(child.props).forEach(childProp => {
const componentIdAndProp = `${child.props.id}.${childProp}`;
if (
has(
componentIdAndProp,
InputGraph.nodes
)
) {
newProps[componentIdAndProp] = {
id: child.props.id,
props: {
[childProp]:
child.props[childProp],
},
};
}
});
}
);
});

/*
* Organize props by shared outputs so that we
Expand Down
Loading