Skip to content

Commit 58bbe0b

Browse files
authored
Merge branch 'dev' into insistence-dev
2 parents 95d4bdb + 553db47 commit 58bbe0b

File tree

13 files changed

+239
-37
lines changed

13 files changed

+239
-37
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5-
## UNRELEASED
5+
## [2.17.1] - 2024-06-12
66

77
## Added
88

@@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1515
- [#2859](https://github.com/plotly/dash/pull/2859) Fix base patch operators. fixes [#2855](https://github.com/plotly/dash/issues/2855)
1616
- [#2856](https://github.com/plotly/dash/pull/2856) Fix multiple consecutive calls with same id to set_props only keeping the last props. Fixes [#2852](https://github.com/plotly/dash/issues/2852)
1717
- [#2867](https://github.com/plotly/dash/pull/2867) Fix clientside no output callback. Fixes [#2866](https://github.com/plotly/dash/issues/2866)
18+
- [#2876](https://github.com/plotly/dash/pull/2876) Fix pattern matching in callback running argument. Fixes [#2863](https://github.com/plotly/dash/issues/2863)
1819

1920
## [2.17.0] - 2024-05-03
2021

components/dash-core-components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/dash-core-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-core-components",
3-
"version": "2.14.0",
3+
"version": "2.14.1",
44
"description": "Core component suite for Dash",
55
"repository": {
66
"type": "git",

dash/_dash_renderer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22

3-
__version__ = "1.20.0"
3+
__version__ = "1.20.1"
44

55
_available_react_versions = {"16.14.0", "18.2.0"}
66
_available_reactdom_versions = {"16.14.0", "18.2.0"}
@@ -64,7 +64,7 @@ def _set_react_version(v_react, v_reactdom=None):
6464
{
6565
"relative_package_path": "dash-renderer/build/dash_renderer.min.js",
6666
"dev_package_path": "dash-renderer/build/dash_renderer.dev.js",
67-
"external_url": "https://unpkg.com/[email protected].0"
67+
"external_url": "https://unpkg.com/[email protected].1"
6868
"/build/dash_renderer.min.js",
6969
"namespace": "dash",
7070
},

dash/dash-renderer/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash/dash-renderer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-renderer",
3-
"version": "1.20.0",
3+
"version": "1.20.1",
44
"description": "render dash components in react",
55
"main": "build/dash_renderer.min.js",
66
"scripts": {

dash/dash-renderer/src/actions/callbacks.ts

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import {
3131
IPrioritizedCallback,
3232
LongCallbackInfo,
3333
CallbackResponse,
34-
CallbackResponseData
34+
CallbackResponseData,
35+
SideUpdateOutput
3536
} from '../types/callbacks';
3637
import {isMultiValued, stringifyId, isMultiOutputProp} from './dependencies';
3738
import {urlBase} from './utils';
@@ -44,6 +45,8 @@ import {handlePatch, isPatch} from './patch';
4445
import {getPath} from './paths';
4546

4647
import {requestDependencies} from './requestDependencies';
48+
import {parsePMCId} from './patternMatching';
49+
import {replacePMC} from './patternMatching';
4750

4851
export const addBlockedCallbacks = createAction<IBlockedCallback[]>(
4952
CallbackActionType.AddBlocked
@@ -345,33 +348,53 @@ function updateComponent(component_id: any, props: any) {
345348
};
346349
}
347350

348-
function sideUpdate(outputs: any, dispatch: any) {
349-
toPairs(outputs).forEach(([id, value]) => {
350-
let componentId = id,
351-
propName;
351+
/**
352+
* Update a component props with `running`/`progress`/`set_props` calls.
353+
*
354+
* @param outputs Props to update.
355+
* @param cb The originating callback info.
356+
* @returns
357+
*/
358+
function sideUpdate(outputs: SideUpdateOutput, cb: ICallbackPayload) {
359+
return function (dispatch: any, getState: any) {
360+
toPairs(outputs)
361+
.reduce((acc, [id, value], i) => {
362+
let componentId = id,
363+
propName,
364+
replacedIds = [];
365+
366+
if (id.startsWith('{')) {
367+
[componentId, propName] = parsePMCId(id);
368+
replacedIds = replacePMC(componentId, cb, i, getState);
369+
} else if (id.includes('.')) {
370+
[componentId, propName] = id.split('.');
371+
}
352372

353-
if (id.startsWith('{')) {
354-
const index = id.lastIndexOf('}');
355-
if (index + 2 < id.length) {
356-
propName = id.substring(index + 2);
357-
componentId = JSON.parse(id.substring(0, index + 1));
358-
} else {
359-
componentId = JSON.parse(id);
360-
}
361-
} else if (id.includes('.')) {
362-
[componentId, propName] = id.split('.');
363-
}
373+
const props = propName ? {[propName]: value} : value;
364374

365-
const props = propName ? {[propName]: value} : value;
366-
dispatch(updateComponent(componentId, props));
367-
});
375+
if (replacedIds.length === 0) {
376+
acc.push([componentId, props]);
377+
} else if (replacedIds.length === 1) {
378+
acc.push([replacedIds[0], props]);
379+
} else {
380+
replacedIds.forEach((rep: any) => {
381+
acc.push([rep, props]);
382+
});
383+
}
384+
385+
return acc;
386+
}, [] as any[])
387+
.forEach(([id, idProps]) => {
388+
dispatch(updateComponent(id, idProps));
389+
});
390+
};
368391
}
369392

370393
function handleServerside(
371394
dispatch: any,
372395
hooks: any,
373396
config: any,
374-
payload: any,
397+
payload: ICallbackPayload,
375398
long: LongCallbackInfo | undefined,
376399
additionalArgs: [string, string, boolean?][] | undefined,
377400
getState: any,
@@ -391,7 +414,7 @@ function handleServerside(
391414
let moreArgs = additionalArgs;
392415

393416
if (running) {
394-
sideUpdate(running.running, dispatch);
417+
dispatch(sideUpdate(running.running, payload));
395418
runningOff = running.runningOff;
396419
}
397420

@@ -501,10 +524,10 @@ function handleServerside(
501524
dispatch(removeCallbackJob({jobId: job}));
502525
}
503526
if (runningOff) {
504-
sideUpdate(runningOff, dispatch);
527+
dispatch(sideUpdate(runningOff, payload));
505528
}
506529
if (progressDefault) {
507-
sideUpdate(progressDefault, dispatch);
530+
dispatch(sideUpdate(progressDefault, payload));
508531
}
509532
};
510533

@@ -527,11 +550,11 @@ function handleServerside(
527550
}
528551

529552
if (data.sideUpdate) {
530-
sideUpdate(data.sideUpdate, dispatch);
553+
dispatch(sideUpdate(data.sideUpdate, payload));
531554
}
532555

533556
if (data.progress) {
534-
sideUpdate(data.progress, dispatch);
557+
dispatch(sideUpdate(data.progress, payload));
535558
}
536559
if (!progressDefault && data.progressDefault) {
537560
progressDefault = data.progressDefault;
@@ -676,11 +699,19 @@ export function executeCallback(
676699

677700
const __execute = async (): Promise<CallbackResult> => {
678701
try {
702+
const changedPropIds = keys<string>(cb.changedPropIds);
703+
const parsedChangedPropsIds = changedPropIds.map(propId => {
704+
if (propId.startsWith('{')) {
705+
return parsePMCId(propId)[0];
706+
}
707+
return propId;
708+
});
679709
const payload: ICallbackPayload = {
680710
output,
681711
outputs: isMultiOutputProp(output) ? outputs : outputs[0],
682712
inputs: inVals,
683-
changedPropIds: keys(cb.changedPropIds),
713+
changedPropIds,
714+
parsedChangedPropsIds,
684715
state: cb.callback.state.length
685716
? fillVals(paths, layout, cb, state, 'State')
686717
: undefined
@@ -726,7 +757,9 @@ export function executeCallback(
726757
if (inter.length) {
727758
additionalArgs.push(['cancelJob', job.jobId]);
728759
if (job.progressDefault) {
729-
sideUpdate(job.progressDefault, dispatch);
760+
dispatch(
761+
sideUpdate(job.progressDefault, payload)
762+
);
730763
}
731764
}
732765
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {keys, equals, dissoc, toPairs} from 'ramda';
2+
import {ICallbackPayload} from '../types/callbacks';
3+
4+
/**
5+
* Deserialize pattern matching ids that come in one of the form:
6+
* - '{"type":"component","index":["MATCH"]}.children'
7+
* - '{"type":"component","index":["MATCH"]}'
8+
*
9+
* @param id The raw object as a string id.
10+
* @returns The id object.
11+
*/
12+
export function parsePMCId(id: string): [any, string | undefined] {
13+
let componentId, propName;
14+
const index = id.lastIndexOf('}');
15+
if (index + 2 < id.length) {
16+
propName = id.substring(index + 2);
17+
componentId = JSON.parse(id.substring(0, index + 1));
18+
} else {
19+
componentId = JSON.parse(id);
20+
}
21+
return [componentId, propName];
22+
}
23+
24+
/**
25+
* Get all the associated ids for an id.
26+
*
27+
* @param id Id to get all the pmc ids from.
28+
* @param state State of the store.
29+
* @param triggerKey Key to remove from the equality comparison.
30+
* @returns
31+
*/
32+
export function getAllPMCIds(id: any, state: any, triggerKey: string) {
33+
const keysOfIds = keys(id);
34+
const idKey = keysOfIds.join(',');
35+
return state.paths.objs[idKey]
36+
.map((obj: any) =>
37+
keysOfIds.reduce((acc, key, i) => {
38+
acc[key] = obj.values[i];
39+
return acc;
40+
}, {} as any)
41+
)
42+
.filter((obj: any) =>
43+
equals(dissoc(triggerKey, obj), dissoc(triggerKey, id))
44+
);
45+
}
46+
47+
/**
48+
* Replace the pattern matching ids with the actual trigger value
49+
* for MATCH, all the ids for ALL and smaller than the trigger value
50+
* for ALLSMALLER.
51+
*
52+
* @param id The parsed id in dictionary format.
53+
* @param cb Original callback info.
54+
* @param index Index of the dependency in case there is more than one changed id.
55+
* @param getState Function to get the state of the redux store.
56+
* @returns List of replaced ids.
57+
*/
58+
export function replacePMC(
59+
id: any,
60+
cb: ICallbackPayload,
61+
index: number,
62+
getState: any
63+
): any[] {
64+
let extras: any = [];
65+
const replaced: any = {};
66+
toPairs(id).forEach(([key, value]) => {
67+
if (extras.length) {
68+
// All done.
69+
return;
70+
}
71+
if (Array.isArray(value)) {
72+
const triggerValue = (cb.parsedChangedPropsIds[index] ||
73+
cb.parsedChangedPropsIds[0])[key];
74+
if (value.includes('MATCH')) {
75+
replaced[key] = triggerValue;
76+
} else if (value.includes('ALL')) {
77+
extras = getAllPMCIds(id, getState(), key);
78+
} else if (value.includes('ALLSMALLER')) {
79+
extras = getAllPMCIds(id, getState(), key).filter(
80+
(obj: any) => obj[key] < triggerValue
81+
);
82+
}
83+
} else {
84+
replaced[key] = value;
85+
}
86+
});
87+
if (extras.length) {
88+
return extras;
89+
}
90+
return [replaced];
91+
}

dash/dash-renderer/src/types/callbacks.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export interface IStoredCallback extends IExecutedCallback {
7272

7373
export interface ICallbackPayload {
7474
changedPropIds: any[];
75+
parsedChangedPropsIds: any[];
7576
inputs: any[];
7677
output: string;
7778
outputs: any[];
@@ -106,3 +107,7 @@ export type CallbackResponseData = {
106107
cancel?: ICallbackProperty[];
107108
sideUpdate?: any;
108109
};
110+
111+
export type SideUpdateOutput = {
112+
[key: string]: any;
113+
};

dash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.17.0"
1+
__version__ = "2.17.1"

0 commit comments

Comments
 (0)