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

Minor update to Navigation docs #190

Merged
merged 19 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
-->

<!--
### Highlights ✨

- A bullet item for the Highlights ✨ category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
<!--
### Removed

- A bullet item for the Removed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
<!--
### Added

- A bullet item for the Added category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
<!--
### Changed

- A bullet item for the Changed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
<!--
### Deprecated

- A bullet item for the Deprecated category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
<!--
### Fixed

- A bullet item for the Fixed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved

-->
<!--
### Security

- A bullet item for the Security category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1))

-->
9 changes: 5 additions & 4 deletions vizro-core/docs/pages/user_guides/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,12 @@ Another way to group together pages in the navigation is to use a [`NavBar`][viz
[NavBar]: ../../assets/user_guides/navigation/nav_bar.png


Here, the first level of the navigation hierarchy ("Group A" and "Group B") is represented by an icon in a navigation bar, and the second level of the navigation (the pages) is represented by an accordion. By default, the icons are the [`filter` icons from the Google Material icons library](https://fonts.google.com/icons?icon.query=filter). The icon label ("Group A" and "Group B") appears as a tooltip on hovering over the icon.
Here, the first level of the navigation hierarchy ("Group A" and "Group B") is represented by an icon in a navigation bar, and the second level of the navigation (the pages) is represented by an accordion.
By default, the set of icons used are the [`filter` icons from the Google Material icons library](https://fonts.google.com/icons?icon.query=filter). The icon label ("Group A" and "Group B") appears as a tooltip on hovering over the icon.

## Customizing the navigation bar

Under the hood, [`NavBar`][vizro.models.NavBar] uses [`NavLink`][vizro.models.NavLink] to build the icons in the navigation bar. It is possible to customize the navigation further by providing the `NavLink`s yourself.
Under the hood, [`NavBar`][vizro.models.NavBar] uses [`NavLink`][vizro.models.NavLink] to build the icons in the navigation bar. It is possible to customize the navigation further by providing the `NavLink` models yourself.

### `NavLink` examples

Expand Down Expand Up @@ -239,7 +240,7 @@ You can alter the icons used by specifying the name of the icon in the [Google M
items=[
vm.NavLink(
label="Section 1",
icon="bar_chart",
icon="Bar Chart",
nadijagraca marked this conversation as resolved.
Show resolved Hide resolved
pages=["My first page", "My second page"],
),
vm.NavLink(label="Section 2", icon="pie_chart", pages=["My third page"]),
Expand All @@ -259,7 +260,7 @@ You can alter the icons used by specifying the name of the icon in the [Google M
type: nav_bar
items:
- label: Section 1
icon: bar_chart
icon: Bar Chart
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
pages:
- My first page
- My second page
Expand Down
4 changes: 2 additions & 2 deletions vizro-core/schemas/0.1.7.dev0.json
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@
},
"NavLink": {
"title": "NavLink",
"description": "Icon to be used in Navigation Panel of Dashboard.\n\nArgs:\n ages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].\n Defaults to `[]`.\n label (str): Text description of the icon for use in tooltip.\n icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons).\n Defaults to `\"\"`.",
"description": "Icon to be used in Navigation Bar of Dashboard.\n\nArgs:\n pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.\n label (str): Text description of the icon for use in tooltip.\n icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons). Defaults to `\"\"`.",
"type": "object",
"properties": {
"id": {
Expand Down Expand Up @@ -1041,7 +1041,7 @@
},
"Navigation": {
"title": "Navigation",
"description": "Navigation in [`Dashboard`][vizro.models.Dashboard] to structure [`Pages`][vizro.models.Page].\n\nArgs:\n pages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].\n Defaults to [].\n nav_selector (Optional[NavSelectorType]): See [`NavSelectorType`][vizro.models.types.NavSelectorType].\n Defaults to `None`.",
"description": "Navigation in [`Dashboard`][vizro.models.Dashboard] to structure [`Pages`][vizro.models.Page].\n\nArgs:\n pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.\n nav_selector (Optional[NavSelectorType]): See [`NavSelectorType`][vizro.models.types.NavSelectorType].\n Defaults to `None`.",
"type": "object",
"properties": {
"id": {
Expand Down
4 changes: 2 additions & 2 deletions vizro-core/schemas/0.1.7.dev1.json
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@
},
"NavLink": {
"title": "NavLink",
"description": "Icon to be used in Navigation Panel of Dashboard.\n\nArgs:\n ages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].\n Defaults to `[]`.\n label (str): Text description of the icon for use in tooltip.\n icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons).\n Defaults to `\"\"`.",
"description": "Icon to be used in Navigation Bar of Dashboard.\n\nArgs:\n pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.\n label (str): Text description of the icon for use in tooltip.\n icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons). Defaults to `\"\"`.",
"type": "object",
"properties": {
"id": {
Expand Down Expand Up @@ -1041,7 +1041,7 @@
},
"Navigation": {
"title": "Navigation",
"description": "Navigation in [`Dashboard`][vizro.models.Dashboard] to structure [`Pages`][vizro.models.Page].\n\nArgs:\n pages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].\n Defaults to [].\n nav_selector (Optional[NavSelectorType]): See [`NavSelectorType`][vizro.models.types.NavSelectorType].\n Defaults to `None`.",
"description": "Navigation in [`Dashboard`][vizro.models.Dashboard] to structure [`Pages`][vizro.models.Page].\n\nArgs:\n pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.\n nav_selector (Optional[NavSelectorType]): See [`NavSelectorType`][vizro.models.types.NavSelectorType].\n Defaults to `None`.",
"type": "object",
"properties": {
"id": {
Expand Down
2 changes: 1 addition & 1 deletion vizro-core/src/vizro/actions/export_data_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def export_data(
"""Exports visible data of target charts/components on page after being triggered.

Args:
targets: List of target component ids to download data from. Defaults to None.
targets: List of target component ids to download data from. Defaults to `None`.
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
file_format: Format of downloaded files. Defaults to `csv`.
inputs: Dict mapping action function names with their inputs e.g.
inputs = {'filters': [], 'parameters': ['gdpPercap'], 'filter_interaction': [], 'theme_selector': True}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def filter_interaction(

Args:
targets: List of target component ids to filter by chart interaction. If missing, will target all valid
components on page. Defaults to None.
components on page. Defaults to `None`.
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
inputs: Dict mapping action function names with their inputs e.g.
inputs = {'filters': [], 'parameters': ['gdpPercap'], 'filter_interaction': [], 'theme_selector': True}

Expand Down
4 changes: 2 additions & 2 deletions vizro-core/src/vizro/models/_components/form/_user_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ class UserInput(VizroBaseModel):
actions (Optional[List[Action]]): Defaults to `[]`.
title (str): Title to be displayed. Defaults to `""`.
input_type (Literal["text", "number", "password", "email", "search", "tel", "url", "range", "hidden"]):
Type of value to validate user input against. Defaults to 'text'.
Type of value to validate user input against. Defaults to `"text"`.
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
"""

type: Literal["user_input"] = "user_input"
title: str = Field("", description="Title to be displayed")
placeholder: str = Field("", description="Default text to display in input field")
input_type: Literal["text", "number", "password", "email", "search", "tel", "url", "range", "hidden"] = Field(
"text", description="Type of value to validate user input against. Defaults to 'text'."
"text", description="Type of value to validate user input against."
)
actions: List[Action] = []

Expand Down
12 changes: 7 additions & 5 deletions vizro-core/src/vizro/models/_navigation/nav_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@


class NavLink(VizroBaseModel):
"""Icon to be used in Navigation Panel of Dashboard.
"""Icon to be used in Navigation Bar of Dashboard.

Args:
ages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].
Defaults to `[]`.
pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.
label (str): Text description of the icon for use in tooltip.
icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons).
Defaults to `""`.
icon (str): Icon name from [Google Material icons library](https://fonts.google.com/icons). Defaults to `""`.

"""

Expand All @@ -40,6 +38,10 @@ class NavLink(VizroBaseModel):
# Re-used validators
_validate_pages = validator("pages", allow_reuse=True)(_validate_pages)

@validator("icon")
def validate_icon(cls, icon) -> str:
return icon.strip().lower().replace(" ", "_")

@_log_call
def pre_build(self):
from vizro.models._navigation.accordion import Accordion
Expand Down
12 changes: 7 additions & 5 deletions vizro-core/src/vizro/models/_navigation/navigation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Optional, cast

from dash import html

try:
Expand All @@ -18,14 +20,13 @@ class Navigation(VizroBaseModel):
"""Navigation in [`Dashboard`][vizro.models.Dashboard] to structure [`Pages`][vizro.models.Page].

Args:
pages (Optional[NavPagesType]): See [`NavPagesType`][vizro.models.types.NavPagesType].
Defaults to [].
pages (NavPagesType): See [`NavPagesType`][vizro.models.types.NavPagesType]. Defaults to `[]`.
nav_selector (Optional[NavSelectorType]): See [`NavSelectorType`][vizro.models.types.NavSelectorType].
Defaults to `None`.
"""

pages: NavPagesType = []
nav_selector: NavSelectorType = None
nav_selector: Optional[NavSelectorType] = None
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@antonymilne - here I wasn't sure whether the Optional was removed intentionally?

Copy link
Contributor

Choose a reason for hiding this comment

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

To be confirmed...

Copy link
Contributor Author

@huong-li-nguyen huong-li-nguyen Dec 6, 2023

Choose a reason for hiding this comment

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

After reading through this section of the migration guide for pydantic V2 and this forum post I don't have a strong preference, both should work fine.

To sum up the relevant parts:

Runtime implications
In practice this means that for the following model all fields behave identically in the sense that neither is required during initialization because all of them will receive None as their value, when no other value is provided, and the type of each of them is set to be Optional[str].


from typing import Optional, Union
from pydantic import BaseModel, Field

class Model(BaseModel):
    a: str | None
    b: Union[str, None]
    c: Optional[str]
    d: Optional[str] = None
    e: Optional[str] = Field(default=None)
    f: Optional[str] = Field(default_factory=lambda: None)
    g: str = None

It seems that the g case, the one we are talking about here is under the hood converted to Optional[str] = None:

Note that even g is implicitly handled in a way that sets its type to be Optional[str], even though we annotate it with just str. You can verify this by doing the following:

for field in Model.__fields__.values():
    print(repr(field))

Migration to V2
What will change in pydantic V2 is the way how Optional[X] are handled if no defaults are provided as far as I understand e.g. Optional[str] and Optional[str] = None were both considered as non-required and a default of None was automatically set in V1.

This is not the case anymore with V2. Optional[str] without a default will be considered as a required field, while Optional[str] = None is not required, can be None, and is None by default. That being said you are right that str = None will result in the same, so that field being considered as non-required, can be None and is None by default. So functionally both are the same.

Screenshot 2023-12-06 at 11 07 31

I have a slight preference for Optional[XXX] = None if we want to be more explicit, but as you already mentioned this sometimes leads to these mypy issues where we have to solve it via cast, making it a bit difficult to understand for new developers. So it’s questionable whether this improves it. So either solutions are actually fine for me :)

Copy link
Contributor

@maxschulz-COL maxschulz-COL Dec 12, 2023

Choose a reason for hiding this comment

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

Interesting conversation. Optional[XXX] = None seems to be a little more what python developer are used to I guess, but if it causes other problems, happy to change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed w/ @antonymilne : Changes will be reverted here and handled properly in separate PR

Copy link
Contributor

Choose a reason for hiding this comment

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

See #210


# validators
_validate_pages = validator("pages", allow_reuse=True)(_validate_pages)
Expand All @@ -41,11 +42,12 @@ def pre_build(self):

@_log_call
def build(self, *, active_page_id=None) -> _NavBuildType:
nav_selector = self.nav_selector.build(active_page_id=active_page_id)
nav_selector = cast(NavSelectorType, self.nav_selector).build(active_page_id=active_page_id)

if "nav_bar_outer" not in nav_selector:
# e.g. nav_selector is Accordion and nav_selector.build returns single html.Div with id="nav_panel_outer".
# This will make it match the case e.g. nav_selector is NavBar and nav_selector.build returns html.Div
# containing children with id="nav_bar_outer" and id="nav_panel_outer"
nav_selector = html.Div([html.Div(className="hidden", id="nav_bar_outer"), nav_selector])
nav_selector = html.Div([html.Div(hidden=True, id="nav_bar_outer"), nav_selector])

return nav_selector
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def test_items_with_with_pages_icons(self, pages_as_dict):
nav_bar = vm.NavBar(pages=pages_as_dict, items=nav_links)
nav_bar.pre_build()
assert nav_bar.items == nav_links
assert nav_bar.items[0].icon == "Home"
assert nav_bar.items[0].icon == "home"
assert nav_bar.items[1].icon == "filter_2"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def test_nav_link_mandatory_and_optional(self, pages_as_list):
assert nav_link.icon == "home"
assert nav_link.pages == pages_as_list

@pytest.mark.parametrize("icon", ["Bar Chart", "bar chart", "bar_chart", "Bar_Chart", " bar_chart "])
def test_validate_icon(self, icon):
nav_link = vm.NavLink(icon=icon, label="Label")
assert nav_link.icon == "bar_chart"

def test_nav_link_valid_pages_as_dict(self, pages_as_dict):
nav_link = vm.NavLink(pages=pages_as_dict, label="Label")
assert nav_link.pages == pages_as_dict
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ def test_default_nav_selector(self, pages, request):
navigation.pre_build()
built_navigation = navigation.build(active_page_id="Page 1")
assert_component_equal(
built_navigation["nav_bar_outer"],
html.Div(className="hidden", id="nav_bar_outer"),
keys_to_strip={"children"},
built_navigation["nav_bar_outer"], html.Div(hidden=True, id="nav_bar_outer"), keys_to_strip={}
)
assert_component_equal(
built_navigation["nav_panel_outer"], html.Div(id="nav_panel_outer"), keys_to_strip={"children", "className"}
Expand Down
Loading