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
This commit is contained in:
Simon Willison 2026-02-25 16:33:27 -08:00 committed by GitHub
commit 24d801b7f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 9 deletions

View file

@ -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)

View file

@ -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 <facet_by_json_array>` or :ref:`date <facet_by_date>` facets in metadata using JSON objects with a single key of ``array`` or ``date`` and a value specifying the column, like this:
.. [[[cog

View file

@ -623,12 +623,48 @@ def test_other_types_of_facet_in_metadata():
}
) as client:
response = client.get("/fixtures/facetable")
for fragment in (
"<strong>created (date)\n",
"<strong>tags (array)\n",
fragments = (
"<strong>state\n",
):
"<strong>tags (array)\n",
"<strong>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