diff --git a/vizro-core/examples/scratch_dev/app.py b/vizro-core/examples/scratch_dev/app.py index bcacbb591..a45c914f9 100644 --- a/vizro-core/examples/scratch_dev/app.py +++ b/vizro-core/examples/scratch_dev/app.py @@ -5,6 +5,7 @@ from vizro import Vizro from vizro.models.types import capture from vizro.managers import data_manager +from vizro.actions import update_figures df = px.data.iris() @@ -42,7 +43,7 @@ page_1_1 = vm.Page( id="page_1_1", - title="Apply controls on button click", + title="Apply the filter on the parameter change", components=[ vm.Graph( id="p11_graph", @@ -66,7 +67,44 @@ ) -dashboard = vm.Dashboard(pages=[page_0_1, page_1_1]) +# ====== **NEW** Apply controls on button click ====== + +vm.Page.add_type("controls", vm.Button) + +page_2_1 = vm.Page( + id="page_2_1", + title="Apply controls on button click", + components=[ + vm.Graph( + id="p21_graph", + figure=px.scatter( + df, x="sepal_width", y="sepal_length", color="species", color_discrete_map=SPECIES_COLORS + ), + ), + vm.Text(id="p21_text", text="Placeholder"), + ], + controls=[ + vm.Filter( + column="species", + targets=["p21_graph"], + selector=vm.RadioItems( + title="Filter that does NOT auto-apply, but is taken into account when its target Graph is updated.", + actions=vm.Action(function=capture("action")(lambda _trigger: _trigger)(), outputs="p21_text"), + ), + ), + vm.Parameter( + targets=["p21_graph.x"], + selector=vm.RadioItems( + title="Parameter that does NOT auto-apply, but is taken into account when its target Graph is updated.", + options=["sepal_width", "sepal_length"], + actions=vm.Action(function=capture("action")(lambda _trigger: _trigger)(), outputs="p21_text"), + ), + ), + vm.Button(text="Apply controls", actions=update_figures()), + ], +) + +dashboard = vm.Dashboard(pages=[page_0_1, page_1_1, page_2_1]) if __name__ == "__main__": Vizro().build(dashboard).run() diff --git a/vizro-core/src/vizro/actions/__init__.py b/vizro-core/src/vizro/actions/__init__.py index f32507eb8..31579977a 100644 --- a/vizro-core/src/vizro/actions/__init__.py +++ b/vizro-core/src/vizro/actions/__init__.py @@ -8,5 +8,13 @@ from vizro.actions._filter_interaction import filter_interaction from vizro.actions._notifications import show_notification, update_notification from vizro.actions._set_control import set_control +from vizro.actions._update_figures import update_figures -__all__ = ["export_data", "filter_interaction", "set_control", "show_notification", "update_notification"] +__all__ = [ + "export_data", + "filter_interaction", + "set_control", + "show_notification", + "update_figures", + "update_notification", +] diff --git a/vizro-core/src/vizro/actions/_filter_action.py b/vizro-core/src/vizro/actions/_filter_action.py deleted file mode 100644 index aa57d16d4..000000000 --- a/vizro-core/src/vizro/actions/_filter_action.py +++ /dev/null @@ -1,41 +0,0 @@ -from collections.abc import Callable -from typing import Any, Literal - -import pandas as pd -from dash import ctx -from pydantic import Field - -from vizro.actions._abstract_action import _AbstractAction -from vizro.actions._actions_utils import _get_modified_page_figures -from vizro.models.types import ModelID, _Controls - - -class _filter(_AbstractAction): - type: Literal["_filter"] = "_filter" - - column: str = Field(description="Column to filter on.") - filter_function: Callable[[pd.Series, Any], pd.Series] = Field( - description="Function to apply to column to perform filtering" - ) - targets: list[ModelID] = Field(description="Target component IDs.") - - def function(self, _controls: _Controls) -> dict[ModelID, Any]: - """Applies _controls to charts on page once filter is applied. - - Returns: - Dict mapping target chart ids to modified figures e.g. {"my_scatter": Figure(...)}. - """ - # This is identical to _on_page_load. - # TODO-AV2 A 1: _controls is not currently used but instead taken out of the Dash context. This - # will change in future once the structure of _controls has been worked out and we know how to pass ids through. - # See https://github.com/mckinsey/vizro/pull/880 - return _get_modified_page_figures( - ctds_filter=ctx.args_grouping["external"]["_controls"]["filters"], - ctds_parameter=ctx.args_grouping["external"]["_controls"]["parameters"], - ctds_filter_interaction=ctx.args_grouping["external"]["_controls"]["filter_interaction"], - targets=self.targets, - ) - - @property - def outputs(self): # type: ignore[override] - return {target: target for target in self.targets} diff --git a/vizro-core/src/vizro/actions/_on_page_load.py b/vizro-core/src/vizro/actions/_on_page_load.py deleted file mode 100644 index 7f6ea4dda..000000000 --- a/vizro-core/src/vizro/actions/_on_page_load.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Any, Literal - -from dash import ctx -from pydantic import Field - -import vizro.models as vm -from vizro.actions._abstract_action import _AbstractAction -from vizro.actions._actions_utils import _get_modified_page_figures -from vizro.managers import model_manager -from vizro.models.types import ModelID, _Controls - - -# TODO-AV2 A 3: rename _on_page_load if desired and make public. Similarly for other built-in actions. -# Think about how to handle filter_function that is not JSON serialisable. -class _on_page_load(_AbstractAction): - type: Literal["_on_page_load"] = "_on_page_load" - - targets: list[ModelID] = Field(description="Target component IDs.") - - def function(self, _controls: _Controls) -> dict[ModelID, Any]: - """Applies controls to charts on page once the page is opened (or refreshed). - - Returns: - Dict mapping target chart ids to modified figures e.g. {"my_scatter": Figure(...)}. - - """ - # TODO-AV2 A 1: _controls is not currently used but instead taken out of the Dash context. This - # will change in future once the structure of _controls has been worked out and we know how to pass ids through. - # See https://github.com/mckinsey/vizro/pull/880 - return _get_modified_page_figures( - ctds_filter=ctx.args_grouping["external"]["_controls"]["filters"], - ctds_parameter=ctx.args_grouping["external"]["_controls"]["parameters"], - ctds_filter_interaction=ctx.args_grouping["external"]["_controls"]["filter_interaction"], - targets=self.targets, - ) - - @property - def outputs(self): # type: ignore[override] - # Special handling for vm.Filter as otherwise the filter's default action output would alter the selector value. - return { - target: f"{target}.selector" if isinstance(model_manager[target], vm.Filter) else target - for target in self.targets - } diff --git a/vizro-core/src/vizro/actions/_parameter_action.py b/vizro-core/src/vizro/actions/_parameter_action.py deleted file mode 100644 index f7f5015ef..000000000 --- a/vizro-core/src/vizro/actions/_parameter_action.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Literal - -from dash import ctx -from pydantic import Field - -import vizro.models as vm -from vizro.actions._abstract_action import _AbstractAction -from vizro.actions._actions_utils import _get_modified_page_figures -from vizro.managers import model_manager -from vizro.models.types import ModelID, _Controls - - -class _parameter(_AbstractAction): - type: Literal["_parameter"] = "_parameter" - - targets: list[str] = Field(description="Targets in the form `.`.") - - @property - def _target_ids(self) -> list[ModelID]: - # This cannot be implemented as PrivateAttr(default_factory=lambda data: ...) because, unlike Field, - # PrivateAttr does not yet support an argument to the default_factory function. See: - # https://github.com/pydantic/pydantic/issues/10992 - # Targets without "." are implicitly added by the `Parameter._set_actions` method - # to handle cases where a dynamic data parameter affects a filter or its targets. - return [target.partition(".")[0] if "." in target else target for target in self.targets] - - def function(self, _controls: _Controls) -> dict[ModelID, Any]: - """Applies _controls to charts on page once the page is opened (or refreshed). - - Returns: - Dict mapping target chart ids to modified figures e.g. {"my_scatter": Figure(...)}. - - """ - # This is identical to _on_page_load but with self._target_ids rather than self.targets. - # TODO-AV2 A 1: _controls is not currently used but instead taken out of the Dash context. This - # will change in future once the structure of _controls has been worked out and we know how to pass ids through. - # See https://github.com/mckinsey/vizro/pull/880 - return _get_modified_page_figures( - ctds_filter=ctx.args_grouping["external"]["_controls"]["filters"], - ctds_parameter=ctx.args_grouping["external"]["_controls"]["parameters"], - ctds_filter_interaction=ctx.args_grouping["external"]["_controls"]["filter_interaction"], - targets=self._target_ids, - ) - - @property - def outputs(self): # type: ignore[override] - # Special handling for vm.Filter as otherwise the filter's default action output would alter the selector value. - return { - target: f"{target}.selector" if isinstance(model_manager[target], vm.Filter) else target - for target in self._target_ids - } diff --git a/vizro-core/src/vizro/actions/_update_figures.py b/vizro-core/src/vizro/actions/_update_figures.py new file mode 100644 index 000000000..c465ea7ca --- /dev/null +++ b/vizro-core/src/vizro/actions/_update_figures.py @@ -0,0 +1,86 @@ +from collections.abc import Iterable +from typing import Any, Literal, cast + +from dash import ctx +from pydantic import Field + +import vizro.models as vm +from vizro.actions._abstract_action import _AbstractAction +from vizro.actions._actions_utils import _get_modified_page_figures +from vizro.managers import model_manager +from vizro.managers._model_manager import FIGURE_MODELS +from vizro.models._models_utils import _log_call +from vizro.models.types import FigureType, ModelID, _Controls + + +# TODO AM QQ: Should we rename "update_figures"? It updates controls too. +# Consider combining words update/recreate/refresh with figures/models/controls. +class update_figures(_AbstractAction): + """Exports data of target charts, tables and figures. + + Args: + targets (list[ModelID]): List of target component ids that will be rebuilt. If none are given then target + all components on the page. + + Example: + ```python + import vizro.actions as va + + vm.Button( + text="Recreate first graph", + actions=va.update_figures(targets=["graph_id_1"]), + ) + ``` + """ + + type: Literal["update_figures"] = "update_figures" + + targets: list[ModelID] = Field(default=[], description="Target component IDs.") + + @_log_call + def pre_build(self): + # Set targets to all figures on the page if not already set. + + # TODO AM-PP OQ: This implementation enables users to manually specify filter targets outside the container. + # TODO-AV2 A 4: work out where this duplicated get_all_targets_on_page logic should live. + + root_model = model_manager._get_model_page(self) + + figure_ids_on_page = [ + model.id for model in cast(Iterable[FigureType], model_manager._get_models(FIGURE_MODELS, root_model)) + ] + dynamic_filter_ids_on_page = [ + filter.id + for filter in cast(Iterable[vm.Filter], model_manager._get_models(vm.Filter, root_model=root_model)) + if filter._dynamic + ] + + if not self.targets: + self.targets = figure_ids_on_page + elif invalid_targets := set(self.targets) - set(figure_ids_on_page + dynamic_filter_ids_on_page): + raise ValueError(f"targets {invalid_targets} are not valid figures on the page.") + + def function(self, _controls: _Controls) -> dict[ModelID, Any]: + """Recreates targeted charts by applying controls. + + Returns: + Dict mapping target chart ids to modified figures e.g. {"my_scatter": Figure(...)}. + + """ + # TODO-AV2 A 1: _controls is not currently used but instead taken out of the Dash context. This + # will change in future once the structure of _controls has been worked out and we know how to pass ids through. + # See https://github.com/mckinsey/vizro/pull/880 + return _get_modified_page_figures( + ctds_filter=ctx.args_grouping["external"]["_controls"]["filters"], + ctds_parameter=ctx.args_grouping["external"]["_controls"]["parameters"], + ctds_filter_interaction=ctx.args_grouping["external"]["_controls"]["filter_interaction"], + targets=self.targets, + ) + + @property + def outputs(self): # type: ignore[override] + # Special handling for controls as otherwise the control's default action output would alter the selector value. + return { + target: f"{target}.selector" if isinstance(model_manager[target], (vm.Filter, vm.Parameter)) else target + for target in self.targets + } diff --git a/vizro-core/src/vizro/models/__init__.py b/vizro-core/src/vizro/models/__init__.py index 7871b3cb0..81cd5ee25 100644 --- a/vizro-core/src/vizro/models/__init__.py +++ b/vizro-core/src/vizro/models/__init__.py @@ -53,10 +53,15 @@ # To resolve ForwardRefs we need to import a few more things that are not part of the vizro.models namespace. # We rebuild all the models even if it's not strictly necessary so that if pydantic changes how model_rebuild works # we won't end up with unresolved references. -from vizro.actions import export_data, filter_interaction, set_control, show_notification, update_notification -from vizro.actions._filter_action import _filter -from vizro.actions._on_page_load import _on_page_load -from vizro.actions._parameter_action import _parameter + +from vizro.actions import ( + export_data, + filter_interaction, + set_control, + show_notification, + update_figures, + update_notification, +) from ._components.form._text_area import TextArea from ._components.form._user_input import UserInput diff --git a/vizro-core/src/vizro/models/_controls/filter.py b/vizro-core/src/vizro/models/_controls/filter.py index c42d0bcce..c25c62285 100644 --- a/vizro-core/src/vizro/models/_controls/filter.py +++ b/vizro-core/src/vizro/models/_controls/filter.py @@ -10,7 +10,7 @@ from pydantic import Field, PrivateAttr, model_validator from vizro._constants import FILTER_ACTION_PREFIX -from vizro.actions._filter_action import _filter +from vizro.actions import update_figures from vizro.managers import data_manager, model_manager from vizro.managers._data_manager import DataSourceName, _DynamicData from vizro.managers._model_manager import FIGURE_MODELS @@ -285,15 +285,9 @@ def pre_build(self): else: self._filter_function = _filter_isin + # TODO AM-PP: If [] or None is set make that the actions are not overwritten. Could be tricky, but doable. if not self.selector.actions: - self.selector.actions = [ - _filter( - id=f"{FILTER_ACTION_PREFIX}_{self.id}", - column=self.column, - filter_function=self._filter_function, - targets=self.targets, - ), - ] + self.selector.actions = update_figures(id=f"{FILTER_ACTION_PREFIX}_{self.id}", targets=self.targets) # A set of properties unique to selector (inner object) that are not present in html.Div (outer build wrapper). # Creates _action_outputs and _action_inputs for forwarding properties to the underlying selector. diff --git a/vizro-core/src/vizro/models/_controls/parameter.py b/vizro-core/src/vizro/models/_controls/parameter.py index f426845f4..0b3511b51 100644 --- a/vizro-core/src/vizro/models/_controls/parameter.py +++ b/vizro-core/src/vizro/models/_controls/parameter.py @@ -5,7 +5,7 @@ from pydantic import AfterValidator, Field, PrivateAttr, model_validator from vizro._constants import PARAMETER_ACTION_PREFIX -from vizro.actions._parameter_action import _parameter +from vizro.actions import update_figures from vizro.managers import model_manager from vizro.models import VizroBaseModel from vizro.models._controls._controls_utils import ( @@ -183,9 +183,10 @@ def pre_build(self): # Extending `self.targets` with `filter_targets` instead of redefining it to avoid triggering the # pydantic validator like `check_dot_notation` on the `self.targets` again. - # We do the update to ensure that `self.targets` is consistent with the targets passed to `_parameter`. + # We do the update to ensure that `self.targets` is consistent with the target ids passed to `_parameter`. self.targets.extend(list(filter_targets)) - self.selector.actions = [_parameter(id=f"{PARAMETER_ACTION_PREFIX}_{self.id}", targets=self.targets)] + targets_ids = [target.partition(".")[0] for target in self.targets] + self.selector.actions = update_figures(id=f"{PARAMETER_ACTION_PREFIX}_{self.id}", targets=targets_ids) @_log_call def build(self): diff --git a/vizro-core/src/vizro/models/_models_utils.py b/vizro-core/src/vizro/models/_models_utils.py index 6e3cde477..f3398bdbb 100755 --- a/vizro-core/src/vizro/models/_models_utils.py +++ b/vizro-core/src/vizro/models/_models_utils.py @@ -7,6 +7,7 @@ from dash.development.base_component import Component from pydantic import ValidationInfo +from vizro._constants import ON_PAGE_LOAD_ACTION_PREFIX from vizro.managers import model_manager from vizro.models.types import CapturedCallable, _SupportsCapturedCallable @@ -127,7 +128,6 @@ def make_actions_chain(self): Table and AgGrid. Even though it's a model validator it is also run on assignment e.g. selector.actions = ... """ from vizro.actions import export_data, filter_interaction - from vizro.actions._on_page_load import _on_page_load converted_actions = [] @@ -164,7 +164,8 @@ def make_actions_chain(self): action._first_in_chain_trigger = model_action_trigger # The actions chain guard should be called only for on page load. - action._prevent_initial_call_of_guard = not isinstance(action, _on_page_load) + # TODO AM-PP OQ: Revisit this and make the implementation better if possible. + action._prevent_initial_call_of_guard = not action.id.startswith(ON_PAGE_LOAD_ACTION_PREFIX) # Temporary workaround for lookups in filter_interaction and set_control. This should become unnecessary once # the model manager supports `parent_model` access for all Vizro models. diff --git a/vizro-core/src/vizro/models/_page.py b/vizro-core/src/vizro/models/_page.py index ec10f0316..721930526 100644 --- a/vizro-core/src/vizro/models/_page.py +++ b/vizro-core/src/vizro/models/_page.py @@ -16,7 +16,7 @@ from typing_extensions import TypedDict from vizro._constants import ON_PAGE_LOAD_ACTION_PREFIX -from vizro.actions._on_page_load import _on_page_load +from vizro.actions import update_figures from vizro.managers import model_manager from vizro.managers._model_manager import FIGURE_MODELS from vizro.models import Filter, Parameter, Tooltip, VizroBaseModel @@ -150,7 +150,15 @@ def pre_build(self): targets = figure_targets + filter_targets if targets: - self.actions = [_on_page_load(id=f"{ON_PAGE_LOAD_ACTION_PREFIX}_{self.id}", targets=targets)] + # TODO AM-PP OQ: Why mypy raises an issue here if a single update_figures object is assigned? + # This doesn't happen in other places where we assign a single action to self.actions. + self.actions = [update_figures(id=f"{ON_PAGE_LOAD_ACTION_PREFIX}_{self.id}", targets=targets)] + # TODO AM-MS-PP OQ: Here's an interesting situation: Implementation like this below relies on that the + # update_figures.pre_build would calculate its targets. However, that's not the case as this update_figure + # object is added to the model_manager after Vizro._pre_build creates the set(model_managers). + # So, with the current implementation, objects that are created as a result of other models' pre_build + # methods will never have their pre_build called. Is there any specific reason for this design decision? + # self.actions = update_figures(id=f"{ON_PAGE_LOAD_ACTION_PREFIX}_{self.id}") # Convert generator to list as it's going to be iterated multiple times. # Use "root_model=self" as controls can be defined inside a "Container.controls" under the "Page.components". diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index c37a377df..beb30333b 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -25,7 +25,6 @@ ValidationError, ValidationInfo, ) -from pydantic.json_schema import SkipJsonSchema from typing_extensions import TypedDict from vizro.charts._charts_utils import _DashboardReadyFigure @@ -704,10 +703,8 @@ class _OptionsDictType(TypedDict): | Annotated["filter_interaction", Tag("filter_interaction")] | Annotated["set_control", Tag("set_control")] | Annotated["show_notification", Tag("show_notification")] - | Annotated["update_notification", Tag("update_notification")] - | SkipJsonSchema[Annotated["_filter", Tag("_filter")]] - | SkipJsonSchema[Annotated["_parameter", Tag("_parameter")]] - | SkipJsonSchema[Annotated["_on_page_load", Tag("_on_page_load")]], + | Annotated["update_figures", Tag("update_figures")] + | Annotated["update_notification", Tag("update_notification")], Field(discriminator=Discriminator(_get_action_discriminator), description="Action."), ] """Discriminated union. Type of action: [`Action`][vizro.models.Action], [`export_data`][vizro.models.export_data] or [ diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py index a08d3c09f..9a5e9e2c7 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py @@ -10,7 +10,7 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.actions._filter_action import _filter +from vizro.actions._update_figures import update_figures from vizro.managers import data_manager, model_manager from vizro.models._controls.filter import Filter, _filter_between, _filter_isin @@ -923,28 +923,60 @@ def test_categorical_options_specific(self, selector, managers_one_page_two_grap assert filter.selector.options == ["Africa", "Europe"] @pytest.mark.parametrize( - "filtered_column, selector, filter_function", + "test_column, test_selector, filter_function", [ - ("lifeExp", None, _filter_between), - ("country", None, _filter_isin), - ("year", None, _filter_between), + ("continent", vm.RadioItems(), _filter_isin), + ("continent", vm.Dropdown(multi=False), _filter_isin), + ("continent", vm.Dropdown(multi=True), _filter_isin), + ("continent", vm.Checklist(), _filter_isin), + ("pop", vm.Slider(), _filter_isin), + ("pop", vm.RangeSlider(), _filter_between), ("year", vm.DatePicker(range=False), _filter_isin), - ("is_europe", None, _filter_isin), + ("year", vm.DatePicker(range=True), _filter_between), + ("is_europe", vm.Switch(), _filter_isin), ], ) - def test_set_actions(self, filtered_column, selector, filter_function, managers_one_page_two_graphs): - filter = vm.Filter(column=filtered_column, selector=selector) + def test_filter_function(self, test_column, test_selector, filter_function, managers_one_page_two_graphs): + filter = vm.Filter(column=test_column, selector=test_selector) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert filter._filter_function is filter_function + + def test_set_actions(self, managers_one_page_two_graphs): + filter = vm.Filter(column="country") + model_manager["test_page"].controls = [filter] filter.pre_build() [default_action] = filter.selector.actions - assert isinstance(default_action, _filter) + assert isinstance(default_action, update_figures) assert default_action.id == f"__filter_action_{filter.id}" - assert default_action.filter_function == filter_function - assert default_action.column == filtered_column assert default_action.targets == ["scatter_chart", "bar_chart"] + # @pytest.mark.parametrize( + # "filtered_column, selector, filter_function", + # [ + # ("lifeExp", None, _filter_between), + # ("country", None, _filter_isin), + # ("year", None, _filter_between), + # ("year", vm.DatePicker(range=False), _filter_isin), + # ("is_europe", None, _filter_isin), + # ], + # ) + # def test_set_actions(self, filtered_column, selector, filter_function, managers_one_page_two_graphs): + # filter = vm.Filter(column=filtered_column, selector=selector) + # model_manager["test_page"].controls = [filter] + # filter.pre_build() + # + # [default_action] = filter.selector.actions + # + # assert isinstance(default_action, _filter) + # assert default_action.id == f"__filter_action_{filter.id}" + # assert default_action.filter_function == filter_function + # assert default_action.column == filtered_column + # assert default_action.targets == ["scatter_chart", "bar_chart"] + # TODO: Add tests for custom temporal and categorical selectors too. Probably inside the conftest file and reused in # all other tests. Also add tests for the custom selector that is an entirely new component and adjust docs. # This test does add_type so ideally we would clean up after this to restore vizro.models to its previous state. @@ -976,10 +1008,8 @@ def build(self): [default_action] = filter.selector.actions - assert isinstance(default_action, _filter) + assert isinstance(default_action, update_figures) assert default_action.id == f"__filter_action_{filter.id}" - assert default_action.column == "lifeExp" - assert default_action.filter_function == _filter_between assert default_action.targets == ["scatter_chart", "bar_chart"] @pytest.mark.usefixtures("managers_one_page_container_controls") @@ -1056,7 +1086,7 @@ class TestFilterBuild: @pytest.mark.usefixtures("managers_one_page_two_graphs") @pytest.mark.parametrize( - "test_column ,test_selector", + "test_column, test_selector", [ ("continent", vm.Checklist()), ("continent", vm.Dropdown()), diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py index 3c9b73fbd..4abff0eda 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py @@ -3,7 +3,7 @@ from dash import dcc, html import vizro.models as vm -from vizro.actions._parameter_action import _parameter +from vizro.actions._update_figures import update_figures from vizro.managers import data_manager, model_manager from vizro.models._controls.parameter import Parameter @@ -195,11 +195,12 @@ def test_set_actions(self, test_input): page = model_manager["test_page"] page.controls = [parameter] parameter.pre_build() + [default_action] = parameter.selector.actions - assert isinstance(default_action, _parameter) + assert isinstance(default_action, update_figures) assert default_action.id == f"__parameter_action_{parameter.id}" - assert default_action.targets == ["scatter_chart.x"] + assert default_action.targets == ["scatter_chart"] def test_set_custom_action(self, managers_one_page_two_graphs, identity_action_function): action_function = identity_action_function() @@ -217,18 +218,27 @@ def test_set_custom_action(self, managers_one_page_two_graphs, identity_action_f @pytest.mark.usefixtures("managers_one_page_two_graphs_with_dynamic_data") @pytest.mark.parametrize( - "filter_targets, expected_parameter_targets", + "filter_targets, expected_parameter_targets, expected_parameter_action_argument", [ - ([], {"scatter_chart.data_frame.first_n"}), - (["scatter_chart"], {"scatter_chart.data_frame.first_n", "filter_id", "scatter_chart"}), + ([], {"scatter_chart.data_frame.first_n"}, {"scatter_chart"}), + ( + ["scatter_chart"], + {"scatter_chart.data_frame.first_n", "filter_id", "scatter_chart"}, + {"scatter_chart", "filter_id"}, + ), ( ["scatter_chart", "box_chart"], {"scatter_chart.data_frame.first_n", "filter_id", "scatter_chart", "box_chart"}, + {"scatter_chart", "filter_id", "box_chart"}, ), ], ) def test_targets_argument_for_data_frame_parameter_action( - self, filter_targets, expected_parameter_targets, gapminder_dynamic_first_n_last_n_function + self, + filter_targets, + expected_parameter_targets, + expected_parameter_action_argument, + gapminder_dynamic_first_n_last_n_function, ): data_manager["gapminder_dynamic_first_n_last_n"] = gapminder_dynamic_first_n_last_n_function @@ -246,7 +256,8 @@ def test_targets_argument_for_data_frame_parameter_action( data_frame_parameter.pre_build() [default_action] = data_frame_parameter.selector.actions - assert set(default_action.targets) == expected_parameter_targets + assert set(data_frame_parameter.targets) == expected_parameter_targets + assert set(default_action.targets) == expected_parameter_action_argument @pytest.mark.usefixtures("managers_one_page_two_graphs") def test_parameter_action_properties(self): diff --git a/vizro-core/tests/unit/vizro/models/test_page.py b/vizro-core/tests/unit/vizro/models/test_page.py index 3b71c3ed7..7cbc3d57c 100644 --- a/vizro-core/tests/unit/vizro/models/test_page.py +++ b/vizro-core/tests/unit/vizro/models/test_page.py @@ -5,7 +5,7 @@ import vizro.models as vm from vizro._constants import ON_PAGE_LOAD_ACTION_PREFIX -from vizro.actions._on_page_load import _on_page_load +from vizro.actions._update_figures import update_figures class TestPageInstantiation: @@ -109,7 +109,7 @@ def test_page_default_action(self, standard_px_chart): page.pre_build() [default_action] = page.actions - assert isinstance(default_action, _on_page_load) + assert isinstance(default_action, update_figures) assert default_action.id == f"{ON_PAGE_LOAD_ACTION_PREFIX}_{page.id}" assert default_action.targets == ["scatter_chart"] assert default_action._trigger == f"{ON_PAGE_LOAD_ACTION_PREFIX}_trigger_{page.id}.data"