-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Deprecate min_max and migrate to group sensor #167718
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
base: dev
Are you sure you want to change the base?
Changes from 30 commits
564280c
5bbfe69
7288d19
6e92ba2
26d8dfb
d4fca37
49ce8ed
f821952
d25f3c6
f006283
44b0717
7f1846b
6d4f5a4
d037f33
983df39
787929f
46a05e4
379c0b1
3f69014
00e8a6c
9fd0f99
e131b21
5079709
80733a2
f4bccbb
d8b23df
ee9326d
2e1c4e0
fbda661
7c6ff3f
30176c6
9634953
aad7b6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| """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.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.""" | ||
|
gjohansson-ST marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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.""" | ||
| entity_reg = er.async_get(self.hass) | ||
| old_entity = entity_reg.async_get_entity_id( | ||
| SENSOR_DOMAIN, DOMAIN, self.entry.entry_id | ||
| ) | ||
| if not old_entity: | ||
| return self.async_abort(reason="entity_not_found") | ||
|
|
||
|
emontnemery marked this conversation as resolved.
|
||
| 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 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd think we should migrate to group default ( |
||
| config[CONF_GROUP_TYPE] = SENSOR_DOMAIN | ||
|
|
||
| new_config_entry = ConfigEntry( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't we use a normal config flow on the group domain to create the new entry, instead of adding it directly?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be a bit difficult as we need to unload
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sort of thing would normally be in the group integration's config flow with a custom initial step for the |
||
| 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, | ||
| ) | ||
|
|
||
| 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, | ||
|
gjohansson-ST marked this conversation as resolved.
gjohansson-ST marked this conversation as resolved.
|
||
| 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) | ||
|
|
||
| return self.async_create_entry(data={}) | ||
|
Comment on lines
+85
to
+90
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Repairing the issue does that?! |
||
|
|
||
| entity_info = entity_reg.async_get(old_entity) | ||
| if TYPE_CHECKING: | ||
| assert entity_info | ||
| title = er.async_get_full_entity_name(self.hass, entity_info) | ||
|
|
||
| return self.async_show_form( | ||
| step_id="migrate", | ||
| data_schema=vol.Schema({}), | ||
| description_placeholders={"title": title}, | ||
| ) | ||
|
|
||
|
|
||
| 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) | ||
| if TYPE_CHECKING: | ||
| assert entry | ||
| return MigrateToGroupSensorFlow(entry) | ||
|
|
||
| return ConfirmRepairFlow() | ||
|
Comment on lines
+108
to
+117
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,15 @@ | |
| from __future__ import annotations | ||
|
|
||
| from datetime import datetime | ||
| import hashlib | ||
| import json | ||
| import logging | ||
| import statistics | ||
| from typing import Any | ||
|
|
||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.components.group import CONF_ENTITIES | ||
| from homeassistant.components.sensor import ( | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA, | ||
| SensorDeviceClass, | ||
|
|
@@ -20,6 +23,7 @@ | |
| ATTR_ENTITY_ID, | ||
| ATTR_UNIT_OF_MEASUREMENT, | ||
| CONF_NAME, | ||
| CONF_PLATFORM, | ||
| CONF_TYPE, | ||
| CONF_UNIQUE_ID, | ||
| STATE_UNAVAILABLE, | ||
|
|
@@ -34,8 +38,10 @@ | |
| 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 | ||
| from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType | ||
| from homeassistant.util import yaml as yaml_util | ||
|
|
||
| from . import PLATFORMS | ||
| from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN | ||
|
|
@@ -53,6 +59,7 @@ | |
| ATTR_RANGE = "range" | ||
| ATTR_SUM = "sum" | ||
|
|
||
|
|
||
| ICON = "mdi:calculator" | ||
|
|
||
| SENSOR_TYPES = { | ||
|
|
@@ -105,6 +112,36 @@ async def async_setup_entry( | |
| ) | ||
|
|
||
|
|
||
| async def yaml_deprecation_notice(hass: HomeAssistant, config: ConfigType) -> None: | ||
| """Raise repair issue for YAML configuration deprecation.""" | ||
| platform_config = config.copy() | ||
| platform_config[CONF_ENTITIES] = platform_config.pop(CONF_ENTITY_IDS) | ||
| platform_config.pop(CONF_ROUND_DIGITS) | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| platform_config.pop(CONF_PLATFORM) | ||
| if CONF_NAME not in platform_config: | ||
| platform_config[CONF_NAME] = f"{platform_config[CONF_TYPE]} sensor".capitalize() | ||
| yaml_config = yaml_util.dump(platform_config) | ||
| yaml_config = yaml_config.replace("\n", "\n ") | ||
| yaml_config = "```yaml\nsensor:\n - platform: group\n " + yaml_config + "\n```" | ||
|
Comment on lines
+117
to
+125
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above |
||
|
|
||
| def make_hash(config: dict[str, Any]) -> str: | ||
| d = hashlib.sha1(json.dumps(config, sort_keys=True).encode()) | ||
| return d.hexdigest() | ||
|
|
||
| issue_id = f"yaml_deprecated-{make_hash(platform_config)}" | ||
| async_create_issue( | ||
| hass, | ||
| DOMAIN, | ||
| issue_id, | ||
| breaks_in_ha_version="2026.12.0", | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| is_fixable=False, | ||
| severity=IssueSeverity.WARNING, | ||
| learn_more_url="https://www.home-assistant.io/integrations/group/", | ||
| translation_key="yaml_deprecated", | ||
| translation_placeholders={"yaml_config": yaml_config}, | ||
| ) | ||
|
gjohansson-ST marked this conversation as resolved.
gjohansson-ST marked this conversation as resolved.
gjohansson-ST marked this conversation as resolved.
gjohansson-ST marked this conversation as resolved.
|
||
|
|
||
|
|
||
| async def async_setup_platform( | ||
| hass: HomeAssistant, | ||
| config: ConfigType, | ||
|
|
@@ -119,6 +156,7 @@ async def async_setup_platform( | |
| unique_id = config.get(CONF_UNIQUE_ID) | ||
|
|
||
| await async_setup_reload_service(hass, DOMAIN, PLATFORMS) | ||
| await yaml_deprecation_notice(hass, config) | ||
|
|
||
| async_add_entities( | ||
| [MinMaxSensor(entity_ids, name, sensor_type, round_digits, unique_id)] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,47 @@ | ||
| { | ||
| "config": { | ||
| "abort": { | ||
| "migrated_to_groups": "The Min/Max helper has been migrated to use Group sensors. Please use the Group helper instead." | ||
| }, | ||
| "step": { | ||
| "user": { | ||
| "data": { | ||
| "entity_ids": "Input entities", | ||
| "name": "[%key:common::config_flow::data::name%]", | ||
| "round_digits": "Precision", | ||
| "type": "Statistic characteristic" | ||
| }, | ||
| "data_description": { | ||
| "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum." | ||
| }, | ||
| "description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.", | ||
| "description": "Min/Max helper has been deprecated, please use the Group helper instead.", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this visible considering we immediately abort the flow?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I will remove it
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have to keep it as the translation script requires it. It will anyhow be removed in 6 months |
||
| "title": "[%key:component::min_max::title%]" | ||
| } | ||
| } | ||
| }, | ||
| "issues": { | ||
| "migrate_to_group_sensor": { | ||
| "fix_flow": { | ||
| "abort": { | ||
| "entity_not_found": "Entity could not be found, please restart repairing the issue.", | ||
|
gjohansson-ST marked this conversation as resolved.
Outdated
|
||
| "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." | ||
| }, | ||
| "step": { | ||
| "migrate": { | ||
| "description": "The Min/Max helper has been deprecated and {title} will be migrated to a Group sensor when you click submit to fix this repair.", | ||
| "title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]" | ||
| } | ||
| } | ||
| }, | ||
| "title": "Min/Max helper has been deprecated" | ||
| }, | ||
| "yaml_deprecated": { | ||
| "description": "The Min/Max helper has been deprecated and you should use Group sensors instead.\n\nReplace your Min/Max YAML configuration with this converted configuration:\n{yaml_config}\n\nOnce you have replaced your YAML configuration, restart Home Assistant to use the Group helper instead.\n\nThe Group helper has more configuration possibilities. Refer to the documentation by clicking Learn More.", | ||
| "title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]" | ||
| } | ||
| }, | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| "options": { | ||
| "step": { | ||
| "init": { | ||
| "data": { | ||
| "entity_ids": "[%key:component::min_max::config::step::user::data::entity_ids%]", | ||
| "round_digits": "[%key:component::min_max::config::step::user::data::round_digits%]", | ||
| "type": "[%key:component::min_max::config::step::user::data::type%]" | ||
| "entity_ids": "Input entities", | ||
| "round_digits": "Precision", | ||
| "type": "Statistic characteristic" | ||
| }, | ||
| "data_description": { | ||
| "round_digits": "[%key:component::min_max::config::step::user::data_description::round_digits%]" | ||
| "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum." | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please move the entity registry changes to a separate PR with a clear motivation for the change as well as updated tests.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #171773 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1932,7 +1932,7 @@ def async_update_entity_platform( | |
| """ | ||
| if ( | ||
| state := self.hass.states.get(entity_id) | ||
| ) is not None and state.state != STATE_UNKNOWN: | ||
| ) is not None and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE): | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| raise ValueError("Only entities that haven't been loaded can be migrated") | ||
|
Comment on lines
1933
to
1936
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should fix this in that case in a separate PR since
Comment on lines
1933
to
1936
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already commented on this above, but I think another PR needs to fix that and look on the integration rather than an entity state (as
Comment on lines
1933
to
1936
|
||
|
|
||
| old = self.entities[entity_id] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.