From 8fa4152731fd68e6a66f5173d4e6e16130fd8635 Mon Sep 17 00:00:00 2001 From: malteos Date: Wed, 10 Jun 2026 08:45:29 +0200 Subject: [PATCH 1/2] fix(leaderboard): restore per-language drilldown on row click Read the clicked row's model_id from `gr.SelectData.row_value` directly instead of resolving it via the Dataframe component as an event input. Gradio 6's `Dataframe.preprocess` converts the value to a `pandas.DataFrame` before handing it to the callback, so the `isinstance(table_value, dict)` branch I added in #5 never fired and every row click silently returned the "click a row" placeholder. `row_value` carries the full clicked row (Gradio docs: "the value of the entire row that the selected item belongs to, as a 1-D list"), so the post-toggle ordering is still respected without us having to thread the Dataframe through the input list. Add a regression test that hands `_on_select` a mock SelectData with an `index` + `row_value` and asserts the drilldown payload comes back populated. --- src/commonlid/leaderboard/app.py | 24 +++++++-------- tests/unit/test_leaderboard_data.py | 45 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/commonlid/leaderboard/app.py b/src/commonlid/leaderboard/app.py index 9c3f658..e5d50c4 100644 --- a/src/commonlid/leaderboard/app.py +++ b/src/commonlid/leaderboard/app.py @@ -410,26 +410,25 @@ def _make_select_handler( ) -> Any: """Build the row-select callback as a closure over the captured state. - The callback looks up the clicked row in the *current* table value - (passed in via Gradio's event arg) so that switching the scope radio - and then clicking a row drills down the row at its post-toggle - position, not the row that would have been there before the swap. + Uses ``gr.SelectData.row_value`` (Gradio's per-click payload that + contains the clicked row as a 1-D list) so the drilldown picks up the + *current* table ordering — switching the scope radio and then clicking + a row resolves to the row at its post-toggle position. Passing the + Dataframe component as an event input would not work: Gradio 6 + preprocesses Dataframe inputs into ``pandas.DataFrame`` objects, not + the ``{"data", "headers"}`` dict we feed in via ``_styled_value``. Gradio inspects ``__defaults__`` when registering events, and comparing a DataFrame default against a type annotation hits an unimplemented arrow dtype path. A closure keeps the state out of the function signature. """ - def _on_select(table_value: Any, evt: gr.SelectData) -> tuple[str, Any]: - if evt.index is None: + def _on_select(evt: gr.SelectData) -> tuple[str, Any]: + if evt.index is None or not evt.row_value: return ("_Click a row to load per-language metrics._", None) - row_idx = evt.index[0] if isinstance(evt.index, list | tuple) else evt.index try: - data = table_value.get("data") if isinstance(table_value, dict) else None - if data is None: - return ("_Click a row to load per-language metrics._", None) - model_id = data[row_idx][0] - except (IndexError, KeyError, TypeError): + model_id = evt.row_value[0] + except (IndexError, TypeError): return ("_Could not resolve clicked row._", None) per_lang = _per_language_drilldown(snapshot_root, dataset_id, model_id) return ( @@ -540,7 +539,6 @@ def build_app( ) leaderboard.select( _make_select_handler(dataset_id, snapshot_root), - inputs=[leaderboard], outputs=[drilldown_label, drilldown], ) gr.Markdown(footer) diff --git a/tests/unit/test_leaderboard_data.py b/tests/unit/test_leaderboard_data.py index 4e9be30..8fb75d2 100644 --- a/tests/unit/test_leaderboard_data.py +++ b/tests/unit/test_leaderboard_data.py @@ -466,6 +466,51 @@ def test_format_table_cov_scope_renders_em_dashes(tmp_path: Path) -> None: assert cov_table.iloc[1]["Samples/s"] == "1234.5" +def test_row_select_handler_loads_drilldown_from_row_value(tmp_path: Path) -> None: + """Clicking a row resolves model_id via ``evt.row_value[0]`` and renders the drilldown. + + Regression: an earlier version pulled the model_id from the Dataframe + input, but Gradio 6 preprocesses Dataframe inputs into ``pandas.DataFrame`` + objects rather than the ``{"data", "headers"}`` dict the app feeds in, + so the handler silently returned the "click a row" placeholder. + """ + pytest.importorskip("gradio") + from types import SimpleNamespace + + from commonlid.leaderboard.app import _make_select_handler + + per_lang = _per_language_block({"eng": (10, 10, 8, 0.01), "fra": (10, 10, 4, 0.02)}) + _write_summary(tmp_path, "commonlid", "GlotLID", per_language=per_lang) + handler = _make_select_handler("commonlid", tmp_path) + + evt = SimpleNamespace( + index=(0, 0), + value="GlotLID", + row_value=["GlotLID", "80.0", "60.0", "0.10", "2", "1234.5"], + ) + label, payload = handler(evt) + assert "GlotLID" in label + assert "commonlid" in label + assert payload is not None + headers = payload["headers"] + assert headers[:2] == ["Language", "F1"] + languages = [row[0] for row in payload["data"]] + assert set(languages) == {"eng", "fra"} + + +def test_row_select_handler_returns_placeholder_when_index_missing(tmp_path: Path) -> None: + pytest.importorskip("gradio") + from types import SimpleNamespace + + from commonlid.leaderboard.app import _make_select_handler + + handler = _make_select_handler("commonlid", tmp_path) + evt = SimpleNamespace(index=None, value=None, row_value=None) + label, payload = handler(evt) + assert "Click a row" in label + assert payload is None + + def test_scope_radio_change_swaps_table_and_legend(tmp_path: Path) -> None: """The scope-change handler returns a fresh styled table + legend Markdown.""" pytest.importorskip("gradio") From f36e1f9784f054349dc50434f46f6adb5bfe1117 Mon Sep 17 00:00:00 2001 From: malteos Date: Wed, 10 Jun 2026 10:57:10 +0200 Subject: [PATCH 2/2] chore(space): install commonlid from github main, not pypi Point the HF Space at git+https://github.com/commoncrawl/commonlid-eval@main so deployment picks up runtime fixes without waiting on a PyPI release. Pinning to a tag or commit SHA stays a single requirements-file edit away. --- hf-space/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hf-space/requirements.txt b/hf-space/requirements.txt index 64eceb4..d8e10d2 100644 --- a/hf-space/requirements.txt +++ b/hf-space/requirements.txt @@ -1,4 +1,4 @@ -# The Space pulls the package from PyPI with the leaderboard extra so the -# Gradio entrypoint and `commonlid leaderboard serve` CLI stay in sync. Pin a -# concrete release here once we tag one (e.g. `commonlid[leaderboard]==0.1.0`). -commonlid[leaderboard] +# Install commonlid directly from GitHub main so the Space picks up changes +# without a PyPI release cycle. Pin to a tag or commit SHA (e.g. +# `...@v0.2.3` or `...@`) if you need to lock the Space to a known build. +commonlid[leaderboard] @ git+https://github.com/commoncrawl/commonlid-eval.git@main