mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 09:23:17 -04:00
Centrally handle reconfigure successful for subentries (#181225)
This commit is contained in:
@@ -33,8 +33,7 @@
|
||||
"config_subentries": {
|
||||
"ai_task_data": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::anthropic::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::anthropic::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "AI task",
|
||||
"error": {
|
||||
@@ -100,8 +99,7 @@
|
||||
},
|
||||
"conversation": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled."
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
"error": {
|
||||
|
||||
@@ -94,9 +94,6 @@
|
||||
},
|
||||
"config_subentries": {
|
||||
"observation": {
|
||||
"abort": {
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"entry_type": "Observation",
|
||||
"error": {
|
||||
"above_below": "[%key:component::bayesian::config::error::above_below%]",
|
||||
|
||||
@@ -362,6 +362,7 @@ class FishAudioSubentryFlowHandler(ConfigSubentryFlow):
|
||||
self._get_reconfigure_subentry(),
|
||||
data=self.config_data,
|
||||
unique_id=unique_id,
|
||||
reason="reconfigure_successful",
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"plane": {
|
||||
"abort": {
|
||||
"api_key_required": "An API key is required to add more than one plane. You can configure it in the integration options.",
|
||||
"max_planes": "You can add a maximum of 4 planes.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"max_planes": "You can add a maximum of 4 planes."
|
||||
},
|
||||
"entry_type": "Plane",
|
||||
"initiate_flow": {
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"config_subentries": {
|
||||
"ai_task_data": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "AI task",
|
||||
"initiate_flow": {
|
||||
@@ -54,8 +53,7 @@
|
||||
},
|
||||
"conversation": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled."
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
"error": {
|
||||
@@ -96,8 +94,7 @@
|
||||
},
|
||||
"stt": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "Speech-to-text",
|
||||
"initiate_flow": {
|
||||
@@ -130,8 +127,7 @@
|
||||
},
|
||||
"tts": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::google_generative_ai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "Text-to-speech",
|
||||
"initiate_flow": {
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
"location": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled."
|
||||
},
|
||||
"entry_type": "Location",
|
||||
"error": {
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before reconfiguring.",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
|
||||
@@ -104,9 +104,6 @@
|
||||
},
|
||||
"config_subentries": {
|
||||
"device": {
|
||||
"abort": {
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "MQTT device with {platform} entity \"{entity}\" was set up successfully.\n\nNote that you can reconfigure the MQTT device at any time, e.g. to add more entities."
|
||||
},
|
||||
|
||||
@@ -83,8 +83,7 @@
|
||||
"config_subentries": {
|
||||
"topic": {
|
||||
"abort": {
|
||||
"already_configured": "Topic is already configured",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"already_configured": "Topic is already configured"
|
||||
},
|
||||
"entry_type": "[%key:component::ntfy::common::topic%]",
|
||||
"error": {
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"download_failed": "[%key:component::ollama::config_subentries::conversation::abort::download_failed%]",
|
||||
"entry_not_loaded": "[%key:component::ollama::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::ollama::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "AI task",
|
||||
"initiate_flow": {
|
||||
@@ -68,8 +67,7 @@
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"download_failed": "Model downloading failed",
|
||||
"entry_not_loaded": "Failed to add agent. The configuration is disabled.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "Failed to add agent. The configuration is disabled."
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
"initiate_flow": {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before reconfiguring.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"entry_type": "AI task",
|
||||
@@ -44,7 +43,6 @@
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"entry_not_loaded": "[%key:component::open_router::config_subentries::ai_task_data::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"config_subentries": {
|
||||
"ai_task_data": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "AI task",
|
||||
"error": {
|
||||
@@ -96,8 +95,7 @@
|
||||
},
|
||||
"conversation": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "Cannot add things while the configuration is disabled."
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
"error": {
|
||||
@@ -165,8 +163,7 @@
|
||||
},
|
||||
"stt": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "Speech-to-text",
|
||||
"initiate_flow": {
|
||||
@@ -189,8 +186,7 @@
|
||||
},
|
||||
"tts": {
|
||||
"abort": {
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "[%key:component::openai_conversation::config_subentries::conversation::abort::entry_not_loaded%]"
|
||||
},
|
||||
"entry_type": "Text-to-speech",
|
||||
"initiate_flow": {
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"entry_not_loaded": "The main integration entry is not loaded. Please ensure the integration is loaded before configuring.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"entry_type": "Conversation agent",
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
"list": {
|
||||
"abort": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"entry_not_loaded": "The Remember The Milk account must be loaded before adding or renaming a list.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"entry_not_loaded": "The Remember The Milk account must be loaded before adding or renaming a list."
|
||||
},
|
||||
"entry_type": "List",
|
||||
"error": {
|
||||
|
||||
@@ -86,8 +86,7 @@
|
||||
"config_subentries": {
|
||||
"recipient": {
|
||||
"abort": {
|
||||
"already_configured": "Recipient is already configured",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"already_configured": "Recipient is already configured"
|
||||
},
|
||||
"entry_type": "Recipient",
|
||||
"initiate_flow": {
|
||||
|
||||
@@ -3835,6 +3835,7 @@ class ConfigSubentryFlow(
|
||||
title: str | UndefinedType = UNDEFINED,
|
||||
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
data_updates: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
reason: str | UndefinedType = UNDEFINED,
|
||||
) -> SubentryFlowResult:
|
||||
"""Update config subentry and finish subentry flow.
|
||||
|
||||
@@ -3843,6 +3844,8 @@ class ConfigSubentryFlow(
|
||||
keys are overridden
|
||||
:param title: replace the title of the subentry
|
||||
:param unique_id: replace the unique_id of the subentry
|
||||
:param reason: set the reason for the abort, defaults to
|
||||
`reconfigure_successful`. A custom reason requires a matching strings.json entry
|
||||
"""
|
||||
self._async_update(
|
||||
entry=entry,
|
||||
@@ -3852,7 +3855,11 @@ class ConfigSubentryFlow(
|
||||
data=data,
|
||||
data_updates=data_updates,
|
||||
)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
translation_domain: str | None = None
|
||||
if reason is UNDEFINED:
|
||||
reason = "reconfigure_successful"
|
||||
translation_domain = HOMEASSISTANT_DOMAIN
|
||||
return self.async_abort(reason=reason, translation_domain=translation_domain)
|
||||
|
||||
@callback
|
||||
def async_update_reload_and_abort(
|
||||
@@ -3864,6 +3871,7 @@ class ConfigSubentryFlow(
|
||||
title: str | UndefinedType = UNDEFINED,
|
||||
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
data_updates: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
reason: str | UndefinedType = UNDEFINED,
|
||||
reload_even_if_entry_is_unchanged: bool = True,
|
||||
) -> SubentryFlowResult:
|
||||
"""Update config subentry, reload config entry and finish subentry flow.
|
||||
@@ -3873,6 +3881,8 @@ class ConfigSubentryFlow(
|
||||
keys are overridden
|
||||
:param title: replace the title of the subentry
|
||||
:param unique_id: replace the unique_id of the subentry
|
||||
:param reason: set the reason for the abort, defaults to
|
||||
`reconfigure_successful`. A custom reason requires a matching strings.json entry
|
||||
:param reload_even_if_entry_is_unchanged: set this to `False` if the entry
|
||||
should not be reloaded if it is unchanged
|
||||
"""
|
||||
@@ -3888,7 +3898,11 @@ class ConfigSubentryFlow(
|
||||
if entry.update_listeners:
|
||||
raise ValueError("Cannot update and reload entry with update listeners")
|
||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
return self.async_abort(reason="reconfigure_successful")
|
||||
translation_domain: str | None = None
|
||||
if reason is UNDEFINED:
|
||||
reason = "reconfigure_successful"
|
||||
translation_domain = HOMEASSISTANT_DOMAIN
|
||||
return self.async_abort(reason=reason, translation_domain=translation_domain)
|
||||
|
||||
@property
|
||||
def _entry_id(self) -> str:
|
||||
|
||||
@@ -1729,6 +1729,7 @@ async def test_subentry_reconfigure_flow(hass: HomeAssistant, client) -> None:
|
||||
assert data == {
|
||||
"handler": ["test1", "test"],
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": "homeassistant",
|
||||
"type": "abort",
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
@@ -7746,6 +7746,62 @@ async def test_update_subentry_and_abort(
|
||||
else:
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
assert result["translation_domain"] == HOMEASSISTANT_DOMAIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"update_and_abort",
|
||||
["async_update_and_abort", "async_update_reload_and_abort"],
|
||||
)
|
||||
async def test_update_subentry_custom_reason_stays_local(
|
||||
hass: HomeAssistant, update_and_abort: str
|
||||
) -> None:
|
||||
"""Test a caller supplied reason resolves against the integration itself."""
|
||||
subentry_id = "blabla"
|
||||
entry = MockConfigEntry(
|
||||
domain="comp",
|
||||
unique_id="entry_unique_id",
|
||||
title="entry_title",
|
||||
data={},
|
||||
subentries_data=[
|
||||
config_entries.ConfigSubentryData(
|
||||
data={"vendor": "data"},
|
||||
subentry_id=subentry_id,
|
||||
subentry_type="test",
|
||||
unique_id="1234",
|
||||
title="Test",
|
||||
)
|
||||
],
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(hass, MockModule("comp"))
|
||||
mock_platform(hass, "comp.config_flow", None)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
class SubentryFlowHandler(config_entries.ConfigSubentryFlow):
|
||||
async def async_step_reconfigure(self, user_input=None):
|
||||
return getattr(self, update_and_abort)(
|
||||
self._get_entry(),
|
||||
self._get_reconfigure_subentry(),
|
||||
reason="custom_reason",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_get_supported_subentry_types(
|
||||
cls, config_entry: config_entries.ConfigEntry
|
||||
) -> dict[str, type[config_entries.ConfigSubentryFlow]]:
|
||||
return {"test": TestFlow.SubentryFlowHandler}
|
||||
|
||||
with mock_config_flow("comp", TestFlow):
|
||||
result = await entry.start_subentry_reconfigure_flow(hass, subentry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "custom_reason"
|
||||
assert "translation_domain" not in result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -7775,6 +7831,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
@@ -7793,6 +7850,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
@@ -7811,6 +7869,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
@@ -7825,6 +7884,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
@@ -7841,6 +7901,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
@@ -7855,6 +7916,7 @@ async def test_update_subentry_and_abort(
|
||||
{
|
||||
"type": FlowResultType.ABORT,
|
||||
"reason": "reconfigure_successful",
|
||||
"translation_domain": HOMEASSISTANT_DOMAIN,
|
||||
"description_placeholders": None,
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user