Skip to content

Commit 530ba52

Browse files
authored
bug(data frame): Subset selected cells from the filtered rows (#1412)
1 parent 58e3a7b commit 530ba52

File tree

6 files changed

+143
-9
lines changed

6 files changed

+143
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929

3030
* Removed temporary state where a data frame renderer would try to subset to selected rows that did not exist. (#1351, #1377)
3131

32+
* Fix an issue in the data frame output which caused the table to freeze when filters removed previously selected cells. (#1412)
33+
3234
### Other changes
3335

3436
* `Session` is now an abstract base class, and `AppSession` is a concrete subclass of it. Also, `ExpressMockSession` has been renamed `ExpressStubSession` and is a concrete subclass of `Session`. (#1331)

js/data-frame/index.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,15 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
328328
const rowsById = table.getSortedRowModel().rowsById;
329329
shinyValue = {
330330
type: "row",
331-
rows: rowSelectionKeys.map((key) => rowsById[key].index).sort(),
331+
rows: rowSelectionKeys
332+
.map((key) => {
333+
if (!(key in rowsById)) {
334+
return null;
335+
}
336+
return rowsById[key].index;
337+
})
338+
.filter((x): x is number => x !== null)
339+
.sort(),
332340
};
333341
} else {
334342
console.error("Unhandled row selection mode:", rowSelectionModes);
@@ -370,7 +378,15 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
370378
if (rowSelectionModes.row !== SelectionModes._rowEnum.NONE) {
371379
const rowSelectionKeys = rowSelection.keys().toList();
372380
const rowsById = table.getSortedRowModel().rowsById;
373-
shinyValue = rowSelectionKeys.map((key) => rowsById[key].index).sort();
381+
shinyValue = rowSelectionKeys
382+
.map((key) => {
383+
if (!(key in rowsById)) {
384+
return null;
385+
}
386+
return rowsById[key].index;
387+
})
388+
.filter((x): x is number => x !== null)
389+
.sort();
374390
}
375391
Shiny.setInputValue!(`${id}_selected_rows`, shinyValue);
376392
}, [id, rowSelection, rowSelectionModes, table]);

shiny/www/shared/py-shiny/data-frame/data-frame.js

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

shiny/www/shared/py-shiny/data-frame/data-frame.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pandas as pd
2+
3+
from shiny import req
4+
from shiny.express import render, ui
5+
6+
df = pd.DataFrame(data={"a": [str(i) for i in range(10)]})
7+
8+
9+
with ui.layout_column_wrap(width=1 / 2):
10+
with ui.card():
11+
ui.card_header("Original data")
12+
13+
@render.data_frame
14+
def my_df():
15+
return render.DataGrid(data=df, filters=True, selection_mode="rows")
16+
17+
with ui.card():
18+
ui.card_header("Selected data")
19+
20+
@render.data_frame
21+
def selected_df():
22+
return my_df.data_view(selected=True)
23+
24+
with ui.card():
25+
ui.card_header("Selected rows")
26+
27+
@render.code
28+
def selected_rows():
29+
return str(req(my_df.cell_selection() or {}).get("rows", ()))
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from __future__ import annotations
2+
3+
import platform
4+
5+
from conftest import ShinyAppProc
6+
from controls import OutputCode, OutputDataFrame
7+
from playwright.sync_api import Page, expect
8+
9+
10+
def test_row_selection(page: Page, local_app: ShinyAppProc) -> None:
11+
page.goto(local_app.url)
12+
13+
my_df = OutputDataFrame(page, "my_df")
14+
selected_df = OutputDataFrame(page, "selected_df")
15+
selected_rows = OutputCode(page, "selected_rows")
16+
17+
# TODO-karan-test: We should add a locator for selected rows
18+
# TODO-karan-test; We should add a expected selected row count method?
19+
def expect_selected_count(x: OutputDataFrame, n: int) -> None:
20+
expect(x.loc_body.locator("tr[aria-selected=true]")).to_have_count(n)
21+
22+
my_df.expect_n_row(10)
23+
expect_selected_count(my_df, 0)
24+
selected_df.expect_n_row(0)
25+
selected_rows.expect_value("()")
26+
27+
# Select row
28+
my_df.select_rows([5, 7])
29+
my_df.expect_n_row(10)
30+
expect_selected_count(my_df, 2)
31+
selected_df.expect_n_row(2)
32+
selected_rows.expect_value("(5, 7)")
33+
34+
# # Filter to different row
35+
# Currently this causes an error
36+
my_df.set_column_filter(0, text="5")
37+
my_df.expect_n_row(1)
38+
expect_selected_count(my_df, 1)
39+
selected_df.expect_n_row(1)
40+
selected_rows.expect_value("(5,)")
41+
# # Remove the filter
42+
my_df.set_column_filter(0, text="")
43+
# Confirm the original data frame returns
44+
my_df.expect_n_row(10)
45+
# Confirm the previous row is still selected as no new selection has been made
46+
expect_selected_count(my_df, 2)
47+
selected_df.expect_n_row(2)
48+
selected_rows.expect_value("(5, 7)")
49+
50+
# Filter to non selected row
51+
# Currently this causes an error
52+
my_df.set_column_filter(0, text="8")
53+
my_df.expect_n_row(1)
54+
expect_selected_count(my_df, 0)
55+
selected_df.expect_n_row(0)
56+
selected_rows.expect_value("()")
57+
58+
# Remove the filter
59+
# Confirm the original data frame returns
60+
my_df.set_column_filter(0, text="")
61+
my_df.expect_n_row(10)
62+
expect_selected_count(my_df, 2)
63+
selected_df.expect_n_row(2)
64+
selected_rows.expect_value("(5, 7)")
65+
66+
# Update selection with new selection while under a filter
67+
my_df.set_column_filter(0, text="8")
68+
my_df.expect_n_row(1)
69+
expect_selected_count(my_df, 0)
70+
selected_df.expect_n_row(0)
71+
selected_rows.expect_value("()")
72+
my_df.select_rows([0])
73+
my_df.set_column_filter(0, text="")
74+
my_df.expect_n_row(10)
75+
expect_selected_count(my_df, 1)
76+
selected_df.expect_n_row(1)
77+
selected_rows.expect_value("(8,)")
78+
79+
# Remove selection
80+
modifier = "Meta" if platform.system() == "Darwin" else "Control"
81+
for row in (8,):
82+
my_df.cell_locator(row=row, col=0).click(
83+
modifiers=[modifier]
84+
) # TODO-karan-test: Support unselect row method
85+
expect_selected_count(my_df, 0)
86+
selected_df.expect_n_row(0)
87+
selected_rows.expect_value("()")

0 commit comments

Comments
 (0)