Skip to content

Commit

Permalink
Repairs (#2293)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* WIP

* WIP

* Remove print statement

* Change logging

* Lint

* Error handling

* Error handling

* Error handling for entities

* Remove issues if device deleted
  • Loading branch information
andrew-codechimp authored Nov 5, 2024
1 parent dfcba9e commit 14f0aa3
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 19 deletions.
4 changes: 4 additions & 0 deletions custom_components/battery_notes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util

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

# Remove any issues raised
ir.async_delete_issue(hass, DOMAIN, f"missing_device_{config_entry.entry_id}")

if "device_id" not in config_entry.data:
return

Expand Down
38 changes: 25 additions & 13 deletions custom_components/battery_notes/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,20 +448,23 @@ async def async_step_init(
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get(self.source_device_id)

_LOGGER.debug(
"Looking up device %s %s %s %s",
device_entry.manufacturer,
device_entry.model,
get_device_model_id(device_entry) or "",
device_entry.hw_version,
)
if not device_entry:
errors["base"] = "orphaned_battery_note"
else:
_LOGGER.debug(
"Looking up device %s %s %s %s",
device_entry.manufacturer,
device_entry.model,
get_device_model_id(device_entry) or "",
device_entry.hw_version,
)

self.model_info = ModelInfo(
device_entry.manufacturer,
device_entry.model,
get_device_model_id(device_entry),
device_entry.hw_version,
)
self.model_info = ModelInfo(
device_entry.manufacturer,
device_entry.model,
get_device_model_id(device_entry),
device_entry.hw_version,
)

schema = self.build_options_schema()
if user_input is not None:
Expand Down Expand Up @@ -492,6 +495,8 @@ async def save_options(
schema: vol.Schema,
) -> dict:
"""Save options, and return errors when validation fails."""
errors = {}

device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get(
self.config_entry.data.get(CONF_DEVICE_ID)
Expand All @@ -502,6 +507,13 @@ async def save_options(
if source_entity_id:
entity_registry = er.async_get(self.hass)
entity_entry = entity_registry.async_get(source_entity_id)
if not entity_entry:
errors["base"] = "orphaned_battery_note"
return errors
else:
if not device_entry:
errors["base"] = "orphaned_battery_note"
return errors

if CONF_NAME in user_input:
title = user_input.get(CONF_NAME)
Expand Down
59 changes: 53 additions & 6 deletions custom_components/battery_notes/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@
PERCENTAGE,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import (
device_registry as dr,
)
from homeassistant.helpers import (
entity_registry as er,
)
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.entity_registry import RegistryEntry

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

if source_entity_id:
entity = entity_registry.async_get(source_entity_id)

if not entity:
ir.async_create_issue(
self.hass,
DOMAIN,
f"missing_device_{self.config.entry_id}",
data={
"entry_id": self.config.entry_id,
"device_id": device_id,
"source_entity_id": source_entity_id,
},
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="missing_device",
translation_placeholders={
"name": config.title,
},
)

_LOGGER.warning(
"%s is orphaned, unable to find entity %s",
self.config.entry_id,
source_entity_id,
)
return False

device_class = entity.device_class or entity.original_device_class
if (
device_class == SensorDeviceClass.BATTERY
Expand Down Expand Up @@ -150,6 +173,30 @@ async def async_setup(self) -> bool:
else:
self.device_name = self.config.title

ir.async_create_issue(
self.hass,
DOMAIN,
f"missing_device_{self.config.entry_id}",
data={
"entry_id": self.config.entry_id,
"device_id": device_id,
"source_entity_id": source_entity_id,
},
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="missing_device",
translation_placeholders={
"name": config.title,
},
)

_LOGGER.warning(
"%s is orphaned, unable to find device %s",
self.config.entry_id,
device_id,
)
return False

self.store = self.hass.data[DOMAIN][DATA_STORE]
self.coordinator = BatteryNotesCoordinator(
self.hass, self.store, self.wrapped_battery
Expand Down
57 changes: 57 additions & 0 deletions custom_components/battery_notes/repairs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Repairs for battery_notes."""

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 MissingDeviceRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""

def __init__(self, data: dict[str, str]) -> None:
"""Initialize."""
self.entry_id = data["entry_id"]
self.device_id = data["device_id"]
self.source_entity_id = data["source_entity_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:
await self.hass.config_entries.async_remove(self.entry_id)

return self.async_create_entry(title="", 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."""
if issue_id.startswith("missing_device_"):
assert data
return MissingDeviceRepairFlow(data)
14 changes: 14 additions & 0 deletions custom_components/battery_notes/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
}
},
"error": {
"orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.",
"unknown": "Unknown error occurred."
}
},
Expand Down Expand Up @@ -182,5 +183,18 @@
"description": "Raise events for devices that have a low battery.",
"name": "Check battery low"
}
},
"issues": {
"missing_device": {
"title": "Orphaned Battery Note",
"fix_flow": {
"step": {
"confirm": {
"title": "Orphaned Battery Note",
"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."
}
}
}
}
}
}

0 comments on commit 14f0aa3

Please sign in to comment.