mckinsey / vizro

Vizro is a toolkit for creating modular data visualization applications.
https://vizro.readthedocs.io/en/stable/
Apache License 2.0
2.65k stars 136 forks source link

Custom action on Page loading #643

Closed BalaNagendraReddy closed 1 month ago

BalaNagendraReddy commented 1 month ago

Question

Hi Team,

I'm currently working on a multipage app using the vizro library and I need to implement a feature that extracts file names from MongoDB when a specific page is loaded. The goal is to dynamically load the page with all the extracted file names based on the output of a function.

Objective: I need to ensure that when the page is opened, the extract_files_from_db function is called, and the page content is dynamically updated with the file names extracted from MongoDB.

Challenges: I am not sure how to integrate the function with the page loading process or how to update the page content dynamically based on the result. Could you please provide guidance on how to:

Trigger the extract_files_from_db function when the page loads. Update the page content with the extracted file names.

Any assistance or examples on how to achieve this with the vizro library would be greatly appreciated.

Code/Examples

import vizro.models as vm
from time import sleep

def extract_files_from_db():
    """Custom action."""
    print("Extracting files from database...")
    sleep(5)
    print("Files extracted.")

my_page = vm.Page(
    title="Mytitle",
    path="path1/path12",
    components=[
        # Action to be triggered when the page is loaded
        vm.Card(text="My text here"),
    ],
)

Package Version: vizro==0.1.18

Which package?

vizro

Code of Conduct

petar-qb commented 1 month ago

Hi @BalaNagendraReddy and thanks for the great question. 🚀

Triggering custom actions when the page is loaded, natively through the Vizro configuration, is currently unavailable feature and will be available once this PR become merged. As it doesn't look like this PR is going to be merged soon, I'm posting two Vizro examples on how this already could be achieved (in less native Vizro way).

The first example solves the problem by utilising dash callbacks.

"""Dev app to try things out."""
from time import sleep
from dash import callback, Input, Output

from vizro import Vizro
import vizro.models as vm

MONGO_DB_FILES = ["file_1.csv", "file_2.csv", "file_3.csv", "file_4.csv"]

def extract_mongo_db_files():
    print("Extracting files from database...")
    sleep(2)
    print(f"Files extracted:\n{MONGO_DB_FILES}")
    return MONGO_DB_FILES

home_page = vm.Page(
    title="Home Page",
    components=[vm.Card(text="Go to the 'Mongo Files Page' to see the extracted files.")],
)

mongo_files_page = vm.Page(
    title="Mongo Files Page",
    path="path1/path2",
    id="mongo_files_page_id",
    components=[
        vm.Card(
            id="my_card_id",
            text=""
        ),
    ],
)

@callback(
    Output("my_card_id", "children"),
    Input(f'on_page_load_action_trigger_{mongo_files_page.id}', "data"),
)
def update_my_card_id(data):
    files = extract_mongo_db_files()
    return f"Extracted files: {files}"

dashboard = vm.Dashboard(pages=[home_page, mongo_files_page])

if __name__ == "__main__":
    Vizro().build(dashboard).run()

The second example solves the same problem in fully native Vizro way.

Useful references:

"""Dev app to try things out."""
import pandas as pd
from time import sleep
from dash import dcc
import dash_bootstrap_components as dbc

from vizro import Vizro
import vizro.models as vm
import vizro.plotly.express as px
from vizro.managers import data_manager
from vizro.models.types import capture
from vizro.tables import dash_ag_grid

MONGO_DB_FILES = ["file_1.csv", "file_2.csv", "file_3.csv", "file_4.csv"]

# TODO - DOCS: More about how to use Vizro cache to speed up your app -> https://vizro.readthedocs.io/en/stable/pages/user-guides/data/#configure-cache
def extract_mongo_db_files():
    print("Extracting files from database...")
    sleep(1)
    print(f"Files extracted:\n{MONGO_DB_FILES}")
    return pd.DataFrame({"files": MONGO_DB_FILES})

data_manager["my_data"] = extract_mongo_db_files

# TODO - DOCS: More about custom figures -> https://vizro.readthedocs.io/en/stable/pages/user-guides/custom-figures/
@capture("figure")
def my_responsive_card(data_frame, id, column):
    return dbc.Card(
        id=id,
        children=f"Files extracted:\n{list(data_frame[column].values)}",
    )

@capture("figure")
def my_responsive_dropdown(data_frame, id, column):
    options = list(data_frame[column].values)

    return dcc.Dropdown(
        id=id,
        options=options,
        value=options[0] if options else None,
        multi=False,
    )

home_page = vm.Page(
    title="Home Page",
    components=[vm.Card(text="Go to the 'Mongo Files Page' to see the extracted files.")],
)

mongo_files_page = vm.Page(
    title="Mongo Files Page",
    path="path1/path2",
    id="mongo_files_page_id",
    components=[
        vm.Figure(
            figure=my_responsive_card(
                data_frame="my_data",
                id="my_card_id",
                column="files",
            )
        ),
        vm.Figure(
            figure=my_responsive_dropdown(
                data_frame="my_data",
                id="my_dropdown_id",
                column="files",
            )
        ),
        vm.AgGrid(
            id="my_ag_grid_id",
            figure=dash_ag_grid(data_frame="my_data")
        ),
    ],
)

dashboard = vm.Dashboard(pages=[home_page, mongo_files_page])

if __name__ == "__main__":
    Vizro().build(dashboard).run()
BalaNagendraReddy commented 1 month ago

Hi @petar-qb,

Thank you for your prompt response.

The function is working, but I noticed that with the second method, the function executes automatically even before navigating to the Mongo Files Page. This seems to create unnecessary load on the website. Ideally, the function should execute only when the user clicks on the Mongo Files Page.

Here is the output I observed:

Extracting files from database...
Files extracted:
['file_1.csv', 'file_2.csv', 'file_3.csv', 'file_4.csv']
Extracting files from database...
Files extracted:
['file_1.csv', 'file_2.csv', 'file_3.csv', 'file_4.csv']
Extracting files from database...
Files extracted:
['file_1.csv', 'file_2.csv', 'file_3.csv', 'file_4.csv']
Dash is running on http://127.0.0.1:8050/

INFO:dash.dash:Dash is running on http://127.0.0.1:8050/

 * Serving Flask app 'vh2_custom_action_page_loading'
 * Debug mode: off
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:8050
INFO:werkzeug:Press CTRL+C to quit
petar-qb commented 1 month ago

Hi @BalaNagendraReddy, this behaviour will be fixed when we merge this https://github.com/mckinsey/vizro/pull/644 and publish the release. We expect that to happen in the middle of next week.

Let's keep this issue opened until we merge mentioned PR 😃.

BalaNagendraReddy commented 1 month ago

Hi @petar-qb ,

Thanks for the clarification.

petar-qb commented 1 month ago

Hey @BalaNagendraReddy 👋

The unnecessary website overloading bug has been fixed and you should no longer encounter this behaviour with the new vizro==0.1.21 released this morning.