From 1d4aa1150550ec089faa51a090a3bf16fafc2a43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:39:59 +0000 Subject: [PATCH 1/7] Initial plan From d508081eaec56e8a4c9390887c14417336245ef3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:50:15 +0000 Subject: [PATCH 2/7] feat: support dynamic master-detail column definitions Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/46ba3652-c128-4b3a-9f3f-5720519c2589 Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- docs/examples/enterprise/master_detail.py | 30 +++++++++ src/lib/components/AgGrid.react.js | 36 ++++++---- src/lib/fragments/AgGrid.react.js | 44 ++++++++++++ tests/test_recursive_functions.py | 81 +++++++++++++++++++++++ 4 files changed, 177 insertions(+), 14 deletions(-) diff --git a/docs/examples/enterprise/master_detail.py b/docs/examples/enterprise/master_detail.py index c7ff68e4..c1940113 100644 --- a/docs/examples/enterprise/master_detail.py +++ b/docs/examples/enterprise/master_detail.py @@ -26,6 +26,11 @@ {"headerName": "Pop. (Metro area)", "field": "population_metro"}, ] +detailColumnDefsSimple = [ + {"headerName": "City", "field": "city"}, + {"headerName": "Pop. (City proper)", "field": "population_city"}, +] + rowData = [ { "country": "China", @@ -164,6 +169,31 @@ ), body=True, ), + html.Hr(), + dbc.Card( + dcc.Markdown( + "Use a JavaScript function in `detailCellRendererParams` to dynamically define detail columns per expanded row." + ), + body=True, + ), + dbc.Card( + dag.AgGrid( + id="master-detail-table-dynamic-columns", + columnDefs=masterColumnDefs, + rowData=rowData, + columnSize="sizeToFit", + enableEnterpriseModules=True, + masterDetail=True, + detailCellRendererParams={ + "function": """params.data.region === "Asia" + ? {detailGridOptions: {columnDefs: %s}, detailColName: "cities", suppressCallback: true} + : {detailGridOptions: {columnDefs: %s}, detailColName: "cities", suppressCallback: true}""" + % (detailColumnDefsSimple, detailColumnDefs) + }, + dashGridOptions={"detailRowAutoHeight": True}, + ), + body=True, + ), ] ) diff --git a/src/lib/components/AgGrid.react.js b/src/lib/components/AgGrid.react.js index ce2e1c3a..2b287829 100644 --- a/src/lib/components/AgGrid.react.js +++ b/src/lib/components/AgGrid.react.js @@ -600,22 +600,30 @@ DashAgGrid.propTypes = { * Specifies the params to be used by the default detail Cell Renderer. See Detail * Grids. */ - detailCellRendererParams: PropTypes.shape({ - /** - * Grid options for detail grid in master-detail view. - */ - detailGridOptions: PropTypes.any, + detailCellRendererParams: PropTypes.oneOfType([ + PropTypes.shape({ + /** + * Grid options for detail grid in master-detail view. + */ + detailGridOptions: PropTypes.any, - /** - * Column name where detail grid data is located in main dataset, for master-detail view. - */ - detailColName: PropTypes.string, + /** + * Column name where detail grid data is located in main dataset, for master-detail view. + */ + detailColName: PropTypes.string, - /** - * Default: true. If true, suppresses the Dash callback in favor of using the data embedded in rowData at the given detailColName. - */ - suppressCallback: PropTypes.bool, - }), + /** + * Default: true. If true, suppresses the Dash callback in favor of using the data embedded in rowData at the given detailColName. + */ + suppressCallback: PropTypes.bool, + }), + PropTypes.shape({ + /** + * JavaScript function that receives detail row params and returns detailCellRendererParams. + */ + function: PropTypes.string, + }), + ]), /** * The style to give a particular row. See Row Style. diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 6f7dad3a..e0310ea3 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -585,6 +585,33 @@ export function DashAgGrid(props) { const convertOneRef = useRef(); const convertAllPropsRef = useRef(); + const normalizeDetailCellRendererParams = useCallback( + (value) => { + if (!value || typeof value !== 'object') { + return value; + } + + let adjustedVal = value; + if ('suppressCallback' in value) { + adjustedVal = { + ...adjustedVal, + getDetailRowData: value.suppressCallback + ? suppressGetDetail(value.detailColName) + : callbackGetDetail, + }; + } + if ('detailGridOptions' in value) { + adjustedVal = assocPath( + ['detailGridOptions', 'components'], + components, + adjustedVal + ); + } + return convertAllPropsRef.current(adjustedVal); + }, + [suppressGetDetail, callbackGetDetail, components] + ); + const convertOne = useCallback( (value, target) => { if (value) { @@ -614,6 +641,21 @@ export function DashAgGrid(props) { }, value); } if (GRID_NESTED_FUNCTIONS[target]) { + if (target === 'detailCellRendererParams') { + if (has('function', value)) { + const dynamicDetailParams = + convertMaybeFunction(value); + if (typeof dynamicDetailParams === 'function') { + return (params) => + normalizeDetailCellRendererParams( + dynamicDetailParams(params) + ); + } + return dynamicDetailParams; + } + return normalizeDetailCellRendererParams(value); + } + let adjustedVal = value; if ( target === 'rowSelection' && @@ -672,6 +714,8 @@ export function DashAgGrid(props) { [ convertCol, convertMaybeFunctionNoParams, + convertMaybeFunction, + normalizeDetailCellRendererParams, suppressGetDetail, callbackGetDetail, components, diff --git a/tests/test_recursive_functions.py b/tests/test_recursive_functions.py index 0f6f811d..f008199f 100644 --- a/tests/test_recursive_functions.py +++ b/tests/test_recursive_functions.py @@ -268,6 +268,87 @@ def test_rf001_recursive_functions(dash_duo): ).get_attribute("style") +def test_rf003_master_detail_dynamic_columns(dash_duo): + app = Dash(__name__) + masterColumnDefs = [ + { + "headerName": "Country", + "field": "country", + "cellRenderer": "agGroupCellRenderer", + }, + {"headerName": "Region", "field": "region"}, + ] + + detailColumnDefsSimple = [ + {"headerName": "City", "field": "city"}, + {"headerName": "Pop. (City proper)", "field": "population_city"}, + ] + detailColumnDefs = detailColumnDefsSimple + [ + {"headerName": "Pop. (Metro area)", "field": "population_metro"}, + ] + + rowData = [ + { + "country": "China", + "region": "Asia", + "cities": [ + { + "city": "Shanghai", + "population_city": 24870895, + "population_metro": "NA", + }, + ], + }, + { + "country": "United States", + "region": "Americas", + "cities": [ + { + "city": "New York", + "population_city": 8398748, + "population_metro": 19303808, + }, + ], + }, + ] + + app.layout = html.Div( + [ + dag.AgGrid( + id="grid", + columnDefs=masterColumnDefs, + rowData=rowData, + columnSize="sizeToFit", + enableEnterpriseModules=True, + masterDetail=True, + detailCellRendererParams={ + "function": """params.data.region === "Asia" + ? {detailGridOptions: {columnDefs: %s}, detailColName: "cities", suppressCallback: true} + : {detailGridOptions: {columnDefs: %s}, detailColName: "cities", suppressCallback: true}""" + % (detailColumnDefsSimple, detailColumnDefs) + }, + dashGridOptions={"detailRowAutoHeight": True}, + ) + ] + ) + + dash_duo.start_server(app) + + grid = utils.Grid(dash_duo, "grid") + grid.wait_for_cell_text(0, 0, "China") + + grid.get_cell_expandable(0, 0).click() + dash_duo.wait_for_text_to_equal( + '#grid .ag-details-grid [row-index="0"] [aria-colindex="2"]', "24870895" + ) + + grid.get_cell_expandable(0, 0).click() + grid.get_cell_expandable(1, 0).click() + dash_duo.wait_for_text_to_equal( + '#grid .ag-details-grid [row-index="0"] [aria-colindex="3"]', "19303808" + ) + + def test_rf002_recursive_functions_server(dash_duo): app = Dash(__name__) masterColumnDefs = [ From 05adfac8ae3a3d9a923f4d89205429a45771914f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:54:45 +0000 Subject: [PATCH 3/7] test: refine dynamic master-detail example coverage Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/46ba3652-c128-4b3a-9f3f-5720519c2589 Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- src/lib/fragments/AgGrid.react.js | 4 +++- tests/test_recursive_functions.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index e0310ea3..1632eac5 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -651,7 +651,9 @@ export function DashAgGrid(props) { dynamicDetailParams(params) ); } - return dynamicDetailParams; + return normalizeDetailCellRendererParams( + dynamicDetailParams + ); } return normalizeDetailCellRendererParams(value); } diff --git a/tests/test_recursive_functions.py b/tests/test_recursive_functions.py index f008199f..0dbee385 100644 --- a/tests/test_recursive_functions.py +++ b/tests/test_recursive_functions.py @@ -69,7 +69,7 @@ def test_rf001_recursive_functions(dash_duo): { "city": "Shanghai", "population_city": 24870895, - "population_metro": "NA", + "population_metro": 0, }, { "city": "Beijing", @@ -295,7 +295,7 @@ def test_rf003_master_detail_dynamic_columns(dash_duo): { "city": "Shanghai", "population_city": 24870895, - "population_metro": "NA", + "population_metro": 0, }, ], }, From 8813c63ac20a7d2a2fef64fb8478981f78106d0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:42:08 +0000 Subject: [PATCH 4/7] test: assert dynamic detail grid headers Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/11ea2980-aa7b-421d-96c2-6e13bf4fdce6 Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- tests/test_recursive_functions.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_recursive_functions.py b/tests/test_recursive_functions.py index 0dbee385..3fddb1a8 100644 --- a/tests/test_recursive_functions.py +++ b/tests/test_recursive_functions.py @@ -338,12 +338,32 @@ def test_rf003_master_detail_dynamic_columns(dash_duo): grid.wait_for_cell_text(0, 0, "China") grid.get_cell_expandable(0, 0).click() + until( + lambda: [ + e.text + for e in dash_duo.find_elements( + '#grid .ag-details-grid [aria-rowindex="1"] .ag-header-cell-text' + ) + ] + == ["City", "Pop. (City proper)"], + timeout=3, + ) dash_duo.wait_for_text_to_equal( '#grid .ag-details-grid [row-index="0"] [aria-colindex="2"]', "24870895" ) grid.get_cell_expandable(0, 0).click() grid.get_cell_expandable(1, 0).click() + until( + lambda: [ + e.text + for e in dash_duo.find_elements( + '#grid .ag-details-grid [aria-rowindex="1"] .ag-header-cell-text' + ) + ] + == ["City", "Pop. (City proper)", "Pop. (Metro area)"], + timeout=3, + ) dash_duo.wait_for_text_to_equal( '#grid .ag-details-grid [row-index="0"] [aria-colindex="3"]', "19303808" ) From a121b4240b52bcf0a12e1c13a1f10ce86a17c17b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:02:42 +0000 Subject: [PATCH 5/7] refactor: remove duplicate convertOne hook dependency Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/3fa839fc-17e1-4335-ad84-edd4e27a1079 Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- src/lib/fragments/AgGrid.react.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 5983da52..b1adba1d 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -727,7 +727,6 @@ export function DashAgGrid(props) { convertAllPropsRef.current, convertFunction, handleDynamicStyle, - convertMaybeFunction, ] ); From e864f33ffbaf96c25bb27e4200ccb086addf58c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:11:14 +0000 Subject: [PATCH 6/7] refactor: centralize detail params normalization and add changelog Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/8ac40ce1-2f40-4da6-be94-8b7a9896c93f Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- CHANGELOG.md | 1 + src/lib/fragments/AgGrid.react.js | 36 ++++++++++--------------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4bb9f02..ba9eaa0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source D ## [unreleased] ### Added - [#453](https://github.com/plotly/dash-ag-grid/pull/453) Test for changelog entry +- [#455](https://github.com/plotly/dash-ag-grid/pull/455) Added support for dynamic `detailCellRendererParams` in Master/Detail, including dynamic detail-grid column definitions. ### Changed - [#452](https://github.com/plotly/dash-ag-grid/pull/452) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index b1adba1d..3fc6f812 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -644,6 +644,13 @@ export function DashAgGrid(props) { }, value); } if (GRID_NESTED_FUNCTIONS[target]) { + if ( + target === 'rowSelection' && + typeof value === 'string' + ) { + // to still support rowSelection='single' | 'multiple' deprecated in v32.3.4 + return value; + } if (target === 'detailCellRendererParams') { if (has('function', value)) { const dynamicDetailParams = @@ -658,32 +665,11 @@ export function DashAgGrid(props) { dynamicDetailParams ); } - return normalizeDetailCellRendererParams(value); - } - - let adjustedVal = value; - if ( - target === 'rowSelection' && - typeof value === 'string' - ) { - // to still support rowSelection='single' | 'multiple' deprecated in v32.3.4 - return value; - } - if ('suppressCallback' in value) { - adjustedVal = { - ...adjustedVal, - getDetailRowData: value.suppressCallback - ? suppressGetDetail(value.detailColName) - : callbackGetDetail, - }; - } - if ('detailGridOptions' in value) { - adjustedVal = assocPath( - ['detailGridOptions', 'components'], - components, - adjustedVal - ); } + const adjustedVal = + target === 'detailCellRendererParams' + ? normalizeDetailCellRendererParams(value) + : value; return convertAllPropsRef.current(adjustedVal); } if (GRID_DANGEROUS_FUNCTIONS[target]) { From 94f7f7e7e32677aab68ed2859fac844aeb924ea1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:44:36 +0000 Subject: [PATCH 7/7] test: stabilize master-detail dynamic columns interaction Agent-Logs-Url: https://github.com/plotly/dash-ag-grid/sessions/87adbb38-84cf-46b5-91ce-a5370263340a Co-authored-by: BSd3v <82055130+BSd3v@users.noreply.github.com> --- tests/test_recursive_functions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_recursive_functions.py b/tests/test_recursive_functions.py index 3fddb1a8..eb6c602d 100644 --- a/tests/test_recursive_functions.py +++ b/tests/test_recursive_functions.py @@ -352,7 +352,11 @@ def test_rf003_master_detail_dynamic_columns(dash_duo): '#grid .ag-details-grid [row-index="0"] [aria-colindex="2"]', "24870895" ) - grid.get_cell_expandable(0, 0).click() + grid.get_cell_collapsable(0, 0).click() + until( + lambda: len(dash_duo.find_elements("#grid .ag-details-grid")) == 0, + timeout=3, + ) grid.get_cell_expandable(1, 0).click() until( lambda: [