From d3426c8a7bc153fd2c73fc1066c5d9f6d54e60c4 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 4 Aug 2026 22:59:50 +0200 Subject: [PATCH] Add scan timing info to config_flow for Alexa Devices (#177223) --- .../components/alexa_devices/config_flow.py | 91 +++++++++++++------ .../components/alexa_devices/strings.json | 3 + .../alexa_devices/test_config_flow.py | 25 +++++ 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/alexa_devices/config_flow.py b/homeassistant/components/alexa_devices/config_flow.py index 3ec7aee255ae..9955bc2697f9 100644 --- a/homeassistant/components/alexa_devices/config_flow.py +++ b/homeassistant/components/alexa_devices/config_flow.py @@ -1,5 +1,6 @@ """Config flow for Alexa Devices integration.""" +import asyncio from collections.abc import Mapping from typing import Any, override @@ -19,6 +20,13 @@ import homeassistant.helpers.config_validation as cv from .const import CONF_LOGIN_DATA, DOMAIN +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_CODE): cv.string, + } +) STEP_REAUTH_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_PASSWORD): cv.string, @@ -52,40 +60,71 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 MINOR_VERSION = 3 + _login_data: dict[str, Any] + + def __init__(self) -> None: + """Initialize a new AmazonDevicesConfigFlow.""" + self._login_task: asyncio.Task[dict[str, Any]] | None = None + self._login_errors: dict[str, str] = {} + self._login_result: dict[str, Any] = {} + @override async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" - errors = {} - if user_input: - try: - data = await validate_input(self.hass, user_input) - except CannotConnect: - errors["base"] = "cannot_connect" - except CannotAuthenticate: - errors["base"] = "invalid_auth" - except CannotRetrieveData: - errors["base"] = "cannot_retrieve_data" - else: - await self.async_set_unique_id(data["customer_info"]["user_id"]) - self._abort_if_unique_id_configured() - user_input.pop(CONF_CODE) - return self.async_create_entry( - title=user_input[CONF_USERNAME], - data=user_input | {CONF_LOGIN_DATA: data}, - ) + if user_input is not None: + self._login_data = user_input + return await self.async_step_login() return self.async_show_form( step_id="user", - errors=errors, - data_schema=vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_CODE): cv.string, - } - ), + data_schema=STEP_USER_DATA_SCHEMA, + errors=self._login_errors, + ) + + async def async_step_login( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Log in and scan for devices, showing progress to the user.""" + if (login_task := self._login_task) and login_task.done(): + self._login_errors = {} + try: + self._login_result = login_task.result() + except CannotConnect: + self._login_errors = {"base": "cannot_connect"} + except CannotAuthenticate: + self._login_errors = {"base": "invalid_auth"} + except CannotRetrieveData: + self._login_errors = {"base": "cannot_retrieve_data"} + finally: + self._login_task = None + + return self.async_show_progress_done( + next_step_id="user" if self._login_errors else "login_done" + ) + + if self._login_task is None: + self._login_task = self.hass.async_create_task( + validate_input(self.hass, self._login_data) + ) + + return self.async_show_progress( + step_id="login", + progress_action="login", + progress_task=self._login_task, + ) + + async def async_step_login_done( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Create the config entry after a successful login.""" + await self.async_set_unique_id(self._login_result["customer_info"]["user_id"]) + self._abort_if_unique_id_configured() + self._login_data.pop(CONF_CODE) + return self.async_create_entry( + title=self._login_data[CONF_USERNAME], + data=self._login_data | {CONF_LOGIN_DATA: self._login_result}, ) async def async_step_reauth( diff --git a/homeassistant/components/alexa_devices/strings.json b/homeassistant/components/alexa_devices/strings.json index 0e2a58adf13e..924ebefc34ba 100644 --- a/homeassistant/components/alexa_devices/strings.json +++ b/homeassistant/components/alexa_devices/strings.json @@ -22,6 +22,9 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "flow_title": "{username}", + "progress": { + "login": "Logging in and scanning for Alexa devices. This can take several minutes if you have many devices linked to your Amazon account." + }, "step": { "reauth_confirm": { "data": { diff --git a/tests/components/alexa_devices/test_config_flow.py b/tests/components/alexa_devices/test_config_flow.py index 7140e68e9415..54df8a16c385 100644 --- a/tests/components/alexa_devices/test_config_flow.py +++ b/tests/components/alexa_devices/test_config_flow.py @@ -44,6 +44,12 @@ async def test_full_flow( CONF_CODE: TEST_CODE, }, ) + assert result["type"] is FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "login" + + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == TEST_USERNAME assert result["data"] == { @@ -94,8 +100,15 @@ async def test_flow_errors( CONF_CODE: TEST_CODE, }, ) + assert result["type"] is FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "login" + + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" assert result["errors"] == {"base": error} mock_amazon_devices_client.login.login_mode_interactive.side_effect = None @@ -108,6 +121,12 @@ async def test_flow_errors( CONF_CODE: TEST_CODE, }, ) + assert result["type"] is FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "login" + + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] is FlowResultType.CREATE_ENTRY @@ -136,6 +155,12 @@ async def test_already_configured( CONF_CODE: TEST_CODE, }, ) + assert result["type"] is FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "login" + + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured"