From 24d801b7f799912cb4eb897a97e4f4a9fe76b966 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Wed, 25 Feb 2026 16:33:27 -0800 Subject: [PATCH] Respect metadata-defined facet ordering in sorted_facet_results (#2648) * Preserve metadata-defined facet ordering on table pages When facets are explicitly defined in table metadata/config, they now appear in the order specified in the configuration rather than being sorted by result count. Request-added facets still appear after metadata-defined facets, sorted by count as before. * Document metadata-defined facet ordering behavior * Apply black formatting https://claude.ai/code/session_01PbSHtjsUpNk3Fx7xjvVqDb --- datasette/views/table.py | 34 ++++++++++++++++++++++++++----- docs/facets.rst | 2 ++ tests/test_facets.py | 44 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/datasette/views/table.py b/datasette/views/table.py index 594e925e..e1e5507f 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -1580,11 +1580,35 @@ async def table_view_data( ] async def extra_sorted_facet_results(extra_facet_results): - return sorted( - extra_facet_results["results"].values(), - key=lambda f: (len(f["results"]), f["name"]), - reverse=True, - ) + facet_configs = table_metadata.get("facets", []) + if facet_configs: + # Build ordered list of facet names from metadata config + metadata_facet_names = [] + for fc in facet_configs: + if isinstance(fc, str): + metadata_facet_names.append(fc) + elif isinstance(fc, dict): + metadata_facet_names.append(list(fc.values())[0]) + metadata_order = {name: i for i, name in enumerate(metadata_facet_names)} + metadata_facets = [] + request_facets = [] + for f in extra_facet_results["results"].values(): + if f["name"] in metadata_order: + metadata_facets.append(f) + else: + request_facets.append(f) + metadata_facets.sort(key=lambda f: metadata_order[f["name"]]) + request_facets.sort( + key=lambda f: (len(f["results"]), f["name"]), + reverse=True, + ) + return metadata_facets + request_facets + else: + return sorted( + extra_facet_results["results"].values(), + key=lambda f: (len(f["results"]), f["name"]), + reverse=True, + ) async def extra_table_definition(): return await db.get_table_definition(table_name) diff --git a/docs/facets.rst b/docs/facets.rst index 15fe7227..2a135b69 100644 --- a/docs/facets.rst +++ b/docs/facets.rst @@ -153,6 +153,8 @@ Here's an example that turns on faceting by default for the ``qLegalStatus`` col Facets defined in this way will always be shown in the interface and returned in the API, regardless of the ``_facet`` arguments passed to the view. +Facets defined in metadata will be displayed in the order they are listed in the configuration. Any additional facets added via query string parameters (e.g. ``?_facet=column_name``) will appear after the metadata-defined facets, sorted by the number of unique values. + You can specify :ref:`array ` or :ref:`date ` facets in metadata using JSON objects with a single key of ``array`` or ``date`` and a value specifying the column, like this: .. [[[cog diff --git a/tests/test_facets.py b/tests/test_facets.py index a2b505ec..8c22ffce 100644 --- a/tests/test_facets.py +++ b/tests/test_facets.py @@ -623,12 +623,48 @@ def test_other_types_of_facet_in_metadata(): } ) as client: response = client.get("/fixtures/facetable") - for fragment in ( - "created (date)\n", - "tags (array)\n", + fragments = ( "state\n", - ): + "tags (array)\n", + "created (date)\n", + ) + for fragment in fragments: assert fragment in response.text + # Verify they appear in the metadata-defined order + positions = [response.text.index(f) for f in fragments] + assert positions == sorted( + positions + ), "Facets should appear in metadata-defined order" + + +def test_metadata_facet_ordering(): + with make_app_client( + metadata={ + "databases": { + "fixtures": { + "tables": { + "facetable": { + "facets": ["state", {"array": "tags"}, {"date": "created"}] + } + } + } + } + } + ) as client: + # JSON response should have facets in the metadata-defined order + response = client.get("/fixtures/facetable.json?_extra=sorted_facet_results") + data = response.json + facet_names = [f["name"] for f in data["sorted_facet_results"]] + assert facet_names == ["state", "tags", "created"] + + # With an additional request-based facet, metadata facets come first + # in their defined order, followed by request-based facets + response2 = client.get( + "/fixtures/facetable.json?_extra=sorted_facet_results&_facet=_city_id" + ) + data2 = response2.json + facet_names2 = [f["name"] for f in data2["sorted_facet_results"]] + assert facet_names2 == ["state", "tags", "created", "_city_id"] @pytest.mark.asyncio