Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repairs #2293

Merged
merged 12 commits into from
Nov 5, 2024
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."
}
}
}
}
}
}