Update doorbird error notification to be a repair flow (#122987)
This commit is contained in:
parent
352f0953f3
commit
8375b58eac
5 changed files with 153 additions and 15 deletions
|
@ -3,11 +3,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
import logging
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
from doorbirdpy import DoorBird
|
from doorbirdpy import DoorBird
|
||||||
|
|
||||||
from homeassistant.components import persistent_notification
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
|
@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
@ -30,6 +31,8 @@ CONF_CUSTOM_URL = "hass_url_override"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the DoorBird component."""
|
"""Set up the DoorBird component."""
|
||||||
|
@ -68,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DoorBirdConfigEntry) ->
|
||||||
door_bird_data = DoorBirdData(door_station, info, event_entity_ids)
|
door_bird_data = DoorBirdData(door_station, info, event_entity_ids)
|
||||||
door_station.update_events(events)
|
door_station.update_events(events)
|
||||||
# Subscribe to doorbell or motion events
|
# Subscribe to doorbell or motion events
|
||||||
if not await _async_register_events(hass, door_station):
|
if not await _async_register_events(hass, door_station, entry):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
entry.async_on_unload(entry.add_update_listener(_update_listener))
|
entry.async_on_unload(entry.add_update_listener(_update_listener))
|
||||||
|
@ -84,24 +87,30 @@ async def async_unload_entry(hass: HomeAssistant, entry: DoorBirdConfigEntry) ->
|
||||||
|
|
||||||
|
|
||||||
async def _async_register_events(
|
async def _async_register_events(
|
||||||
hass: HomeAssistant, door_station: ConfiguredDoorBird
|
hass: HomeAssistant, door_station: ConfiguredDoorBird, entry: DoorBirdConfigEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Register events on device."""
|
"""Register events on device."""
|
||||||
|
issue_id = f"doorbird_schedule_error_{entry.entry_id}"
|
||||||
try:
|
try:
|
||||||
await door_station.async_register_events()
|
await door_station.async_register_events()
|
||||||
except ClientResponseError:
|
except ClientResponseError as ex:
|
||||||
persistent_notification.async_create(
|
ir.async_create_issue(
|
||||||
hass,
|
hass,
|
||||||
(
|
DOMAIN,
|
||||||
"Doorbird configuration failed. Please verify that API "
|
issue_id,
|
||||||
"Operator permission is enabled for the Doorbird user. "
|
severity=ir.IssueSeverity.ERROR,
|
||||||
"A restart will be required once permissions have been "
|
translation_key="error_registering_events",
|
||||||
"verified."
|
data={"entry_id": entry.entry_id},
|
||||||
),
|
is_fixable=True,
|
||||||
title="Doorbird Configuration Failure",
|
translation_placeholders={
|
||||||
notification_id="doorbird_schedule_error",
|
"error": str(ex),
|
||||||
|
"name": door_station.name or entry.data[CONF_NAME],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
_LOGGER.debug("Error registering DoorBird events", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
ir.async_delete_issue(hass, DOMAIN, issue_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -111,4 +120,4 @@ async def _update_listener(hass: HomeAssistant, entry: DoorBirdConfigEntry) -> N
|
||||||
door_station = entry.runtime_data.door_station
|
door_station = entry.runtime_data.door_station
|
||||||
door_station.update_events(entry.options[CONF_EVENTS])
|
door_station.update_events(entry.options[CONF_EVENTS])
|
||||||
# Subscribe to doorbell or motion events
|
# Subscribe to doorbell or motion events
|
||||||
await _async_register_events(hass, door_station)
|
await _async_register_events(hass, door_station, entry)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "DoorBird",
|
"name": "DoorBird",
|
||||||
"codeowners": ["@oblogic7", "@bdraco", "@flacjacket"],
|
"codeowners": ["@oblogic7", "@bdraco", "@flacjacket"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http", "repairs"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/doorbird",
|
"documentation": "https://www.home-assistant.io/integrations/doorbird",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["doorbirdpy"],
|
"loggers": ["doorbirdpy"],
|
||||||
|
|
55
homeassistant/components/doorbird/repairs.py
Normal file
55
homeassistant/components/doorbird/repairs.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""Repairs for DoorBird."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components.repairs import RepairsFlow
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
|
||||||
|
|
||||||
|
class DoorBirdReloadConfirmRepairFlow(RepairsFlow):
|
||||||
|
"""Handler to show doorbird error and reload."""
|
||||||
|
|
||||||
|
def __init__(self, entry_id: str) -> None:
|
||||||
|
"""Initialize the flow."""
|
||||||
|
self.entry_id = entry_id
|
||||||
|
|
||||||
|
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_confirm()
|
||||||
|
|
||||||
|
async def async_step_confirm(
|
||||||
|
self, user_input: dict[str, str] | None = None
|
||||||
|
) -> data_entry_flow.FlowResult:
|
||||||
|
"""Handle the confirm step of a fix flow."""
|
||||||
|
if user_input is not None:
|
||||||
|
self.hass.config_entries.async_schedule_reload(self.entry_id)
|
||||||
|
return self.async_create_entry(data={})
|
||||||
|
|
||||||
|
issue_registry = ir.async_get(self.hass)
|
||||||
|
description_placeholders = None
|
||||||
|
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
|
||||||
|
description_placeholders = issue.translation_placeholders
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="confirm",
|
||||||
|
data_schema=vol.Schema({}),
|
||||||
|
description_placeholders=description_placeholders,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_create_fix_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
issue_id: str,
|
||||||
|
data: dict[str, str | int | float | None] | None,
|
||||||
|
) -> RepairsFlow:
|
||||||
|
"""Create flow."""
|
||||||
|
assert data is not None
|
||||||
|
entry_id = data["entry_id"]
|
||||||
|
assert isinstance(entry_id, str)
|
||||||
|
return DoorBirdReloadConfirmRepairFlow(entry_id=entry_id)
|
|
@ -11,6 +11,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"issues": {
|
||||||
|
"error_registering_events": {
|
||||||
|
"title": "DoorBird {name} configuration failure",
|
||||||
|
"fix_flow": {
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "[%key:component::doorbird::issues::error_registering_events::title%]",
|
||||||
|
"description": "Configuring DoorBird {name} failed with error: `{error}`. Please enable the API Operator permission for the DoorBird user and continue to reload the integration."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
|
61
tests/components/doorbird/test_repairs.py
Normal file
61
tests/components/doorbird/test_repairs.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
"""Test repairs for doorbird."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from homeassistant.components.doorbird.const import DOMAIN
|
||||||
|
from homeassistant.components.repairs.issue_handler import (
|
||||||
|
async_process_repairs_platforms,
|
||||||
|
)
|
||||||
|
from homeassistant.components.repairs.websocket_api import (
|
||||||
|
RepairsFlowIndexView,
|
||||||
|
RepairsFlowResourceView,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from . import mock_not_found_exception
|
||||||
|
from .conftest import DoorbirdMockerType
|
||||||
|
|
||||||
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
|
|
||||||
|
async def test_change_schedule_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
doorbird_mocker: DoorbirdMockerType,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
) -> None:
|
||||||
|
"""Test a doorbird when change_schedule fails."""
|
||||||
|
assert await async_setup_component(hass, "repairs", {})
|
||||||
|
doorbird_entry = await doorbird_mocker(
|
||||||
|
favorites_side_effect=mock_not_found_exception()
|
||||||
|
)
|
||||||
|
assert doorbird_entry.entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
issue_reg = ir.async_get(hass)
|
||||||
|
assert len(issue_reg.issues) == 1
|
||||||
|
issue = list(issue_reg.issues.values())[0]
|
||||||
|
issue_id = issue.issue_id
|
||||||
|
assert issue.domain == DOMAIN
|
||||||
|
|
||||||
|
await async_process_repairs_platforms(hass)
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
url = RepairsFlowIndexView.url
|
||||||
|
resp = await client.post(url, json={"handler": DOMAIN, "issue_id": issue_id})
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
placeholders = data["description_placeholders"]
|
||||||
|
assert "404" in placeholders["error"]
|
||||||
|
assert data["step_id"] == "confirm"
|
||||||
|
|
||||||
|
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||||
|
resp = await client.post(url)
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
assert data["type"] == "create_entry"
|
Loading…
Add table
Reference in a new issue