fix(entity_grid): sortie MATCH unique pour le datasource (dash.set_props)
Le datasource _get_rows des grilles acheteur/titulaire mélangeait une sortie MATCH (getRowsResponse de la grille pattern-matching) avec deux sorties fixes (<org>-total / -total-unique). Dash l'interdit : le dev-renderer (mode debug de run.py) lève « MATCH wildcards must be on the same keys for all Outputs » (4 erreurs : 2 par page). Le callback échoue au dispatch et la grille reste vide. Correctif : _get_rows n'a plus qu'une sortie MATCH (getRowsResponse) ; les stores totaux (id fixes) sont alimentés impérativement via dash.set_props. Test de régression en debug=True (dev-renderer) : sans le correctif la grille ne charge aucune ligne ; avec, lignes + compteur « N marchés » présents.
This commit is contained in:
@@ -11,7 +11,18 @@ Les callbacks Dash sont enregistrés par register_entity_grid_callbacks()
|
|||||||
(cf. module séparé de wiring) ; ici, seules des fonctions pures testables.
|
(cf. module séparé de wiring) ; ici, seules des fonctions pures testables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dash import ALL, MATCH, Input, Output, State, callback, ctx, dcc, no_update
|
from dash import (
|
||||||
|
ALL,
|
||||||
|
MATCH,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
State,
|
||||||
|
callback,
|
||||||
|
ctx,
|
||||||
|
dcc,
|
||||||
|
no_update,
|
||||||
|
set_props,
|
||||||
|
)
|
||||||
|
|
||||||
from src.figures import (
|
from src.figures import (
|
||||||
AG_GRID_LOCALE_FR, # noqa: F401 (réexport pratique)
|
AG_GRID_LOCALE_FR, # noqa: F401 (réexport pratique)
|
||||||
@@ -126,26 +137,32 @@ def register_entity_grid_callbacks(org_type: str) -> None:
|
|||||||
"""
|
"""
|
||||||
gtype = grid_type(org_type)
|
gtype = grid_type(org_type)
|
||||||
|
|
||||||
# 1) Datasource server-side. Input MATCH (grille) → Output MATCH
|
# 1) Datasource server-side. Une seule sortie MATCH (getRowsResponse de la
|
||||||
# (getRowsResponse) + Outputs fixes (stores totaux). Autorisé en Dash 4.4.
|
# grille pattern-matching) : Dash INTERDIT de mélanger une sortie MATCH
|
||||||
|
# avec des sorties fixes (« MATCH wildcards must be on the same keys for
|
||||||
|
# all Outputs », erreur du dev-renderer en mode debug). Les stores totaux
|
||||||
|
# (id fixes) sont donc alimentés impérativement via dash.set_props.
|
||||||
@callback(
|
@callback(
|
||||||
Output({"type": gtype, "entity_id": MATCH, "year": MATCH}, "getRowsResponse"),
|
Output({"type": gtype, "entity_id": MATCH, "year": MATCH}, "getRowsResponse"),
|
||||||
Output(f"{org_type}-total", "data"),
|
|
||||||
Output(f"{org_type}-total-unique", "data"),
|
|
||||||
Input({"type": gtype, "entity_id": MATCH, "year": MATCH}, "getRowsRequest"),
|
Input({"type": gtype, "entity_id": MATCH, "year": MATCH}, "getRowsRequest"),
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
def _get_rows(request):
|
def _get_rows(request):
|
||||||
gid = ctx.triggered_id # {"type", "entity_id", "year"}
|
gid = ctx.triggered_id # {"type", "entity_id", "year"}
|
||||||
if request is None or gid is None:
|
if request is None or gid is None:
|
||||||
return no_update, no_update, no_update
|
return no_update
|
||||||
if request.get("filterModel") and request.get("startRow", 0) == 0:
|
if request.get("filterModel") and request.get("startRow", 0) == 0:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from src.utils.tracking import track_search
|
from src.utils.tracking import track_search
|
||||||
|
|
||||||
track_search(json.dumps(request["filterModel"]), org_type)
|
track_search(json.dumps(request["filterModel"]), org_type)
|
||||||
return fetch_entity_page(org_type, gid["entity_id"], gid["year"], request)
|
response, total, total_unique = fetch_entity_page(
|
||||||
|
org_type, gid["entity_id"], gid["year"], request
|
||||||
|
)
|
||||||
|
set_props(f"{org_type}-total", {"data": total})
|
||||||
|
set_props(f"{org_type}-total-unique", {"data": total_unique})
|
||||||
|
return response
|
||||||
|
|
||||||
# 2) (Re)construit la grille au changement de fiche (URL), d'année, ou de
|
# 2) (Re)construit la grille au changement de fiche (URL), d'année, ou de
|
||||||
# colonnes masquées. Le remontage réinitialise le filterModel (accepté
|
# colonnes masquées. Le remontage réinitialise le filterModel (accepté
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
"""Régression : le datasource server-side de la grille entité (_get_rows) doit
|
||||||
|
répondre sans erreur de callback Dash.
|
||||||
|
|
||||||
|
Bug migration AG Grid (#41) : _get_rows mélangeait une sortie MATCH
|
||||||
|
(getRowsResponse de la grille pattern-matching) avec des sorties fixes
|
||||||
|
(acheteur-total / -total-unique). Dash l'interdit (« MATCH wildcards must be on
|
||||||
|
the same keys for all Outputs ») — l'erreur est levée par le dev-renderer en
|
||||||
|
mode debug (celui de `run.py`), pas par le renderer de production. Le callback
|
||||||
|
échoue alors au dispatch et la grille ne reçoit aucune ligne.
|
||||||
|
|
||||||
|
Ce test tourne donc en `debug=True` (dev-renderer) pour exercer cette
|
||||||
|
validation, sinon le bug passe inaperçu. Le correctif : une seule sortie MATCH,
|
||||||
|
les stores totaux alimentés via dash.set_props.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import src.app # noqa: F401 # instancie l'app → register_page()
|
||||||
|
from src.db import query_marches
|
||||||
|
|
||||||
|
|
||||||
|
def _an_acheteur_id():
|
||||||
|
return query_marches("TRUE", (), columns=["acheteur_id"])["acheteur_id"][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_entity_grid_datasource_returns_rows_in_debug(dash_duo):
|
||||||
|
from src.app import app
|
||||||
|
|
||||||
|
ach = _an_acheteur_id()
|
||||||
|
|
||||||
|
# debug=True → dev-renderer, qui valide la cohérence MATCH des Outputs.
|
||||||
|
dash_duo.start_server(
|
||||||
|
app, debug=True, use_reloader=False, dev_tools_hot_reload=False
|
||||||
|
)
|
||||||
|
dash_duo.wait_for_text_to_equal(".logo > h1", "colibre", timeout=6)
|
||||||
|
dash_duo.wait_for_page(dash_duo.server_url + f"/acheteurs/{ach}")
|
||||||
|
|
||||||
|
# La grille charge des lignes → _get_rows a répondu (pas d'erreur de dispatch).
|
||||||
|
dash_duo.wait_for_element(
|
||||||
|
"#acheteur-grid-container .ag-center-cols-container .ag-row", timeout=15
|
||||||
|
)
|
||||||
|
# nb_rows alimenté via les stores totaux mis à jour par set_props côté serveur.
|
||||||
|
dash_duo.wait_for_contains_text("#acheteur_nb_rows", "marchés", timeout=8)
|
||||||
|
assert "0 marchés" not in dash_duo.find_element("#acheteur_nb_rows").text
|
||||||
|
|
||||||
|
# Aucune erreur « Mismatched MATCH wildcards » dans la console du dev-renderer.
|
||||||
|
match_errors = [
|
||||||
|
entry
|
||||||
|
for entry in (dash_duo.get_logs() or [])
|
||||||
|
if "MATCH" in str(entry.get("message", ""))
|
||||||
|
]
|
||||||
|
assert not match_errors, f"Erreur dev-renderer MATCH : {match_errors}"
|
||||||
Reference in New Issue
Block a user