Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loading spinner on login #172

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions app/callbacks/data_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
Output("user_credentials", "data"),
Output("user_headers", "data"),
Output("form_feedback_area", "children"),
Output("username_input", "style"),
Output("password_input", "style"),
Output("send_form_button", "style"),
Output("form_feedback_area", "style"),
Output("loading_spinner", "style"),
],
Input("send_form_button", "n_clicks"),
[
Expand All @@ -52,12 +57,28 @@ def login_callback(n_clicks, username, password, user_headers):
This function is triggered when the login button is clicked. It verifies the provided username and password,
attempts to authenticate the user via the API, and updates the user credentials and headers.
If authentication fails or credentials are missing, it provides appropriate feedback.
After login succeeds and while data required to boot the dashboard is being fetched from the API,
the login form is hidden and a spinner is displayed.

Returns:
dash.dependencies.Output: Updated user credentials and headers, and form feedback.
dash.dependencies.Output: Updated user credentials and headers, and form feedback + styles to hide/show login elements and loading spinners.
"""
input_style_unchanged = {"width": "250px"}
empty_style_unchanged = {"": ""}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cleaner to user {} instead, but then mypy complains because it wants to know the type of empty_style_unchanged and it seems python does not infer types for empty dictionaries => using {"": ""} seems simpler than bringing in a whole typing library to handle that error only (from typing import Dict, Optional ... empty_style: Dict[str, Optional[str]] = {}).

hide_element_style = {"display": "none"}
show_spinner_style = {"transform": "scale(4)"}

if user_headers is not None:
return dash.no_update, dash.no_update, dash.no_update
return (
dash.no_update,
dash.no_update,
dash.no_update,
input_style_unchanged,
input_style_unchanged,
empty_style_unchanged,
empty_style_unchanged,
hide_element_style,
)

if n_clicks:
# We instantiate the form feedback output
Expand All @@ -70,7 +91,16 @@ def login_callback(n_clicks, username, password, user_headers):
form_feedback.append(html.P("Il semble qu'il manque votre nom d'utilisateur et/ou votre mot de passe."))

# The login modal remains open; other outputs are updated with arbitrary values
return dash.no_update, dash.no_update, form_feedback
return (
dash.no_update,
dash.no_update,
form_feedback,
input_style_unchanged,
input_style_unchanged,
empty_style_unchanged,
empty_style_unchanged,
hide_element_style,
)
else:
# This is the route of the API that we are going to use for the credential check
try:
Expand All @@ -80,12 +110,26 @@ def login_callback(n_clicks, username, password, user_headers):
{"username": username, "password": password},
client.headers,
dash.no_update,
hide_element_style,
hide_element_style,
hide_element_style,
hide_element_style,
show_spinner_style,
)
except Exception:
# This if statement is verified if credentials are invalid
form_feedback.append(html.P("Nom d'utilisateur et/ou mot de passe erroné."))

return dash.no_update, dash.no_update, form_feedback
return (
dash.no_update,
dash.no_update,
form_feedback,
input_style_unchanged,
input_style_unchanged,
empty_style_unchanged,
empty_style_unchanged,
hide_element_style,
)
Copy link
Collaborator Author

@vanderlindenma vanderlindenma Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MateoLostanlen @fe51 @RonanMorgan Another example where Dash does not scale well. In a "sane" 😉 web framework:

  1. You shouldn't have to repeatedly include placeholders for things you don't want to update: Just pass a map/dict of what you do want to change and the framework should know the rest should be left untouched.
  2. You shouldn't have to go back and forth between the parameters in the head of your function and the return to match the order of the declared outputs and with the order of the returns => just return a map each time.

Maybe there are workarounds to fix that in Dash but this kind of update behavior should really come out of the box.


raise PreventUpdate

Expand Down
5 changes: 5 additions & 0 deletions app/pages/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def login_layout():
html.Div(style={"height": "15px"}), # Spacing
# Feedback message area
html.Div(id="form_feedback_area"),
html.Div(
dbc.Spinner(),
id="loading_spinner",
style={"display": "none"},
),
],
),
]
Expand Down
Loading