Add support for menu step (#68203)

This commit is contained in:
Paulus Schoutsen 2022-03-16 14:14:50 -07:00 committed by GitHub
parent aa57907c18
commit f6af93ae35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 4 deletions

View file

@ -21,6 +21,7 @@ RESULT_TYPE_EXTERNAL_STEP = "external"
RESULT_TYPE_EXTERNAL_STEP_DONE = "external_done"
RESULT_TYPE_SHOW_PROGRESS = "progress"
RESULT_TYPE_SHOW_PROGRESS_DONE = "progress_done"
RESULT_TYPE_MENU = "menu"
# Event that is fired when a flow is progressed via external or progress source.
EVENT_DATA_ENTRY_FLOW_PROGRESSED = "data_entry_flow_progressed"
@ -82,6 +83,7 @@ class FlowResult(TypedDict, total=False):
result: Any
last_step: bool | None
options: Mapping[str, Any]
menu_options: list[str] | Mapping[str, Any]
@callback
@ -249,7 +251,15 @@ class FlowManager(abc.ABC):
if cur_step.get("data_schema") is not None and user_input is not None:
user_input = cur_step["data_schema"](user_input)
result = await self._async_handle_step(flow, cur_step["step_id"], user_input)
# Handle a menu navigation choice
if cur_step["type"] == RESULT_TYPE_MENU and user_input:
result = await self._async_handle_step(
flow, user_input["next_step_id"], None
)
else:
result = await self._async_handle_step(
flow, cur_step["step_id"], user_input
)
if cur_step["type"] in (RESULT_TYPE_EXTERNAL_STEP, RESULT_TYPE_SHOW_PROGRESS):
if cur_step["type"] == RESULT_TYPE_EXTERNAL_STEP and result["type"] not in (
@ -343,6 +353,7 @@ class FlowManager(abc.ABC):
RESULT_TYPE_EXTERNAL_STEP_DONE,
RESULT_TYPE_SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE,
RESULT_TYPE_MENU,
):
raise ValueError(f"Handler returned incorrect type: {result['type']}")
@ -352,6 +363,7 @@ class FlowManager(abc.ABC):
RESULT_TYPE_EXTERNAL_STEP_DONE,
RESULT_TYPE_SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE,
RESULT_TYPE_MENU,
):
flow.cur_step = result
return result
@ -507,6 +519,28 @@ class FlowHandler:
"step_id": next_step_id,
}
@callback
def async_show_menu(
self,
*,
step_id: str,
menu_options: list[str] | dict[str, str],
description_placeholders: dict | None = None,
) -> FlowResult:
"""Show a navigation menu to the user.
Options dict maps step_id => i18n label
"""
return {
"type": RESULT_TYPE_MENU,
"flow_id": self.flow_id,
"handler": self.handler,
"step_id": step_id,
"data_schema": vol.Schema({"next_step_id": vol.In(menu_options)}),
"menu_options": menu_options,
"description_placeholders": description_placeholders,
}
@callback
def _create_abort_data(

View file

@ -6,6 +6,7 @@ from typing import Any
from aiohttp import web
import voluptuous as vol
import voluptuous_serialize
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.http import HomeAssistantView
@ -32,11 +33,9 @@ class _BaseFlowManagerView(HomeAssistantView):
data.pop("data")
return data
if result["type"] != data_entry_flow.RESULT_TYPE_FORM:
if "data_schema" not in result:
return result
import voluptuous_serialize # pylint: disable=import-outside-toplevel
data = result.copy()
if (schema := data["data_schema"]) is None:

View file

@ -110,6 +110,7 @@ def gen_data_entry_schema(
step_title_class("title"): cv.string_with_no_html,
vol.Optional("description"): cv.string_with_no_html,
vol.Optional("data"): {str: cv.string_with_no_html},
vol.Optional("menu_options"): {str: cv.string_with_no_html},
}
},
vol.Optional("error"): {str: cv.string_with_no_html},

View file

@ -487,3 +487,46 @@ async def test_abort_raises_unknown_flow_if_not_in_progress(manager):
"""Test abort raises UnknownFlow if the flow is not in progress."""
with pytest.raises(data_entry_flow.UnknownFlow):
await manager.async_abort("wrong_flow_id")
@pytest.mark.parametrize(
"menu_options",
(["target1", "target2"], {"target1": "Target 1", "target2": "Target 2"}),
)
async def test_show_menu(hass, manager, menu_options):
"""Test show menu."""
manager.hass = hass
@manager.mock_reg_handler("test")
class TestFlow(data_entry_flow.FlowHandler):
VERSION = 5
data = None
task_one_done = False
async def async_step_init(self, user_input=None):
return self.async_show_menu(
step_id="init",
menu_options=menu_options,
description_placeholders={"name": "Paulus"},
)
async def async_step_target1(self, user_input=None):
return self.async_show_form(step_id="target1")
async def async_step_target2(self, user_input=None):
return self.async_show_form(step_id="target2")
result = await manager.async_init("test")
assert result["type"] == data_entry_flow.RESULT_TYPE_MENU
assert result["menu_options"] == menu_options
assert result["description_placeholders"] == {"name": "Paulus"}
assert len(manager.async_progress()) == 1
assert len(manager.async_progress_by_handler("test")) == 1
assert manager.async_get(result["flow_id"])["handler"] == "test"
# Mimic picking a step
result = await manager.async_configure(
result["flow_id"], {"next_step_id": "target1"}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "target1"