fix(tableau): griser/désactiver les boutons vues pour les non-abonnés

La barre des vues sauvegardées restait masquée pour les non-abonnés via un
style inline display:none — inopérant car .d-inline-flex est
display:inline-flex !important, la barre était donc toujours visible.

La barre reste désormais visible pour tous ; « Sauvegarder la vue » et
« Mes vues » sont grisés et désactivés (disabled) pour les non-abonnés. Le
gating serveur de save_view (prepare_view_to_save) reste inchangé.
This commit is contained in:
Colin Maudry
2026-07-13 19:23:03 +02:00
parent 3034e911e7
commit f0a891b73f
4 changed files with 48 additions and 10 deletions
+13 -5
View File
@@ -330,7 +330,6 @@ layout = [
), ),
html.Div( html.Div(
id="saved-views-bar", id="saved-views-bar",
style={"display": "none"},
className="d-inline-flex align-items-center gap-2", className="d-inline-flex align-items-center gap-2",
children=[ children=[
dbc.Button( dbc.Button(
@@ -338,7 +337,10 @@ layout = [
id="btn-save-view", id="btn-save-view",
color="secondary", color="secondary",
size="sm", size="sm",
title="Enregistrer les filtres, tris et colonnes actuels sous un nom", # Grisé/désactivé pour les non-abonnés (le callback
# toggle_saved_views_controls affine au chargement).
disabled=True,
title="Enregistrer les filtres, tris et colonnes actuels sous un nom (abonnés)",
), ),
dbc.DropdownMenu( dbc.DropdownMenu(
id="saved-views-menu", id="saved-views-menu",
@@ -346,6 +348,7 @@ layout = [
color="secondary", color="secondary",
size="sm", size="sm",
children=[], children=[],
disabled=True,
className="d-inline-block", className="d-inline-block",
), ),
], ],
@@ -627,11 +630,16 @@ def reset_view(n_clicks, column_state):
@callback( @callback(
Output("saved-views-bar", "style"), Output("btn-save-view", "disabled"),
Output("saved-views-menu", "disabled"),
Input("tableau_url", "pathname"), Input("tableau_url", "pathname"),
) )
def toggle_saved_views_bar(_pathname): def toggle_saved_views_controls(_pathname):
return saved_views_ui.bar_style(current_user_has_subscription()) # La barre reste visible pour tous ; « Sauvegarder la vue » et « Mes vues »
# sont grisés et désactivés pour les non-abonnés (le gating serveur de
# save_view reste en place via prepare_view_to_save).
disabled = saved_views_ui.controls_disabled(current_user_has_subscription())
return disabled, disabled
def resolve_vue_from_url(search: str) -> dict | None: def resolve_vue_from_url(search: str) -> dict | None:
+4 -2
View File
@@ -7,8 +7,10 @@ from unidecode import unidecode
from src.utils import DOMAIN_NAME from src.utils import DOMAIN_NAME
def bar_style(has_subscription: bool) -> dict: def controls_disabled(has_subscription: bool) -> bool:
return {} if has_subscription else {"display": "none"} """Boutons « Sauvegarder la vue » / « Mes vues » : actifs pour les abonnés,
grisés et désactivés sinon. La barre elle-même reste visible pour tous."""
return not has_subscription
def clean_view_name(name: str | None) -> str: def clean_view_name(name: str | None) -> str:
@@ -0,0 +1,28 @@
"""Régression : pour un visiteur non abonné, la barre des vues sauvegardées
reste visible, mais « Sauvegarder la vue » et « Mes vues » sont grisés et
désactivés (le gating serveur de save_view reste par ailleurs en place).
"""
import src.app # noqa: F401 # instancie l'app → register_page()
def test_controls_disabled_for_anonymous_visitor(dash_duo):
from src.app import app
dash_duo.start_server(app)
dash_duo.wait_for_text_to_equal(".logo > h1", "colibre", timeout=6)
dash_duo.wait_for_page(dash_duo.server_url + "/tableau")
# La barre est visible pour tous.
bar = dash_duo.wait_for_element("#saved-views-bar", timeout=10)
assert bar.value_of_css_property("display") != "none"
# « Sauvegarder la vue » est désactivé (donc grisé) pour un anonyme.
save_btn = dash_duo.find_element("#btn-save-view")
assert save_btn.get_attribute("disabled") is not None
# « Mes vues » (le bouton toggle du DropdownMenu) est désactivé aussi.
menu_toggle = dash_duo.find_element("#saved-views-menu .dropdown-toggle")
disabled_attr = menu_toggle.get_attribute("disabled")
has_disabled_class = "disabled" in (menu_toggle.get_attribute("class") or "")
assert disabled_attr is not None or has_disabled_class
+3 -3
View File
@@ -9,9 +9,9 @@ def _view(view_id, name, query, token="abc123"):
return _Row(id=view_id, name=name, query=query, token=token) return _Row(id=view_id, name=name, query=query, token=token)
def test_bar_style_hidden_for_non_subscriber(): def test_controls_disabled_for_non_subscriber():
assert ui.bar_style(False) == {"display": "none"} assert ui.controls_disabled(False) is True
assert ui.bar_style(True) == {} assert ui.controls_disabled(True) is False
def test_clean_view_name_strips_and_empties(): def test_clean_view_name_strips_and_empties():