diff --git a/frontend/src/plugins/impl/DataTablePlugin.tsx b/frontend/src/plugins/impl/DataTablePlugin.tsx index ee67f9359b5..133843d36fb 100644 --- a/frontend/src/plugins/impl/DataTablePlugin.tsx +++ b/frontend/src/plugins/impl/DataTablePlugin.tsx @@ -255,6 +255,7 @@ export const DataTablePlugin = createPlugin("marimo-table") .nullable() .default(null), showDownload: z.boolean().default(false), + defaultSort: z.string().optional(), showFilters: z.boolean().default(false), showColumnSummaries: z .union([z.boolean(), z.enum(["stats", "chart"])]) @@ -500,8 +501,15 @@ export const LoadingDataTableComponent = memo( const search = props.search; const setValue = props.setValue; + const initialSorting = useMemo( + () => + props.defaultSort + ? [{ id: props.defaultSort, desc: false }] + : Arrays.EMPTY, + [props.defaultSort], + ); // Sorting/searching state - const [sorting, setSorting] = useState([]); + const [sorting, setSorting] = useState(initialSorting); const [paginationState, setPaginationState] = React.useState({ pageSize: props.pageSize, @@ -563,7 +571,11 @@ export const LoadingDataTableComponent = memo( searchQuery === "" && paginationState.pageIndex === 0 && filters.length === 0 && - sorting.length === 0 && + (sorting.length === 0 || + (sorting.length === 1 && + Boolean(props.defaultSort) && + sorting[0]?.id === props.defaultSort && + sorting[0]?.desc === false)) && !props.lazy && !pageSizeChanged; diff --git a/marimo/_plugins/ui/_impl/table.py b/marimo/_plugins/ui/_impl/table.py index 3bc42937624..8d1f0d14ff5 100644 --- a/marimo/_plugins/ui/_impl/table.py +++ b/marimo/_plugins/ui/_impl/table.py @@ -355,6 +355,8 @@ def hover_cell(rowId, columnName, value): Defaults to True. show_download (bool, optional): Whether to show the download button. Defaults to True for dataframes, False otherwise. + default_sort (str, optional): Column name to sort by on initial render. + Sorting is ascending by default. format_mapping (Dict[str, Union[str, Callable[..., Any]]], optional): A mapping from column names to formatting strings or functions. freeze_columns_left (Sequence[str], optional): List of column names to freeze on the left. @@ -469,6 +471,7 @@ def __init__( show_download: bool = True, max_columns: MaxColumnsType = MAX_COLUMNS_NOT_PROVIDED, *, + default_sort: Optional[str] = None, label: str = "", on_change: Optional[ Callable[ @@ -673,14 +676,27 @@ def __init__( field_types: Optional[FieldTypes] = None num_columns = 0 + if default_sort is not None: + existing_columns = set(self._manager.get_column_names()) + if default_sort not in existing_columns: + raise ValueError( + f"default_sort column '{default_sort}' not found in table columns" + ) + if not _internal_lazy: + default_sort_args = ( + [SortArgs(by=default_sort, descending=False)] + if default_sort is not None + else None + ) + # Search first page search_result = self._search( SearchTableArgs( page_size=page_size, page_number=0, query=None, - sort=None, + sort=default_sort_args, filters=None, ) ) @@ -722,6 +738,7 @@ def __init__( "show-filters": self._manager.supports_filters(), "show-download": show_download and self._manager.supports_download(), + "default-sort": default_sort, "show-column-summaries": show_column_summaries, "show-data-types": show_data_types, "show-page-size-selector": show_page_size_selector, diff --git a/tests/_plugins/ui/_impl/test_table.py b/tests/_plugins/ui/_impl/test_table.py index db4cc562944..41e43a5c970 100644 --- a/tests/_plugins/ui/_impl/test_table.py +++ b/tests/_plugins/ui/_impl/test_table.py @@ -2231,6 +2231,34 @@ def test_max_columns_not_provided_with_sort(): assert len(result_data[0].keys()) == 100 +def test_default_sort_applies_on_initial_render() -> None: + data = {"name": ["charlie", "alice", "bob"], "value": [3, 1, 2]} + table = ui.table(data, selection=None, default_sort="name") + + result_data = json.loads(table._component_args["data"]) + assert [row["name"] for row in result_data] == ["alice", "bob", "charlie"] + assert table._component_args["default-sort"] == "name" + + +def test_default_sort_invalid_column_raises() -> None: + data = {"name": ["charlie", "alice", "bob"], "value": [3, 1, 2]} + + with pytest.raises(ValueError, match="default_sort column 'missing'"): + ui.table(data, selection=None, default_sort="missing") + + +def test_default_sort_invalid_column_raises_in_lazy_mode() -> None: + data = {"name": ["charlie", "alice", "bob"], "value": [3, 1, 2]} + + with pytest.raises(ValueError, match="default_sort column 'missing'"): + ui.table( + data, + selection=None, + default_sort="missing", + _internal_lazy=True, + ) + + @pytest.mark.parametrize( "df", create_dataframes(