diff --git a/homeassistant/components/min_max/__init__.py b/homeassistant/components/min_max/__init__.py index 343759c2e4dc..fbb3fb63ee88 100644 --- a/homeassistant/components/min_max/__init__.py +++ b/homeassistant/components/min_max/__init__.py @@ -1,79 +1,33 @@ """The min_max component.""" -from datetime import datetime -import logging -from types import MappingProxyType - -from homeassistant.components.group import ( - CONF_ENTITIES, - CONF_GROUP_TYPE, - CONF_HIDE_MEMBERS, - CONF_IGNORE_NON_NUMERIC, - DOMAIN as GROUP_DOMAIN, -) -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.config_entries import SOURCE_USER, ConfigEntry, ConfigEntryState +from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN +from .const import DOMAIN PLATFORMS = [Platform.SENSOR] -_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Min/Max from a config entry.""" - - # Create group config from entry options - config = dict(entry.options) - config[CONF_ENTITIES] = config.pop(CONF_ENTITY_IDS) - 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 - - new_config_entry = ConfigEntry( - data={}, - discovery_keys=MappingProxyType({}), - domain=GROUP_DOMAIN, - minor_version=1, - options=config, - source=SOURCE_USER, - subentries_data=[], - title=entry.title, - unique_id=None, - version=1, + async_create_issue( + hass, + DOMAIN, + "migrate_to_group_sensor", + is_fixable=True, + is_persistent=False, + severity=IssueSeverity.WARNING, + translation_key="migrate_to_group_sensor", + translation_placeholders={"title": entry.title}, + data={"entry_id": entry.entry_id}, ) - - entity_reg = er.async_get(hass) - if old_entity := entity_reg.async_get_entity_id( - SENSOR_DOMAIN, DOMAIN, entry.entry_id - ): - entity_reg.async_update_entity_platform( - old_entity, GROUP_DOMAIN, new_config_entry_id=new_config_entry.entry_id - ) - # If entity is not existing, it has already been migrated - # and we should not create it again - await hass.config_entries.async_add(new_config_entry) - - # Wait for config entry setup to finish before removing the old config entry - async def remove_old_entry(now: datetime) -> None: - """Remove the old config entry after migration.""" - if entry.state == ConfigEntryState.LOADED: - await hass.config_entries.async_remove(entry.entry_id) - else: - async_call_later(hass, 5, remove_old_entry) - - async_call_later(hass, 5, remove_old_entry) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - - return True + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/min_max/config_flow.py b/homeassistant/components/min_max/config_flow.py index af404af3f822..f6c7c8b6399b 100644 --- a/homeassistant/components/min_max/config_flow.py +++ b/homeassistant/components/min_max/config_flow.py @@ -1,19 +1,82 @@ """Config flow for Min/Max integration.""" -from typing import Any +from collections.abc import Mapping +from typing import Any, cast -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +import voluptuous as vol -from .const import DOMAIN +from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN +from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import CONF_TYPE +from homeassistant.helpers import selector +from homeassistant.helpers.schema_config_entry_flow import ( + SchemaCommonFlowHandler, + SchemaConfigFlowHandler, + SchemaFlowFormStep, +) + +from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN + +_STATISTIC_MEASURES = [ + "min", + "max", + "mean", + "median", + "last", + "range", + "sum", +] -class MinMaxConfigFlow(ConfigFlow, domain=DOMAIN): - """Handle a config flow for min_max integration.""" +OPTIONS_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY_IDS): selector.EntitySelector( + selector.EntitySelectorConfig( + domain=[SENSOR_DOMAIN, NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], + multiple=True, + ), + ), + vol.Required(CONF_TYPE): selector.SelectSelector( + selector.SelectSelectorConfig( + options=_STATISTIC_MEASURES, translation_key=CONF_TYPE + ), + ), + vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=6, mode=selector.NumberSelectorMode.BOX + ), + ), + } +) - VERSION = 1 +CONFIG_SCHEMA = vol.Schema({}) - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Handle the user step.""" - return self.async_abort(reason="migrated_to_groups") + +async def migrate_to_groups( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Abort flow as migrate to groups.""" + handler.parent_handler.async_abort(reason="migrated_to_groups") + return user_input + + +CONFIG_FLOW = { + "user": SchemaFlowFormStep(CONFIG_SCHEMA, validate_user_input=migrate_to_groups), +} + +OPTIONS_FLOW = { + "init": SchemaFlowFormStep(OPTIONS_SCHEMA), +} + + +class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): + """Handle a config or options flow for Min/Max.""" + + config_flow = CONFIG_FLOW + options_flow = OPTIONS_FLOW + options_flow_reloads = True + + def async_config_entry_title(self, options: Mapping[str, Any]) -> str: + """Return config entry title.""" + return cast(str, options["name"]) if "name" in options else "" diff --git a/homeassistant/components/min_max/repairs.py b/homeassistant/components/min_max/repairs.py new file mode 100644 index 000000000000..0bbb4d7f89b2 --- /dev/null +++ b/homeassistant/components/min_max/repairs.py @@ -0,0 +1,102 @@ +"""Repairs platform for the Workday integration.""" + +from __future__ import annotations + +from types import MappingProxyType +from typing import 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 +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN + + +class MigrateToGroupSensorFlow(RepairsFlow): + """Handler for an issue fixing flow.""" + + def __init__(self, entry: ConfigEntry) -> None: + """Create flow.""" + self.entry = entry + super().__init__() + + async def async_step_init( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.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: + """Handle the migration step of a fix flow.""" + errors: dict[str, str] = {} + if user_input is not None: + config = dict(self.entry.options) + config[CONF_ENTITIES] = config.pop(CONF_ENTITY_IDS) + 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 + + 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, + ) + + entity_reg = er.async_get(self.hass) + if old_entity := entity_reg.async_get_entity_id( + SENSOR_DOMAIN, DOMAIN, self.entry.entry_id + ): + entity_reg.async_update_entity_platform( + old_entity, + GROUP_DOMAIN, + new_config_entry_id=new_config_entry.entry_id, + ) + + await self.hass.config_entries.async_add(new_config_entry) + await self.hass.config_entries.async_remove(self.entry.entry_id) + + return self.async_create_entry(data={}) + + return self.async_show_form( + step_id="migrate", + data_schema=vol.Schema({}), + errors=errors, + ) + + +async def async_create_fix_flow( + hass: HomeAssistant, + issue_id: str, + data: dict[str, Any] | None, +) -> RepairsFlow: + """Create flow.""" + if data and (entry_id := data.get("entry_id")): + entry_id = cast(str, entry_id) + entry = hass.config_entries.async_get_entry(entry_id) + assert entry + return MigrateToGroupSensorFlow(entry) + + return ConfirmRepairFlow() diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index f9c721400610..bfe29e9c6122 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.sensor import ( SensorEntity, SensorStateClass, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, @@ -26,9 +27,12 @@ from homeassistant.const import ( ) from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity import get_device_class -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddConfigEntryEntitiesCallback, + AddEntitiesCallback, +) from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.reload import async_setup_reload_service @@ -78,6 +82,32 @@ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend( ) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Initialize min/max/mean config entry.""" + registry = er.async_get(hass) + entity_ids = er.async_validate_entity_ids( + registry, config_entry.options[CONF_ENTITY_IDS] + ) + sensor_type = config_entry.options[CONF_TYPE] + round_digits = int(config_entry.options[CONF_ROUND_DIGITS]) + + async_add_entities( + [ + MinMaxSensor( + entity_ids, + config_entry.title, + sensor_type, + round_digits, + config_entry.entry_id, + ) + ] + ) + + async def yaml_deprecation_notice(hass: HomeAssistant, config: ConfigType) -> None: """Raise repair issue for YAML configuration deprecation.""" platform_config = config.copy()