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

Add data entry flow show progress step #42419

Merged
merged 8 commits into from Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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):
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
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