Deprecate the UniFi Protect Detected Object sensor (#83480)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Christopher Bailey 2022-12-26 14:32:05 -05:00 committed by GitHub
parent b96330df03
commit 3aa759fc49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 288 additions and 5 deletions

View File

@ -27,6 +27,7 @@ from .const import (
from .data import ProtectData, async_ufp_instance_for_config_entry_ids
from .discovery import async_start_discovery
from .migrate import async_migrate_data
from .repairs import async_create_repairs
from .services import async_cleanup_services, async_setup_services
from .utils import (
_async_unifi_mac_from_hass,
@ -121,6 +122,7 @@ async def _async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, data_service: ProtectData
) -> None:
await async_migrate_data(hass, entry, data_service.api)
await async_create_repairs(hass, entry, data_service.api)
await data_service.async_setup()
if not data_service.last_update_success:

View File

@ -2,20 +2,93 @@
from __future__ import annotations
from typing import cast
from functools import partial
from itertools import chain
import logging
from typing import Any, cast
from pyunifiprotect import ProtectApiClient
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.automation import (
EVENT_AUTOMATION_RELOADED,
automations_with_entity,
)
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.components.script import scripts_with_entity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.issue_registry import async_get as async_get_issue_registry
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_get as async_get_issue_registry,
)
from .const import CONF_ALLOW_EA
from .const import CONF_ALLOW_EA, DOMAIN
from .utils import async_create_api_client
_LOGGER = logging.getLogger(__name__)
async def async_create_repairs(
hass: HomeAssistant, entry: ConfigEntry, protect: ProtectApiClient
) -> None:
"""Create any additional repairs for deprecations."""
await _deprecate_smart_sensor(hass, entry, protect)
entry.async_on_unload(
hass.bus.async_listen(
EVENT_AUTOMATION_RELOADED,
partial(_deprecate_smart_sensor, hass, entry, protect),
)
)
async def _deprecate_smart_sensor(
hass: HomeAssistant,
entry: ConfigEntry,
protect: ProtectApiClient,
*args: Any,
**kwargs: Any,
) -> None:
entity_registry = er.async_get(hass)
automations: dict[str, list[str]] = {}
scripts: dict[str, list[str]] = {}
for entity in er.async_entries_for_config_entry(entity_registry, entry.entry_id):
if (
entity.domain == Platform.SENSOR
and entity.disabled_by is None
and "detected_object" in entity.unique_id
):
entity_automations = automations_with_entity(hass, entity.entity_id)
entity_scripts = scripts_with_entity(hass, entity.entity_id)
if entity_automations:
automations[entity.entity_id] = entity_automations
if entity_scripts:
scripts[entity.entity_id] = entity_scripts
if automations or scripts:
items = sorted(
set(
chain.from_iterable(list(automations.values()) + list(scripts.values()))
)
)
ir.async_create_issue(
hass,
DOMAIN,
"deprecate_smart_sensor",
is_fixable=False,
breaks_in_ha_version="2023.3.0",
severity=IssueSeverity.WARNING,
translation_key="deprecate_smart_sensor",
translation_placeholders={"items": "* `" + "`\n* `".join(items) + "`\n"},
)
else:
_LOGGER.debug("No found usages of Detected Object sensor")
ir.async_delete_issue(hass, DOMAIN, "deprecate_smart_sensor")
class EAConfirm(RepairsFlow):
"""Handler for an issue fixing flow."""

View File

@ -76,6 +76,10 @@
"title": "Setup error using Early Access version",
"description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}"
},
"deprecate_smart_sensor": {
"title": "Smart Detection Sensor Deprecated",
"description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type.\n\nBelow are the detected automations or scripts that use one or more of the deprecated entities:\n{items}\nThe above list may be incomplete and it does not include any template usages inside of dashboards. Please update any templates, automations or scripts accordingly."
},
"deprecated_service_set_doorbell_message": {
"title": "set_doorbell_message is Deprecated",
"fix_flow": {

View File

@ -51,6 +51,10 @@
}
},
"issues": {
"deprecate_smart_sensor": {
"description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type.\n\nBelow are the detected automations or scripts that use one or more of the deprecated entities:\n{items}\nThe above list may be incomplete and it does not include any template usages inside of dashboards. Please update any templates, automations or scripts accordingly.",
"title": "Smart Detection Sensor Deprecated"
},
"deprecated_service_set_doorbell_message": {
"fix_flow": {
"step": {

View File

@ -4,10 +4,11 @@ from __future__ import annotations
from copy import copy
from http import HTTPStatus
from unittest.mock import Mock
from unittest.mock import Mock, patch
from pyunifiprotect.data import Version
from pyunifiprotect.data import Camera, Version
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
from homeassistant.components.repairs.issue_handler import (
async_process_repairs_platforms,
)
@ -15,8 +16,12 @@ from homeassistant.components.repairs.websocket_api import (
RepairsFlowIndexView,
RepairsFlowResourceView,
)
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
from homeassistant.components.unifiprotect.const import DOMAIN
from homeassistant.const import SERVICE_RELOAD, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from .utils import MockUFPFixture, init_entry
@ -124,3 +129,198 @@ async def test_ea_warning_fix(
data = await resp.json()
assert data["type"] == "create_entry"
async def test_deprecate_smart_default(
hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera
):
"""Test Deprecate Sensor repair does not exist by default (new installs)."""
await init_entry(hass, ufp, [doorbell])
await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is None
async def test_deprecate_smart_no_automations(
hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera
):
"""Test Deprecate Sensor repair exists for existing installs."""
registry = er.async_get(hass)
registry.async_get_or_create(
Platform.SENSOR,
DOMAIN,
f"{doorbell.mac}_detected_object",
config_entry=ufp.entry,
)
await init_entry(hass, ufp, [doorbell])
await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is None
async def _load_automation(hass: HomeAssistant, entity_id: str):
assert await async_setup_component(
hass,
AUTOMATION_DOMAIN,
{
AUTOMATION_DOMAIN: [
{
"alias": "test1",
"trigger": [
{"platform": "state", "entity_id": entity_id},
{
"platform": "event",
"event_type": "state_changed",
"event_data": {"entity_id": entity_id},
},
],
"condition": {
"condition": "state",
"entity_id": entity_id,
"state": "on",
},
"action": [
{
"service": "test.script",
"data": {"entity_id": entity_id},
},
],
},
]
},
)
async def test_deprecate_smart_automation(
hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera
):
"""Test Deprecate Sensor repair exists for existing installs."""
registry = er.async_get(hass)
entry = registry.async_get_or_create(
Platform.SENSOR,
DOMAIN,
f"{doorbell.mac}_detected_object",
config_entry=ufp.entry,
)
await _load_automation(hass, entry.entity_id)
await init_entry(hass, ufp, [doorbell])
await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is not None
with patch(
"homeassistant.config.load_yaml_config_file",
autospec=True,
return_value={AUTOMATION_DOMAIN: []},
):
await hass.services.async_call(AUTOMATION_DOMAIN, SERVICE_RELOAD, blocking=True)
await hass.async_block_till_done()
await ws_client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is None
async def _load_script(hass: HomeAssistant, entity_id: str):
assert await async_setup_component(
hass,
SCRIPT_DOMAIN,
{
SCRIPT_DOMAIN: {
"test": {
"sequence": {
"service": "test.script",
"data": {"entity_id": entity_id},
}
}
},
},
)
async def test_deprecate_smart_script(
hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera
):
"""Test Deprecate Sensor repair exists for existing installs."""
registry = er.async_get(hass)
entry = registry.async_get_or_create(
Platform.SENSOR,
DOMAIN,
f"{doorbell.mac}_detected_object",
config_entry=ufp.entry,
)
await _load_script(hass, entry.entity_id)
await init_entry(hass, ufp, [doorbell])
await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is not None
with patch(
"homeassistant.config.load_yaml_config_file",
autospec=True,
return_value={SCRIPT_DOMAIN: {}},
):
await hass.services.async_call(SCRIPT_DOMAIN, SERVICE_RELOAD, blocking=True)
await hass.config_entries.async_reload(ufp.entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
issue = None
for i in msg["result"]["issues"]:
if i["issue_id"] == "deprecate_smart_sensor":
issue = i
assert issue is None