Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion marimo/_plugins/ui/_impl/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1353,7 +1353,13 @@ def clamp_rows_and_columns(manager: TableManager[Any]) -> str:

# Do not clamp if max_columns is None
if max_columns is not None and len(column_names) > max_columns:
data = data.select_columns(column_names[:max_columns])
columns_to_select = column_names[:max_columns]
# Always include _marimo_row_id so the frontend can use
# stable row IDs for selection, even when columns are clamped.
# Without this, filtering + selecting returns wrong rows.
if self._has_stable_row_id:
columns_to_select = [INDEX_COLUMN_NAME] + columns_to_select
data = data.select_columns(columns_to_select)

try:
return data.to_json_str(self._format_mapping)
Expand Down
57 changes: 55 additions & 2 deletions tests/_plugins/ui/_impl/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,9 @@ def test_column_clamping_with_dataframes(df: Any):
assert table._component_args["max-columns"] == DEFAULT_MAX_COLUMNS
json_data = json.loads(table._component_args["data"])
headers = json_data[0].keys()
assert len(headers) == DEFAULT_MAX_COLUMNS # 50 columns
assert (
len(headers) == DEFAULT_MAX_COLUMNS + 1
) # 50 columns + _marimo_row_id
# Field types are not clamped
assert len(table._component_args["field-types"]) == 60

Expand All @@ -1594,7 +1596,7 @@ def test_column_clamping_with_dataframes(df: Any):
assert table._component_args["max-columns"] == 40
json_data = json.loads(table._component_args["data"])
headers = json_data[0].keys()
assert len(headers) == 40 # 40 columns
assert len(headers) == 41 # 40 columns + _marimo_row_id
# Field types aren't clamped
assert len(table._component_args["field-types"]) == 60

Expand All @@ -1611,6 +1613,57 @@ def test_column_clamping_with_dataframes(df: Any):
assert len(table._component_args["field-types"]) == 60


@pytest.mark.parametrize(
"df",
create_dataframes(
{
"person": ["Alice", "Bob", "Charlie"],
"age": [20, 30, 40],
**{f"col{i}": [1, 2, 3] for i in range(49)},
},
exclude=NON_EAGER_LIBS,
),
)
def test_selection_with_clamped_columns_and_filter(df: Any):
"""Regression test for #8029: selection returns wrong row when table
has more columns than max_columns and is filtered."""
import narwhals as nw

table = ui.table(df, max_columns=50)

# The data sent to frontend should include _marimo_row_id
# even when columns are clamped
json_data = json.loads(table._component_args["data"])
assert INDEX_COLUMN_NAME in json_data[0]

# Apply a filter to show only rows where age >= 30
# (should return Bob and Charlie, with _marimo_row_id 1 and 2)
search_args = SearchTableArgs(
page_size=10,
page_number=0,
filters=[Condition(column_id="age", operator=">=", value=30)],
)
response = table._search(search_args)
result_data = json.loads(response.data)

# Filtered data should still include _marimo_row_id
assert INDEX_COLUMN_NAME in result_data[0]
assert len(result_data) == 2 # Bob and Charlie

# Select row with _marimo_row_id=2 (Charlie)
value = table._convert_value(["2"])
assert not isinstance(value, nw.DataFrame)
nw_value = nw.from_native(value)
assert nw_value["person"][0] == "Charlie"
assert nw_value["age"][0] == 40

# Select row with _marimo_row_id=1 (Bob)
value = table._convert_value(["1"])
nw_value = nw.from_native(value)
assert nw_value["person"][0] == "Bob"
assert nw_value["age"][0] == 30


@pytest.mark.skipif(
not DependencyManager.pandas.has(), reason="Pandas not installed"
)
Expand Down
Loading