Repository navigation
fix(dataeditor): apply default column type and id to reactive columns - #7597
anishgurjar wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
[Medium impact] The PR appears safe to merge, with one non-blocking test coverage gap remaining from the previous review. Findings
SummaryApplies default
Reviews (3) · Last reviewed commit: "chore: refresh DataEditor generated stub..." · Reviewed by Greptile |
| def test_dataeditor_reactive_columns_js_semantics(): | ||
| test_script = """ | ||
| const cols = [{ title: "Name" }, { title: "Age", type: "int" }].map( | ||
| (col) => ({ type: "str", id: (col.title ?? "").toLowerCase(), ...col }) | ||
| ); | ||
| const row = { name: "Alice" }; | ||
| console.log(JSON.stringify([cols, row[cols[0].id]])); | ||
| """ |
There was a problem hiding this comment.
The dict-row test only checks the prop text. This JavaScript test runs a hand-written copy of the map and reads the row directly. Neither checks that the generated cell callback returns a valid cell through formatDataEditorCells, so both can pass if that callback stops using the normalized columns.
Execute the generated expression with the real helper, including a column whose explicit id differs from its lowercased title.
Merging this PR will not alter performance
|
FarhanAliRaza
left a comment
There was a problem hiding this comment.
Can you please fix the review posted.
| props["columns"] = columns._replace( | ||
| _js_expr=( | ||
| f'({columns!s}).map((col) => ({{ type: "str", ' | ||
| f'id: (col.title ?? "").toLowerCase(), ...col }}))' | ||
| ) | ||
| ) |
There was a problem hiding this comment.
add_hooks builds the cell callback from str(self.columns). That is now this wrapped expression. So the compiled getData_* runs the .map() on every cell call:
return formatDataEditorCells(col, row, (state.cols).map((col) => ({ type: "str", id: ..., ...col })), state.data);Glide calls this callback for each visible cell on each draw. So one draw rebuilds all column objects once per cell. In a Node benchmark with 40 rows, the extra cost per draw goes from 0.02 ms to 0.45 ms at 20 columns, and to 2.5 ms at 50 columns.
Please compute the wrapped columns once per render. Then use that one value in the columns prop and in getData.
| # Reactive columns skip the literal formatting above, so apply the | ||
| # documented default column type when rendering (#7596). An | ||
| # explicit per-column type still wins via the spread order. |
There was a problem hiding this comment.
This comment is not accurate. The literal formatting is in the elif below, not above. The code also sets a default id, not only a default type. Please make the comment state what the code does.
| wrapper = DataEditor.create(columns=EditorState.columns) | ||
| editor = cast(DataEditor, wrapper.children[0]) | ||
| # The wrapped expression must keep the original var's data (state dep). | ||
| assert editor.columns._var_data == EditorState.columns._var_data |
There was a problem hiding this comment.
This line adds 2 pyright errors. The DataEditor stub has no columns attribute. The field type is a list, which has no _var_data. The same file on main has 0 pyright errors. Please make uv run pyright clean for this file.
| def test_dataeditor_reactive_columns_default_id_supports_dict_rows(): | ||
| # Dict rows are keyed by column id; the JS-side id default keeps them | ||
| # working for reactive columns that omit id. See issue #7596. | ||
| editor = DataEditor.create( | ||
| columns=Var(_js_expr="state.columns"), | ||
| data=[{"name": "Alice"}], | ||
| ).render() | ||
| assert 'id: (col.title ?? "").toLowerCase()' in _columns_prop(editor) |
There was a problem hiding this comment.
This test asserts the same string as test_dataeditor_reactive_columns_default_type. The data argument has no effect on the assertion, so nothing here exercises dict rows. Line 142 also adds a pyright error: list[dict[str, str]] does not match Sequence[Sequence[Any]].
Please remove this test, or move the dict-row lookup into the Node test below.
| assert 'id: (col.title ?? "").toLowerCase()' in _columns_prop(editor) | ||
|
|
||
|
|
||
| def test_dataeditor_reactive_columns_js_semantics(): |
There was a problem hiding this comment.
This test runs a copy of the JS that is written in the test. It does not run the expression that create() generates. If the generated expression changes or breaks, this test still passes.
Please evaluate the generated expression instead. For example, take str(...) of the columns prop for Var(_js_expr="state.columns"), and run it in Node with a stub state object. Then assert the default type, the default id, the explicit override, and the dict-row lookup.
| @@ -0,0 +1 @@ | |||
| Apply the documented default column `type` and `id` to reactive (state-backed) DataEditor columns, fixing a render crash when a column omits them. | |||
There was a problem hiding this comment.
The file name uses the issue number. towncrier's issue_format turns 7596 into a link to the issue, not to this PR. Please rename the file to 7597.bugfix.md or +<slug>.bugfix.md.
…ced by the prop and cell callback
…rid prop and cell callback
|
Addressed all six, PTAL |
masenf
left a comment
There was a problem hiding this comment.
i like the idea here, but the implementation is taking this component away from the direction we're heading on the roadmap.
i have an alternate implementation that I will propose that leverages hooks in VarData worked out during create, so this component doesn't need to relying on post-create mutability to work
Summary
DataEditor.createonly applies the documented default column type ("str") to literal column lists. Columns passed as a reactive stateVarskip that branch and reach Glide DataEditor without atype, producing an invalid TextCell and crashing at render. Untyped reactive columns also reach Glide without anid, so dict-row data hits the same crash.This applies the defaults at render time for reactive columns: the columns expression is wrapped so each entry is spread over
{ type: "str", id: (col.title ?? "").toLowerCase() }, so an explicit per-columntypeoridstill wins (same semantics as the literal path viaformat_data_editor_column). The wrap usesVar._replace, which preserves the original var's_var_data, so state subscriptions and imports are unchanged.Alternative considered: patching the default branch of
formatCellin.templates/web/utils/helpers/dataeditor.js. That would only stop the TextCell crash at render; the python-side wrap normalizes the column definition itself, which keeps the default co-located with the literal path and also fixes dict-row lookup (id). Note the wrap produces a fresh columns array identity on each render (.mapover the reactive value); Glide already re-renders on reactive prop changes, so no memoization contract changes.Fixes #7596
Type of change
Testing
test_dataeditor_reactive_columns_default_type: reactiveVarcolumns render as(state.columns).map((col) => ({ type: "str", id: (col.title ?? "").toLowerCase(), ...col }))(columns prop looked up by name).test_dataeditor_reactive_columns_preserve_var_data:rx.State-backed test pinning that the wrap preserves_var_data(state dependency tracked).test_dataeditor_reactive_columns_default_id_supports_dict_rows: dict-row data renders without crashing.test_dataeditor_reactive_columns_js_semantics: node-checked JS semantics - default applies, explicittype/idoverride wins.test_dataeditor_literal_columns_default_type: pins existing literal-path behavior.pytest tests/units/components/datadisplay/test_dataeditor.py: 8 passed.pyright: 0 errors.ruff check/ruff format --check: clean.pytest tests/units: 12746 passed; 249 failures are all inreflex_cli/v2(deploy/auth/gcp cloud tests needing credentials/network) plus one tty frontend test, none import or touch dataeditor.cc @masenf