Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 9b9b1a7

Browse files
committed
fix: use snake_case for sorting traitlets without sync_key workaround
1 parent 7813eaa commit 9b9b1a7

File tree

3 files changed

+43
-39
lines changed

3 files changed

+43
-39
lines changed

bigframes/display/anywidget.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ class TableWidget(_WIDGET_BASE):
7070
max_columns = traitlets.Int(allow_none=True, default_value=None).tag(sync=True)
7171
row_count = traitlets.Int(allow_none=True, default_value=None).tag(sync=True)
7272
table_html = traitlets.Unicode("").tag(sync=True)
73-
sort_context = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
73+
sort_columns = traitlets.List(traitlets.Unicode(), default_value=[]).tag(sync=True)
74+
sort_ascending = traitlets.List(traitlets.Bool(), default_value=[]).tag(sync=True)
7475
orderable_columns = traitlets.List(traitlets.Unicode(), []).tag(sync=True)
7576
_initial_load_complete = traitlets.Bool(False).tag(sync=True)
7677
_batches: Optional[blocks.PandasBatches] = None
@@ -305,8 +306,8 @@ def _set_table_html(self) -> None:
305306

306307
# Apply sorting if a column is selected
307308
df_to_display = self._dataframe
308-
sort_columns = [item["column"] for item in self.sort_context]
309-
sort_ascending = [item["ascending"] for item in self.sort_context]
309+
sort_columns = list(self.sort_columns)
310+
sort_ascending = list(self.sort_ascending)
310311

311312
if sort_columns:
312313
# TODO(b/463715504): Support sorting by index columns.
@@ -382,10 +383,11 @@ def _set_table_html(self) -> None:
382383
# re-enter _set_table_html. Since we've released the lock, this is safe.
383384
self.page = new_page
384385

385-
@traitlets.observe("sort_context")
386-
def _sort_changed(self, _change: dict[str, Any]):
386+
@traitlets.observe("sort_columns", "sort_ascending")
387+
def _sort_changed(self, change: dict[str, Any]):
387388
"""Handler for when sorting parameters change from the frontend."""
388-
self._set_table_html()
389+
if len(self.sort_columns) == len(self.sort_ascending):
390+
self._set_table_html()
389391

390392
@traitlets.observe("page")
391393
def _page_changed(self, _change: dict[str, Any]) -> None:
@@ -402,7 +404,8 @@ def _page_size_changed(self, _change: dict[str, Any]) -> None:
402404
# Reset the page to 0 when page size changes to avoid invalid page states
403405
self.page = 0
404406
# Reset the sort state to default (no sort)
405-
self.sort_context = []
407+
self.sort_ascending = []
408+
self.sort_columns = []
406409

407410
# Reset batches to use new page size for future data fetching
408411
self._reset_batches_for_new_page_size()

bigframes/display/table_widget.js

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const ModelProperty = {
2020
PAGE: 'page',
2121
PAGE_SIZE: 'page_size',
2222
ROW_COUNT: 'row_count',
23-
SORT_CONTEXT: 'sort_context',
23+
SORT_COLUMNS: 'sort_columns',
24+
SORT_ASCENDING: 'sort_ascending',
2425
TABLE_HTML: 'table_html',
2526
MAX_COLUMNS: 'max_columns',
2627
};
@@ -192,10 +193,10 @@ function render({ model, el }) {
192193
}, 0);
193194

194195
const sortableColumns = model.get(ModelProperty.ORDERABLE_COLUMNS);
195-
const currentSortContext = model.get(ModelProperty.SORT_CONTEXT) || [];
196+
const currentSortColumns = model.get(ModelProperty.SORT_COLUMNS) || [];
197+
const currentSortAscending = model.get(ModelProperty.SORT_ASCENDING) || [];
196198

197-
const getSortIndex = (colName) =>
198-
currentSortContext.findIndex((item) => item.column === colName);
199+
const getSortIndex = (colName) => currentSortColumns.indexOf(colName);
199200

200201
const headers = tableContainer.querySelectorAll('th');
201202
headers.forEach((header) => {
@@ -214,7 +215,7 @@ function render({ model, el }) {
214215
const sortIndex = getSortIndex(columnName);
215216

216217
if (sortIndex !== -1) {
217-
const isAscending = currentSortContext[sortIndex].ascending;
218+
const isAscending = currentSortAscending[sortIndex];
218219
indicator = isAscending ? '▲' : '▼';
219220
indicatorSpan.style.visibility = 'visible'; // Sorted arrows always visible
220221
} else {
@@ -239,48 +240,46 @@ function render({ model, el }) {
239240
}
240241
});
241242

242-
// Add click handler for three-state toggle
243243
header.addEventListener(Event.CLICK, (event) => {
244244
const sortIndex = getSortIndex(columnName);
245-
let newContext = [...currentSortContext];
245+
let newSortColumns = [...currentSortColumns];
246+
let newSortAscending = [...currentSortAscending];
246247

247248
if (event.shiftKey) {
248249
if (sortIndex !== -1) {
249250
// Already sorted. Toggle or Remove.
250-
if (newContext[sortIndex].ascending) {
251+
if (newSortAscending[sortIndex]) {
251252
// Asc -> Desc
252-
// Clone object to avoid mutation issues
253-
newContext[sortIndex] = {
254-
...newContext[sortIndex],
255-
ascending: false,
256-
};
253+
newSortAscending[sortIndex] = false;
257254
} else {
258255
// Desc -> Remove
259-
newContext.splice(sortIndex, 1);
256+
newSortColumns.splice(sortIndex, 1);
257+
newSortAscending.splice(sortIndex, 1);
260258
}
261259
} else {
262260
// Not sorted -> Append Asc
263-
newContext.push({ column: columnName, ascending: true });
261+
newSortColumns.push(columnName);
262+
newSortAscending.push(true);
264263
}
265264
} else {
266265
// No shift key. Single column mode.
267-
if (sortIndex !== -1 && newContext.length === 1) {
266+
if (sortIndex !== -1 && newSortColumns.length === 1) {
268267
// Already only this column. Toggle or Remove.
269-
if (newContext[sortIndex].ascending) {
270-
newContext[sortIndex] = {
271-
...newContext[sortIndex],
272-
ascending: false,
273-
};
268+
if (newSortAscending[sortIndex]) {
269+
newSortAscending[sortIndex] = false;
274270
} else {
275-
newContext = [];
271+
newSortColumns = [];
272+
newSortAscending = [];
276273
}
277274
} else {
278275
// Start fresh with this column
279-
newContext = [{ column: columnName, ascending: true }];
276+
newSortColumns = [columnName];
277+
newSortAscending = [true];
280278
}
281279
}
282280

283-
model.set(ModelProperty.SORT_CONTEXT, newContext);
281+
model.set(ModelProperty.SORT_ASCENDING, newSortAscending);
282+
model.set(ModelProperty.SORT_COLUMNS, newSortColumns);
284283
model.save_changes();
285284
});
286285
}

tests/unit/display/test_anywidget.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,12 @@ def test_sorting_single_column(mock_df):
134134
widget = TableWidget(mock_df)
135135

136136
# Verify initial state
137-
assert widget.sort_context == []
137+
assert widget.sort_columns == []
138+
assert widget.sort_ascending == []
138139

139140
# Apply sort
140-
widget.sort_context = [{"column": "col1", "ascending": True}]
141+
widget.sort_columns = ["col1"]
142+
widget.sort_ascending = [True]
141143

142144
# This should trigger _sort_changed -> _set_table_html
143145
# which calls df.sort_values
@@ -153,10 +155,8 @@ def test_sorting_multi_column(mock_df):
153155
widget = TableWidget(mock_df)
154156

155157
# Apply multi-column sort
156-
widget.sort_context = [
157-
{"column": "col1", "ascending": True},
158-
{"column": "col2", "ascending": False},
159-
]
158+
widget.sort_columns = ["col1", "col2"]
159+
widget.sort_ascending = [True, False]
160160

161161
mock_df.sort_values.assert_called_with(by=["col1", "col2"], ascending=[True, False])
162162

@@ -169,13 +169,15 @@ def test_page_size_change_resets_sort(mock_df):
169169
widget = TableWidget(mock_df)
170170

171171
# Set sort state
172-
widget.sort_context = [{"column": "col1", "ascending": True}]
172+
widget.sort_columns = ["col1"]
173+
widget.sort_ascending = [True]
173174

174175
# Change page size
175176
widget.page_size = 50
176177

177178
# Sort should be reset
178-
assert widget.sort_context == []
179+
assert widget.sort_columns == []
180+
assert widget.sort_ascending == []
179181

180182
# to_pandas_batches called again (reset)
181183
assert mock_df.to_pandas_batches.call_count >= 2

0 commit comments

Comments
 (0)