Skip to content

Commit 14f0aa3

Browse files
Repairs (#2293)
* WIP * WIP * WIP * WIP * Remove print statement * Change logging * Lint * Error handling * Error handling * Error handling for entities * Remove issues if device deleted
1 parent dfcba9e commit 14f0aa3

File tree

5 files changed

+153
-19
lines changed

5 files changed

+153
-19
lines changed

custom_components/battery_notes/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from homeassistant.core import HomeAssistant, callback
1818
from homeassistant.helpers import config_validation as cv
1919
from homeassistant.helpers import entity_registry as er
20+
from homeassistant.helpers import issue_registry as ir
2021
from homeassistant.helpers.typing import ConfigType
2122
from homeassistant.util import dt as dt_util
2223

@@ -163,6 +164,9 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
163164
async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
164165
"""Device removed, tidy up store."""
165166

167+
# Remove any issues raised
168+
ir.async_delete_issue(hass, DOMAIN, f"missing_device_{config_entry.entry_id}")
169+
166170
if "device_id" not in config_entry.data:
167171
return
168172

custom_components/battery_notes/config_flow.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -448,20 +448,23 @@ async def async_step_init(
448448
device_registry = dr.async_get(self.hass)
449449
device_entry = device_registry.async_get(self.source_device_id)
450450

451-
_LOGGER.debug(
452-
"Looking up device %s %s %s %s",
453-
device_entry.manufacturer,
454-
device_entry.model,
455-
get_device_model_id(device_entry) or "",
456-
device_entry.hw_version,
457-
)
451+
if not device_entry:
452+
errors["base"] = "orphaned_battery_note"
453+
else:
454+
_LOGGER.debug(
455+
"Looking up device %s %s %s %s",
456+
device_entry.manufacturer,
457+
device_entry.model,
458+
get_device_model_id(device_entry) or "",
459+
device_entry.hw_version,
460+
)
458461

459-
self.model_info = ModelInfo(
460-
device_entry.manufacturer,
461-
device_entry.model,
462-
get_device_model_id(device_entry),
463-
device_entry.hw_version,
464-
)
462+
self.model_info = ModelInfo(
463+
device_entry.manufacturer,
464+
device_entry.model,
465+
get_device_model_id(device_entry),
466+
device_entry.hw_version,
467+
)
465468

466469
schema = self.build_options_schema()
467470
if user_input is not None:
@@ -492,6 +495,8 @@ async def save_options(
492495
schema: vol.Schema,
493496
) -> dict:
494497
"""Save options, and return errors when validation fails."""
498+
errors = {}
499+
495500
device_registry = dr.async_get(self.hass)
496501
device_entry = device_registry.async_get(
497502
self.config_entry.data.get(CONF_DEVICE_ID)
@@ -502,6 +507,13 @@ async def save_options(
502507
if source_entity_id:
503508
entity_registry = er.async_get(self.hass)
504509
entity_entry = entity_registry.async_get(source_entity_id)
510+
if not entity_entry:
511+
errors["base"] = "orphaned_battery_note"
512+
return errors
513+
else:
514+
if not device_entry:
515+
errors["base"] = "orphaned_battery_note"
516+
return errors
505517

506518
if CONF_NAME in user_input:
507519
title = user_input.get(CONF_NAME)

custom_components/battery_notes/device.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@
1313
PERCENTAGE,
1414
)
1515
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
16-
from homeassistant.helpers import (
17-
device_registry as dr,
18-
)
19-
from homeassistant.helpers import (
20-
entity_registry as er,
21-
)
16+
from homeassistant.helpers import device_registry as dr
17+
from homeassistant.helpers import entity_registry as er
18+
from homeassistant.helpers import issue_registry as ir
2219
from homeassistant.helpers.entity_registry import RegistryEntry
2320

2421
from .const import (
@@ -94,6 +91,32 @@ async def async_setup(self) -> bool:
9491

9592
if source_entity_id:
9693
entity = entity_registry.async_get(source_entity_id)
94+
95+
if not entity:
96+
ir.async_create_issue(
97+
self.hass,
98+
DOMAIN,
99+
f"missing_device_{self.config.entry_id}",
100+
data={
101+
"entry_id": self.config.entry_id,
102+
"device_id": device_id,
103+
"source_entity_id": source_entity_id,
104+
},
105+
is_fixable=True,
106+
severity=ir.IssueSeverity.WARNING,
107+
translation_key="missing_device",
108+
translation_placeholders={
109+
"name": config.title,
110+
},
111+
)
112+
113+
_LOGGER.warning(
114+
"%s is orphaned, unable to find entity %s",
115+
self.config.entry_id,
116+
source_entity_id,
117+
)
118+
return False
119+
97120
device_class = entity.device_class or entity.original_device_class
98121
if (
99122
device_class == SensorDeviceClass.BATTERY
@@ -150,6 +173,30 @@ async def async_setup(self) -> bool:
150173
else:
151174
self.device_name = self.config.title
152175

176+
ir.async_create_issue(
177+
self.hass,
178+
DOMAIN,
179+
f"missing_device_{self.config.entry_id}",
180+
data={
181+
"entry_id": self.config.entry_id,
182+
"device_id": device_id,
183+
"source_entity_id": source_entity_id,
184+
},
185+
is_fixable=True,
186+
severity=ir.IssueSeverity.WARNING,
187+
translation_key="missing_device",
188+
translation_placeholders={
189+
"name": config.title,
190+
},
191+
)
192+
193+
_LOGGER.warning(
194+
"%s is orphaned, unable to find device %s",
195+
self.config.entry_id,
196+
device_id,
197+
)
198+
return False
199+
153200
self.store = self.hass.data[DOMAIN][DATA_STORE]
154201
self.coordinator = BatteryNotesCoordinator(
155202
self.hass, self.store, self.wrapped_battery
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Repairs for battery_notes."""
2+
3+
from __future__ import annotations
4+
5+
import voluptuous as vol
6+
from homeassistant import data_entry_flow
7+
from homeassistant.components.repairs import RepairsFlow
8+
from homeassistant.core import HomeAssistant
9+
from homeassistant.helpers import issue_registry as ir
10+
11+
12+
class MissingDeviceRepairFlow(RepairsFlow):
13+
"""Handler for an issue fixing flow."""
14+
15+
def __init__(self, data: dict[str, str]) -> None:
16+
"""Initialize."""
17+
self.entry_id = data["entry_id"]
18+
self.device_id = data["device_id"]
19+
self.source_entity_id = data["source_entity_id"]
20+
21+
async def async_step_init(
22+
self, user_input: dict[str, str] | None = None
23+
) -> data_entry_flow.FlowResult:
24+
"""Handle the first step of a fix flow."""
25+
26+
return await (self.async_step_confirm())
27+
28+
async def async_step_confirm(
29+
self, user_input: dict[str, str] | None = None
30+
) -> data_entry_flow.FlowResult:
31+
"""Handle the confirm step of a fix flow."""
32+
if user_input is not None:
33+
await self.hass.config_entries.async_remove(self.entry_id)
34+
35+
return self.async_create_entry(title="", data={})
36+
37+
issue_registry = ir.async_get(self.hass)
38+
description_placeholders = None
39+
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
40+
description_placeholders = issue.translation_placeholders
41+
42+
return self.async_show_form(
43+
step_id="confirm",
44+
data_schema=vol.Schema({}),
45+
description_placeholders=description_placeholders
46+
)
47+
48+
49+
async def async_create_fix_flow(
50+
hass: HomeAssistant,
51+
issue_id: str,
52+
data: dict[str, str | int | float | None] | None,
53+
) -> RepairsFlow:
54+
"""Create flow."""
55+
if issue_id.startswith("missing_device_"):
56+
assert data
57+
return MissingDeviceRepairFlow(data)

custom_components/battery_notes/translations/en.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
}
7676
},
7777
"error": {
78+
"orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.",
7879
"unknown": "Unknown error occurred."
7980
}
8081
},
@@ -182,5 +183,18 @@
182183
"description": "Raise events for devices that have a low battery.",
183184
"name": "Check battery low"
184185
}
186+
},
187+
"issues": {
188+
"missing_device": {
189+
"title": "Orphaned Battery Note",
190+
"fix_flow": {
191+
"step": {
192+
"confirm": {
193+
"title": "Orphaned Battery Note",
194+
"description": "The associated device or entity no longer exists for the Battery Note entry {name}, the Battery Note should be deleted.\nSelect **Submit** to delete this Battery Note."
195+
}
196+
}
197+
}
198+
}
185199
}
186200
}

0 commit comments

Comments
 (0)