From 7e3103cbe7c57c443d18ebf90ed89ba92890ae2a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 24 May 2026 15:14:44 +0000 Subject: [PATCH] Import --- homeassistant/components/group/__init__.py | 27 +++++++++- homeassistant/components/group/config_flow.py | 28 ++++++++++ homeassistant/components/min_max/repairs.py | 51 +++++-------------- homeassistant/components/min_max/strings.json | 1 + .../min_max/snapshots/test_init.ambr | 3 +- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index d434633e2740..714d96ecb8e0 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -3,7 +3,7 @@ import asyncio from collections.abc import Collection import logging -from typing import Any +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -136,6 +136,31 @@ def groups_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a config entry.""" + if "old_entity_id" in entry.options: + # This means we are migrating from a Min/Max helper to a Group sensor + old_entity = entry.options["old_entity_id"] + entity_reg = er.async_get(hass) + old_entity_entry = entity_reg.async_get(old_entity) + if not old_entity_entry: + # User has manually removed it before we came here + # Skip the migration and just continue with setting up the group sensor + _LOGGER.warning( + "Old entity %s has been manually removed before migration", old_entity + ) + else: + if TYPE_CHECKING: + assert old_entity_entry.config_entry_id + await hass.config_entries.async_unload(old_entity_entry.config_entry_id) + entity_reg.async_update_entity_platform( + old_entity, + DOMAIN, + new_config_entry_id=entry.entry_id, + new_unique_id=entry.entry_id, + ) + new_options = dict(entry.options) + new_options.pop("old_entity_id") + hass.config_entries.async_update_entry(entry, options=new_options) + await hass.config_entries.async_remove(old_entity_entry.config_entry_id) await hass.config_entries.async_forward_entry_setups( entry, (entry.options["group_type"],) ) diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index c5d9ab473849..01a6e53cc881 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -134,6 +134,12 @@ SENSOR_CONFIG_SCHEMA = basic_group_config_schema( ["sensor", "number", "input_number"] ).extend(SENSOR_CONFIG_EXTENDS) +SENSOR_IMPORT_SCHEMA = vol.Schema( + { + vol.Required("old_entity_id"): selector.TextSelector(), + } +).extend(SENSOR_CONFIG_SCHEMA.schema) + async def light_switch_options_schema( domain: str, handler: SchemaCommonFlowHandler | None @@ -197,8 +203,30 @@ def set_group_type( return _set_group_type +def validate_import() -> Callable[ + [SchemaCommonFlowHandler, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]] +]: + """Validate import from Min/Max integration. + + Should be removed when deprecation of Min/Max integration has ended. + """ + + async def _validate_and_set_type( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] + ) -> dict[str, Any]: + """Add group type to user input.""" + validated_input = SENSOR_IMPORT_SCHEMA(user_input) + return {CONF_GROUP_TYPE: "sensor", **validated_input} + + return _validate_and_set_type + + CONFIG_FLOW = { "user": SchemaFlowMenuStep(GROUP_TYPES), + "import": SchemaFlowFormStep( + None, + validate_user_input=validate_import(), + ), "binary_sensor": SchemaFlowFormStep( BINARY_SENSOR_CONFIG_SCHEMA, preview="group", diff --git a/homeassistant/components/min_max/repairs.py b/homeassistant/components/min_max/repairs.py index a63e2388995c..493c4ae6a139 100644 --- a/homeassistant/components/min_max/repairs.py +++ b/homeassistant/components/min_max/repairs.py @@ -1,22 +1,19 @@ """Repairs platform for the Min/Max integration.""" -from types import MappingProxyType from typing import TYPE_CHECKING, Any, cast import voluptuous as vol -from homeassistant import data_entry_flow from homeassistant.components.group import ( CONF_ENTITIES, - CONF_GROUP_TYPE, CONF_HIDE_MEMBERS, - CONF_IGNORE_NON_NUMERIC, DOMAIN as GROUP_DOMAIN, ) from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.config_entries import SOURCE_USER, ConfigEntry, ConfigEntryDisabler +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResult, FlowResultType from homeassistant.helpers import entity_registry as er from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN @@ -32,13 +29,13 @@ class MigrateToGroupSensorFlow(RepairsFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> data_entry_flow.FlowResult: + ) -> FlowResult: """Handle the first step of a fix flow.""" return await self.async_step_migrate() async def async_step_migrate( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> FlowResult: """Handle the migration step of a fix flow.""" entity_reg = er.async_get(self.hass) old_entity = entity_reg.async_get_entity_id( @@ -53,40 +50,18 @@ class MigrateToGroupSensorFlow(RepairsFlow): config.pop(CONF_ROUND_DIGITS) # Set group sensor defaults config[CONF_HIDE_MEMBERS] = False - config[CONF_IGNORE_NON_NUMERIC] = False - config[CONF_GROUP_TYPE] = SENSOR_DOMAIN + # config[CONF_IGNORE_NON_NUMERIC] = False + # config[CONF_GROUP_TYPE] = SENSOR_DOMAIN + config["old_entity_id"] = old_entity - new_config_entry = ConfigEntry( - data={}, - discovery_keys=MappingProxyType({}), - domain=GROUP_DOMAIN, - minor_version=1, - options=config, - source=SOURCE_USER, - subentries_data=[], - title=self.entry.title, - unique_id=None, - version=1, - disabled_by=ConfigEntryDisabler.USER, + import_result = await self.hass.config_entries.flow.async_init( + GROUP_DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, ) - if not await self.hass.config_entries.async_unload(self.entry.entry_id): - return self.async_abort(reason="unload_failed") - await self.hass.config_entries.async_add(new_config_entry) - try: - entity_reg.async_update_entity_platform( - old_entity, - GROUP_DOMAIN, - new_config_entry_id=new_config_entry.entry_id, - new_unique_id=new_config_entry.entry_id, - ) - except ValueError: - return self.async_abort(reason="entity_update_failed") - await self.hass.config_entries.async_set_disabled_by( - entry_id=new_config_entry.entry_id, disabled_by=None - ) - await self.hass.config_entries.async_remove(self.entry.entry_id) - + if import_result["type"] != FlowResultType.CREATE_ENTRY: + return self.async_abort(reason="could_not_import") return self.async_create_entry(data={}) entity_info = entity_reg.async_get(old_entity) diff --git a/homeassistant/components/min_max/strings.json b/homeassistant/components/min_max/strings.json index fabfe353d131..74a0cb019553 100644 --- a/homeassistant/components/min_max/strings.json +++ b/homeassistant/components/min_max/strings.json @@ -14,6 +14,7 @@ "migrate_to_group_sensor": { "fix_flow": { "abort": { + "could_not_import": "Failed to import the config entry for the Group helper, please manually remove the Min/Max helper and create your new Group helper.", "entity_not_found": "Entity could not be found as it has been removed, aborting the repair.", "entity_update_failed": "Failed to update the entity to a Group sensor, please manually remove the obsolete entity.", "unload_failed": "Failed to unload the Min/Max helper, please restart repairing the issue." diff --git a/tests/components/min_max/snapshots/test_init.ambr b/tests/components/min_max/snapshots/test_init.ambr index 7c70f223ea57..4d480a8c657a 100644 --- a/tests/components/min_max/snapshots/test_init.ambr +++ b/tests/components/min_max/snapshots/test_init.ambr @@ -15,13 +15,12 @@ ]), 'group_type': 'sensor', 'hide_members': False, - 'ignore_non_numeric': False, 'name': 'My min_max', 'type': 'max', }), 'pref_disable_new_entities': False, 'pref_disable_polling': False, - 'source': 'user', + 'source': 'import', 'subentries': list([ ]), 'title': 'My min_max',