Skip to content

Commit

Permalink
Add data entry flow show progress step (#42419)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinHjelmare committed Nov 9, 2020
1 parent 3380b69 commit 1338c4a
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 4 deletions.
52 changes: 48 additions & 4 deletions homeassistant/data_entry_flow.py
Expand Up @@ -14,8 +14,10 @@
RESULT_TYPE_ABORT = "abort"
RESULT_TYPE_EXTERNAL_STEP = "external"
RESULT_TYPE_EXTERNAL_STEP_DONE = "external_done"
RESULT_TYPE_SHOW_PROGRESS = "progress"
RESULT_TYPE_SHOW_PROGRESS_DONE = "progress_done"

# Event that is fired when a flow is progressed via external source.
# Event that is fired when a flow is progressed via external or progress source.
EVENT_DATA_ENTRY_FLOW_PROGRESSED = "data_entry_flow_progressed"


Expand Down Expand Up @@ -152,19 +154,29 @@ async def async_configure(

result = await self._async_handle_step(flow, cur_step["step_id"], user_input)

if cur_step["type"] == RESULT_TYPE_EXTERNAL_STEP:
if result["type"] not in (
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 (
RESULT_TYPE_EXTERNAL_STEP,
RESULT_TYPE_EXTERNAL_STEP_DONE,
):
raise ValueError(
"External step can only transition to "
"external step or external step done."
)
if cur_step["type"] == RESULT_TYPE_SHOW_PROGRESS and result["type"] not in (
RESULT_TYPE_SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE,
):
raise ValueError(
"Show progress can only transition to show progress or show progress done."
)

# If the result has changed from last result, fire event to update
# the frontend.
if cur_step["step_id"] != result.get("step_id"):
if (
cur_step["step_id"] != result.get("step_id")
or result["type"] == RESULT_TYPE_SHOW_PROGRESS
):
# Tell frontend to reload the flow state.
self.hass.bus.async_fire(
EVENT_DATA_ENTRY_FLOW_PROGRESSED,
Expand Down Expand Up @@ -217,13 +229,17 @@ async def _async_handle_step(
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_ABORT,
RESULT_TYPE_EXTERNAL_STEP_DONE,
RESULT_TYPE_SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE,
):
raise ValueError(f"Handler returned incorrect type: {result['type']}")

if result["type"] in (
RESULT_TYPE_FORM,
RESULT_TYPE_EXTERNAL_STEP,
RESULT_TYPE_EXTERNAL_STEP_DONE,
RESULT_TYPE_SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE,
):
flow.cur_step = result
return result
Expand Down Expand Up @@ -348,6 +364,34 @@ def async_external_step_done(self, *, next_step_id: str) -> Dict[str, Any]:
"step_id": next_step_id,
}

@callback
def async_show_progress(
self,
*,
step_id: str,
progress_action: str,
description_placeholders: Optional[Dict] = None,
) -> Dict[str, Any]:
"""Show a progress message to the user, without user input allowed."""
return {
"type": RESULT_TYPE_SHOW_PROGRESS,
"flow_id": self.flow_id,
"handler": self.handler,
"step_id": step_id,
"progress_action": progress_action,
"description_placeholders": description_placeholders,
}

@callback
def async_show_progress_done(self, *, next_step_id: str) -> Dict[str, Any]:
"""Mark the progress done."""
return {
"type": RESULT_TYPE_SHOW_PROGRESS_DONE,
"flow_id": self.flow_id,
"handler": self.handler,
"step_id": next_step_id,
}


@callback
def _create_abort_data(
Expand Down
70 changes: 70 additions & 0 deletions tests/test_data_entry_flow.py
Expand Up @@ -285,6 +285,76 @@ async def async_step_finish(self, user_input=None):
assert result["title"] == "Hello"


async def test_show_progress(hass, manager):
"""Test show progress logic."""
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):
if not user_input:
if not self.task_one_done:
self.task_one_done = True
progress_action = "task_one"
else:
progress_action = "task_two"
return self.async_show_progress(
step_id="init",
progress_action=progress_action,
)

self.data = user_input
return self.async_show_progress_done(next_step_id="finish")

async def async_step_finish(self, user_input=None):
return self.async_create_entry(title=self.data["title"], data=self.data)

events = async_capture_events(
hass, data_entry_flow.EVENT_DATA_ENTRY_FLOW_PROGRESSED
)

result = await manager.async_init("test")
assert result["type"] == data_entry_flow.RESULT_TYPE_SHOW_PROGRESS
assert result["progress_action"] == "task_one"
assert len(manager.async_progress()) == 1

# Mimic task one done and moving to task two
# Called by integrations: `hass.config_entries.flow.async_configure(…)`
result = await manager.async_configure(result["flow_id"])
assert result["type"] == data_entry_flow.RESULT_TYPE_SHOW_PROGRESS
assert result["progress_action"] == "task_two"

await hass.async_block_till_done()
assert len(events) == 1
assert events[0].data == {
"handler": "test",
"flow_id": result["flow_id"],
"refresh": True,
}

# Mimic task two done and continuing step
# Called by integrations: `hass.config_entries.flow.async_configure(…)`
result = await manager.async_configure(result["flow_id"], {"title": "Hello"})
assert result["type"] == data_entry_flow.RESULT_TYPE_SHOW_PROGRESS_DONE

await hass.async_block_till_done()
assert len(events) == 2
assert events[1].data == {
"handler": "test",
"flow_id": result["flow_id"],
"refresh": True,
}

# Frontend refreshes the flow
result = await manager.async_configure(result["flow_id"])
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Hello"


async def test_abort_flow_exception(manager):
"""Test that the AbortFlow exception works."""

Expand Down

0 comments on commit 1338c4a

Please sign in to comment.