Add issue asking users to disable ESPHome assist_in_progress sensor (#125805)
* Add issue asking users to disable ESPHome assist_in_progress binary sensor * Include integration name in title and description * Add repair flow * Improve test coveragepull/126406/head
parent
9bfc2eaeb9
commit
6cd99e4ed4
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"domain": "assist_pipeline",
|
||||
"name": "Assist pipeline",
|
||||
"after_dependencies": ["repairs"],
|
||||
"codeowners": ["@balloob", "@synesthesiam"],
|
||||
"dependencies": ["conversation", "stt", "tts", "wake_word"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/assist_pipeline",
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
"""Repairs implementation for the cloud integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.assist_satellite import DOMAIN as ASSIST_SATELLITE_DOMAIN
|
||||
from homeassistant.components.repairs import RepairsFlow
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
REQUIRED_KEYS = ("entity_id", "entity_uuid", "integration_name")
|
||||
|
||||
|
||||
class AssistInProgressDeprecatedRepairFlow(RepairsFlow):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
def __init__(self, data: dict[str, str | int | float | None] | None) -> None:
|
||||
"""Initialize."""
|
||||
if not data or any(key not in data for key in REQUIRED_KEYS):
|
||||
raise ValueError("Missing data")
|
||||
self._data = data
|
||||
|
||||
async def async_step_init(self, _: None = None) -> FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
return await self.async_step_confirm_disable_entity()
|
||||
|
||||
async def async_step_confirm_disable_entity(
|
||||
self,
|
||||
user_input: dict[str, str] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
entity_registry = er.async_get(self.hass)
|
||||
entity_entry = entity_registry.async_get(
|
||||
cast(str, self._data["entity_uuid"])
|
||||
)
|
||||
if entity_entry:
|
||||
entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER
|
||||
)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
description_placeholders: dict[str, str] = {
|
||||
"assist_satellite_domain": ASSIST_SATELLITE_DOMAIN,
|
||||
"entity_id": cast(str, self._data["entity_id"]),
|
||||
"integration_name": cast(str, self._data["integration_name"]),
|
||||
}
|
||||
return self.async_show_form(
|
||||
step_id="confirm_disable_entity",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders=description_placeholders,
|
||||
)
|
|
@ -21,5 +21,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"assist_in_progress_deprecated": {
|
||||
"title": "{integration_name} assist in progress binary sensors are deprecated",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm_disable_entity": {
|
||||
"description": "The {integration_name} assist in progress binary sensor `{entity_id}` is deprecated.\n\nMigrate your configuration to use the corresponding `{assist_satellite_domain}` entity and then click SUBMIT to disable the assist in progress binary sensor and fix this issue."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityInfo
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -10,9 +12,11 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import EsphomeAssistEntity, EsphomeEntity, platform_async_setup_entry
|
||||
from .entry_data import ESPHomeConfigEntry
|
||||
|
||||
|
@ -79,6 +83,40 @@ class EsphomeAssistInProgressBinarySensor(EsphomeAssistEntity, BinarySensorEntit
|
|||
translation_key="assist_in_progress",
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Create issue."""
|
||||
await super().async_added_to_hass()
|
||||
if TYPE_CHECKING:
|
||||
assert self.registry_entry is not None
|
||||
ir.async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"assist_in_progress_deprecated_{self.registry_entry.id}",
|
||||
breaks_in_ha_version="2025.3",
|
||||
data={
|
||||
"entity_id": self.entity_id,
|
||||
"entity_uuid": self.registry_entry.id,
|
||||
"integration_name": "ESPHome",
|
||||
},
|
||||
is_fixable=True,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="assist_in_progress_deprecated",
|
||||
translation_placeholders={
|
||||
"integration_name": "ESPHome",
|
||||
},
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Remove issue."""
|
||||
await super().async_will_remove_from_hass()
|
||||
if TYPE_CHECKING:
|
||||
assert self.registry_entry is not None
|
||||
ir.async_delete_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"assist_in_progress_deprecated_{self.registry_entry.id}",
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if the binary sensor is on."""
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
"""Repairs implementation for the cloud integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.assist_pipeline.repair_flows import (
|
||||
AssistInProgressDeprecatedRepairFlow,
|
||||
)
|
||||
from homeassistant.components.repairs import RepairsFlow
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, str | int | float | None] | None,
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
if issue_id.startswith("assist_in_progress_deprecated"):
|
||||
return AssistInProgressDeprecatedRepairFlow(data)
|
||||
# If ESPHome adds confirm-only repairs in the future, this should be changed
|
||||
# to return a ConfirmRepairFlow instead of raising a ValueError
|
||||
raise ValueError(f"unknown repair {issue_id}")
|
|
@ -93,6 +93,16 @@
|
|||
}
|
||||
},
|
||||
"issues": {
|
||||
"assist_in_progress_deprecated": {
|
||||
"title": "[%key:component::assist_pipeline::issues::assist_in_progress_deprecated::title%]",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm_disable_entity": {
|
||||
"description": "[%key:component::assist_pipeline::issues::assist_in_progress_deprecated::fix_flow::step::confirm_disable_entity::description%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ble_firmware_outdated": {
|
||||
"title": "Update {name} with ESPHome {version} or later",
|
||||
"description": "To improve Bluetooth reliability and performance, we highly recommend updating {name} with ESPHome {version} or later. When updating the device from ESPHome earlier than 2022.12.0, it is recommended to use a serial cable instead of an over-the-air update to take advantage of the new partition scheme."
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
"""Test repair flows."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.assist_pipeline.repair_flows import (
|
||||
AssistInProgressDeprecatedRepairFlow,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [None, {}, {"entity_id": "blah", "entity_uuid": "12345"}]
|
||||
)
|
||||
def test_assist_in_progress_deprecated_flow_requires_data(data: dict | None) -> None:
|
||||
"""Test AssistInProgressDeprecatedRepairFlow requires data."""
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
AssistInProgressDeprecatedRepairFlow(data)
|
|
@ -1,6 +1,7 @@
|
|||
"""Test ESPHome binary sensors."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
from aioesphomeapi import (
|
||||
APIClient,
|
||||
|
@ -12,14 +13,17 @@ from aioesphomeapi import (
|
|||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.esphome import DomainData
|
||||
from homeassistant.components.esphome import DOMAIN, DomainData
|
||||
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .conftest import MockESPHomeDevice
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
|
@ -49,6 +53,7 @@ async def test_assist_in_progress(
|
|||
async def test_assist_in_progress_disabled_by_default(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
mock_voice_assistant_v1_entry,
|
||||
) -> None:
|
||||
"""Test assist in progress binary sensor is added disabled."""
|
||||
|
@ -59,6 +64,116 @@ async def test_assist_in_progress_disabled_by_default(
|
|||
assert entity_entry.disabled
|
||||
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
# Test no issue for disabled entity
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_assist_in_progress_issue(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
mock_voice_assistant_v1_entry,
|
||||
) -> None:
|
||||
"""Test assist in progress binary sensor."""
|
||||
|
||||
state = hass.states.get("binary_sensor.test_assist_in_progress")
|
||||
assert state is not None
|
||||
|
||||
entity_entry = entity_registry.async_get("binary_sensor.test_assist_in_progress")
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"assist_in_progress_deprecated_{entity_entry.id}"
|
||||
)
|
||||
assert issue is not None
|
||||
|
||||
# Test issue goes away after disabling the entity
|
||||
entity_registry.async_update_entity(
|
||||
"binary_sensor.test_assist_in_progress",
|
||||
disabled_by=er.RegistryEntryDisabler.USER,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"assist_in_progress_deprecated_{entity_entry.id}"
|
||||
)
|
||||
assert issue is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_assist_in_progress_repair_flow(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
mock_voice_assistant_v1_entry,
|
||||
) -> None:
|
||||
"""Test assist in progress binary sensor deprecation issue flow."""
|
||||
|
||||
state = hass.states.get("binary_sensor.test_assist_in_progress")
|
||||
assert state is not None
|
||||
|
||||
entity_entry = entity_registry.async_get("binary_sensor.test_assist_in_progress")
|
||||
assert entity_entry.disabled_by is None
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"assist_in_progress_deprecated_{entity_entry.id}"
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.data == {
|
||||
"entity_id": "binary_sensor.test_assist_in_progress",
|
||||
"entity_uuid": entity_entry.id,
|
||||
"integration_name": "ESPHome",
|
||||
}
|
||||
assert issue.translation_key == "assist_in_progress_deprecated"
|
||||
assert issue.translation_placeholders == {"integration_name": "ESPHome"}
|
||||
|
||||
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
resp = await client.post(
|
||||
"/api/repairs/issues/fix",
|
||||
json={"handler": DOMAIN, "issue_id": issue.issue_id},
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data == {
|
||||
"data_schema": [],
|
||||
"description_placeholders": {
|
||||
"assist_satellite_domain": "assist_satellite",
|
||||
"entity_id": "binary_sensor.test_assist_in_progress",
|
||||
"integration_name": "ESPHome",
|
||||
},
|
||||
"errors": None,
|
||||
"flow_id": flow_id,
|
||||
"handler": DOMAIN,
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
"step_id": "confirm_disable_entity",
|
||||
"type": "form",
|
||||
}
|
||||
|
||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data == {
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
"flow_id": flow_id,
|
||||
"handler": DOMAIN,
|
||||
"type": "create_entry",
|
||||
}
|
||||
|
||||
# Test the entity is disabled
|
||||
entity_entry = entity_registry.async_get("binary_sensor.test_assist_in_progress")
|
||||
assert entity_entry.disabled_by is er.RegistryEntryDisabler.USER
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"binary_state", [(True, STATE_ON), (False, STATE_OFF), (None, STATE_UNKNOWN)]
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
"""Test ESPHome binary sensors."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.esphome import repairs
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_create_fix_flow_raises_on_unknown_issue_id(hass: HomeAssistant) -> None:
|
||||
"""Test reate_fix_flow raises on unknown issue_id."""
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await repairs.async_create_fix_flow(hass, "no_such_issue", None)
|
Loading…
Reference in New Issue