Skip to content

Commit 09b9393

Browse files
committed
store different persisted values for each persistence id
1 parent ba8d5ba commit 09b9393

File tree

3 files changed

+129
-98
lines changed

3 files changed

+129
-98
lines changed

dash-renderer/src/actions/index.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,16 @@ function updateOutput(
541541

542542
// This is a callback-generated update.
543543
// Check if this invalidates existing persisted prop values,
544-
prunePersistence(path(itempath, layout), updatedProps, dispatch);
544+
// or if persistence changed, whether this updates other props.
545+
const updatedProps2 = prunePersistence(
546+
path(itempath, layout),
547+
updatedProps,
548+
dispatch
549+
);
545550

546551
// In case the update contains whole components, see if any of
547552
// those components have props to update to persist user edits.
548-
const {props} = applyPersistence({props: updatedProps}, dispatch);
553+
const {props} = applyPersistence({props: updatedProps2}, dispatch);
549554

550555
dispatch(
551556
updateProps({

dash-renderer/src/persistence.js

+112-86
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ import {
6161
forEach,
6262
keys,
6363
lensPath,
64+
mergeRight,
6465
set,
65-
symmetricDifference,
6666
type,
6767
} from 'ramda';
6868
import {createAction} from 'redux-actions';
@@ -276,9 +276,8 @@ const getTransform = (element, propName, propPart) =>
276276
? element.persistenceTransforms[propName][propPart]
277277
: noopTransform;
278278

279-
const getNewValKey = (id, persistedProp) => id + '.' + persistedProp;
280-
const getOriginalValKey = newValKey => newValKey + '.orig';
281-
const getPersistIdKey = newValKey => newValKey + '.id';
279+
const getValsKey = (id, persistedProp, persistence) =>
280+
`${id}.${persistedProp}.${JSON.stringify(persistence)}`;
282281

283282
const getProps = layout => {
284283
const {props} = layout;
@@ -322,43 +321,27 @@ export function recordUiEdit(layout, newProps, dispatch) {
322321
const storage = getStore(persistence_type, dispatch);
323322
const {extract} = getTransform(element, propName, propPart);
324323

325-
const newValKey = getNewValKey(id, persistedProp);
326-
const persistIdKey = getPersistIdKey(newValKey);
327-
const previousVal = extract(props[propName]);
324+
const valsKey = getValsKey(id, persistedProp, persistence);
325+
let originalVal = extract(props[propName]);
328326
const newVal = extract(newProps[propName]);
329327

330328
// mainly for nested props with multiple persisted parts, it's
331329
// possible to have the same value as before - should not store
332330
// in this case.
333-
if (previousVal !== newVal) {
334-
if (
335-
!storage.hasItem(newValKey) ||
336-
storage.getItem(persistIdKey) !== persistence
337-
) {
338-
storage.setItem(
339-
getOriginalValKey(newValKey),
340-
previousVal,
341-
dispatch
342-
);
343-
storage.setItem(persistIdKey, persistence, dispatch);
331+
if (originalVal !== newVal) {
332+
if (storage.hasItem(valsKey)) {
333+
originalVal = storage.getItem(valsKey)[1];
344334
}
345-
storage.setItem(newValKey, newVal, dispatch);
335+
const vals =
336+
originalVal === undefined
337+
? [newVal]
338+
: [newVal, originalVal];
339+
storage.setItem(valsKey, vals);
346340
}
347341
}
348342
}, persisted_props);
349343
}
350344

351-
function clearUIEdit(id, persistence_type, persistedProp, dispatch) {
352-
const storage = getStore(persistence_type, dispatch);
353-
const newValKey = getNewValKey(id, persistedProp);
354-
355-
if (storage.hasItem(newValKey)) {
356-
storage.removeItem(newValKey);
357-
storage.removeItem(getOriginalValKey(newValKey));
358-
storage.removeItem(getPersistIdKey(newValKey));
359-
}
360-
}
361-
362345
/*
363346
* Used for entire layouts (on load) or partial layouts (from children
364347
* callbacks) to apply previously-stored UI edits to components
@@ -371,6 +354,28 @@ export function applyPersistence(layout, dispatch) {
371354
return persistenceMods(layout, layout, [], dispatch);
372355
}
373356

357+
const UNDO = true;
358+
function modProp(key, storage, element, props, persistedProp, update, undo) {
359+
if (storage.hasItem(key)) {
360+
const [newVal, originalVal] = storage.getItem(key);
361+
const fromVal = undo ? newVal : originalVal;
362+
const toVal = undo ? originalVal : newVal;
363+
const [propName, propPart] = persistedProp.split('.');
364+
const transform = getTransform(element, propName, propPart);
365+
366+
if (equals(fromVal, transform.extract(props[propName]))) {
367+
update[propName] = transform.apply(
368+
toVal,
369+
propName in update ? update[propName] : props[propName]
370+
);
371+
} else {
372+
// clear this saved edit - we've started with the wrong
373+
// value for this persistence ID
374+
storage.removeItem(key);
375+
}
376+
}
377+
}
378+
374379
function persistenceMods(layout, component, path, dispatch) {
375380
const {
376381
id,
@@ -385,31 +390,18 @@ function persistenceMods(layout, component, path, dispatch) {
385390
if (persistence) {
386391
const storage = getStore(persistence_type, dispatch);
387392
const update = {};
388-
forEach(persistedProp => {
389-
const [propName, propPart] = persistedProp.split('.');
390-
const newValKey = getNewValKey(id, persistedProp);
391-
const storedPersistID = storage.getItem(getPersistIdKey(newValKey));
392-
const transform = getTransform(element, propName, propPart);
393-
394-
if (storedPersistID) {
395-
if (
396-
storedPersistID === persistence &&
397-
equals(
398-
storage.getItem(getOriginalValKey(newValKey)),
399-
transform.extract(props[propName])
400-
)
401-
) {
402-
// To handle multiple nested props, apply each stored value
403-
// in turn; then at the end we'll push these into the layout
404-
update[propName] = transform.apply(
405-
storage.getItem(newValKey),
406-
propName in update ? update[propName] : props[propName]
407-
);
408-
} else {
409-
clearUIEdit(id, persistence_type, persistedProp, dispatch);
410-
}
411-
}
412-
}, persisted_props);
393+
forEach(
394+
persistedProp =>
395+
modProp(
396+
getValsKey(id, persistedProp, persistence),
397+
storage,
398+
element,
399+
props,
400+
persistedProp,
401+
update
402+
),
403+
persisted_props
404+
);
413405

414406
for (const propName in update) {
415407
layoutOut = set(
@@ -452,51 +444,85 @@ function persistenceMods(layout, component, path, dispatch) {
452444
export function prunePersistence(layout, newProps, dispatch) {
453445
const {
454446
id,
447+
props,
455448
persistence,
456449
persisted_props,
457450
persistence_type,
458451
element,
459452
} = getProps(layout);
460-
if (!persistence) {
461-
return;
462-
}
463453

464-
// first look for conditions that clear the persistence store entirely
465-
if (
466-
('persistence' in newProps && newProps.persistence !== persistence) ||
467-
('persistence_type' in newProps &&
468-
newProps.persistence_type !== persistence_type)
469-
) {
470-
getStore(persistence_type, dispatch).clear(id);
471-
return;
454+
const getFinal = (propName, prevVal) =>
455+
propName in newProps ? newProps[propName] : prevVal;
456+
const finalPersistence = getFinal('persistence', persistence);
457+
458+
if (!persistence && !finalPersistence) {
459+
return newProps;
472460
}
473461

474-
// if the persisted props list itself changed, clear any props not
475-
// present in both the new and old
476-
if ('persisted_props' in newProps) {
462+
const finalPersistenceType = getFinal('persistence_type', persistence_type);
463+
const finalPersistedProps = getFinal('persisted_props', persisted_props);
464+
const persistenceChanged =
465+
finalPersistence !== persistence ||
466+
finalPersistenceType !== persistence_type ||
467+
finalPersistedProps !== persisted_props;
468+
469+
const notInNewProps = persistedProp =>
470+
!(persistedProp.split('.')[0] in newProps);
471+
472+
const update = {};
473+
474+
if (persistenceChanged && persistence) {
475+
// clear previously-applied persistence
476+
const storage = getStore(persistence_type, dispatch);
477477
forEach(
478478
persistedProp =>
479-
clearUIEdit(id, persistence_type, persistedProp, dispatch),
480-
symmetricDifference(persisted_props, newProps.persisted_props)
479+
modProp(
480+
getValsKey(id, persistedProp, persistence),
481+
storage,
482+
element,
483+
props,
484+
persistedProp,
485+
update,
486+
UNDO
487+
),
488+
filter(notInNewProps, persisted_props)
481489
);
482490
}
483491

484-
// now the main point - clear any edit associated with a prop that changed
485-
// note that this is independent of the new prop value.
486-
const transforms = element.persistenceTransforms || {};
487-
for (const propName in newProps) {
488-
const propTransforms = transforms[propName];
489-
if (propTransforms) {
490-
for (const propPart in propTransforms) {
491-
clearUIEdit(
492-
id,
493-
persistence_type,
494-
`${propName}.${propPart}`,
495-
dispatch
496-
);
492+
if (finalPersistence) {
493+
const finalStorage = getStore(finalPersistenceType, dispatch);
494+
495+
if (persistenceChanged) {
496+
// apply new persistence
497+
forEach(
498+
persistedProp =>
499+
modProp(
500+
getValsKey(id, persistedProp, persistence),
501+
finalStorage,
502+
element,
503+
props,
504+
persistedProp,
505+
update
506+
),
507+
filter(notInNewProps, finalPersistedProps)
508+
);
509+
}
510+
511+
// now the main point - clear any edit of a prop that changed
512+
// note that this is independent of the new prop value.
513+
const transforms = element.persistenceTransforms || {};
514+
for (const propName in newProps) {
515+
const propTransforms = transforms[propName];
516+
if (propTransforms) {
517+
for (const propPart in propTransforms) {
518+
finalStorage.removeItem(
519+
getValsKey(id, `${propName}.${propPart}`, persistence)
520+
);
521+
}
522+
} else {
523+
finalStorage.removeItem(getValsKey(id, propName, persistence));
497524
}
498-
} else {
499-
clearUIEdit(id, persistence_type, propName, dispatch);
500525
}
501526
}
527+
return persistenceChanged ? mergeRight(newProps, update) : newProps;
502528
}

tests/integration/renderer/test_persistence.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ def test_rdps001_local_reload(dash_duo):
9898
app.persistence.value = 2
9999
dash_duo.wait_for_page()
100100
check_table_names(dash_duo, ['a', 'b'])
101+
rename_and_hide(dash_duo, 1, 'two', 0)
102+
dash_duo.wait_for_text_to_equal('#out', 'names: [a, two]; hidden: [c0]')
103+
check_table_names(dash_duo, ['two'])
101104

102-
# put back the old persistence, but values are gone
105+
# put back the old persistence, get the old values
103106
app.persistence.value = 1
104107
dash_duo.wait_for_page()
105-
check_table_names(dash_duo, ['a', 'b'])
106-
rename_and_hide(dash_duo)
108+
dash_duo.wait_for_text_to_equal('#out', 'names: [x, b]; hidden: [c1]')
109+
check_table_names(dash_duo, ['x'])
107110

108111
# falsy persistence disables it
109112
app.persistence.value = 0
@@ -114,13 +117,11 @@ def test_rdps001_local_reload(dash_duo):
114117
dash_duo.wait_for_page()
115118
check_table_names(dash_duo, ['a', 'b'])
116119

117-
# falsy to previous truthy DOES bring the values back
118-
# because with falsy persistence we don't look at the store at all
119-
# TODO: is this what we want? maybe not... but the same thing would
120-
# happen if you changed persistence_type then changed it back.
121-
app.persistence.value = 1
120+
# falsy to previous truthy also brings the values
121+
app.persistence.value = 2
122122
dash_duo.wait_for_page()
123-
check_table_names(dash_duo, ['x'])
123+
dash_duo.wait_for_text_to_equal('#out', 'names: [a, two]; hidden: [c0]')
124+
check_table_names(dash_duo, ['two'])
124125

125126

126127
def test_rdps002_session_reload(dash_duo):
@@ -218,7 +219,6 @@ def toggle_table(n):
218219

219220
dash_duo.find_element('#toggle-table').click()
220221
# back to original persisted_props hidden_columns returns
221-
# TODO: is this actually what we want?
222222
check_table_names(dash_duo, ['x'])
223223

224224

0 commit comments

Comments
 (0)