mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Allow storing arbitrary data in repairs issues (#76288)
This commit is contained in:
parent
b366090175
commit
9aa8838479
@ -6,6 +6,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
|
||||||
class DemoFixFlow(RepairsFlow):
|
class DemoFixFlow(RepairsFlow):
|
||||||
@ -28,7 +29,11 @@ class DemoFixFlow(RepairsFlow):
|
|||||||
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
|
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
|
||||||
|
|
||||||
|
|
||||||
async def async_create_fix_flow(hass, issue_id):
|
async def async_create_fix_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
issue_id: str,
|
||||||
|
data: dict[str, str | int | float | None] | None,
|
||||||
|
) -> RepairsFlow:
|
||||||
"""Create flow."""
|
"""Create flow."""
|
||||||
if issue_id == "bad_psu":
|
if issue_id == "bad_psu":
|
||||||
# The bad_psu issue doesn't have its own flow
|
# The bad_psu issue doesn't have its own flow
|
||||||
|
@ -36,7 +36,9 @@ class FluNearYouFixFlow(RepairsFlow):
|
|||||||
|
|
||||||
|
|
||||||
async def async_create_fix_flow(
|
async def async_create_fix_flow(
|
||||||
hass: HomeAssistant, issue_id: str
|
hass: HomeAssistant,
|
||||||
) -> FluNearYouFixFlow:
|
issue_id: str,
|
||||||
|
data: dict[str, str | int | float | None] | None,
|
||||||
|
) -> RepairsFlow:
|
||||||
"""Create flow."""
|
"""Create flow."""
|
||||||
return FluNearYouFixFlow()
|
return FluNearYouFixFlow()
|
||||||
|
@ -64,10 +64,14 @@ class RepairsFlowManager(data_entry_flow.FlowManager):
|
|||||||
|
|
||||||
platforms: dict[str, RepairsProtocol] = self.hass.data[DOMAIN]["platforms"]
|
platforms: dict[str, RepairsProtocol] = self.hass.data[DOMAIN]["platforms"]
|
||||||
if handler_key not in platforms:
|
if handler_key not in platforms:
|
||||||
return ConfirmRepairFlow()
|
flow: RepairsFlow = ConfirmRepairFlow()
|
||||||
|
else:
|
||||||
platform = platforms[handler_key]
|
platform = platforms[handler_key]
|
||||||
|
flow = await platform.async_create_fix_flow(self.hass, issue_id, issue.data)
|
||||||
|
|
||||||
return await platform.async_create_fix_flow(self.hass, issue_id)
|
flow.issue_id = issue_id
|
||||||
|
flow.data = issue.data
|
||||||
|
return flow
|
||||||
|
|
||||||
async def async_finish_flow(
|
async def async_finish_flow(
|
||||||
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult
|
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult
|
||||||
@ -109,6 +113,7 @@ def async_create_issue(
|
|||||||
*,
|
*,
|
||||||
issue_domain: str | None = None,
|
issue_domain: str | None = None,
|
||||||
breaks_in_ha_version: str | None = None,
|
breaks_in_ha_version: str | None = None,
|
||||||
|
data: dict[str, str | int | float | None] | None = None,
|
||||||
is_fixable: bool,
|
is_fixable: bool,
|
||||||
is_persistent: bool = False,
|
is_persistent: bool = False,
|
||||||
learn_more_url: str | None = None,
|
learn_more_url: str | None = None,
|
||||||
@ -131,6 +136,7 @@ def async_create_issue(
|
|||||||
issue_id,
|
issue_id,
|
||||||
issue_domain=issue_domain,
|
issue_domain=issue_domain,
|
||||||
breaks_in_ha_version=breaks_in_ha_version,
|
breaks_in_ha_version=breaks_in_ha_version,
|
||||||
|
data=data,
|
||||||
is_fixable=is_fixable,
|
is_fixable=is_fixable,
|
||||||
is_persistent=is_persistent,
|
is_persistent=is_persistent,
|
||||||
learn_more_url=learn_more_url,
|
learn_more_url=learn_more_url,
|
||||||
@ -146,6 +152,7 @@ def create_issue(
|
|||||||
issue_id: str,
|
issue_id: str,
|
||||||
*,
|
*,
|
||||||
breaks_in_ha_version: str | None = None,
|
breaks_in_ha_version: str | None = None,
|
||||||
|
data: dict[str, str | int | float | None] | None = None,
|
||||||
is_fixable: bool,
|
is_fixable: bool,
|
||||||
is_persistent: bool = False,
|
is_persistent: bool = False,
|
||||||
learn_more_url: str | None = None,
|
learn_more_url: str | None = None,
|
||||||
@ -162,6 +169,7 @@ def create_issue(
|
|||||||
domain,
|
domain,
|
||||||
issue_id,
|
issue_id,
|
||||||
breaks_in_ha_version=breaks_in_ha_version,
|
breaks_in_ha_version=breaks_in_ha_version,
|
||||||
|
data=data,
|
||||||
is_fixable=is_fixable,
|
is_fixable=is_fixable,
|
||||||
is_persistent=is_persistent,
|
is_persistent=is_persistent,
|
||||||
learn_more_url=learn_more_url,
|
learn_more_url=learn_more_url,
|
||||||
|
@ -27,6 +27,7 @@ class IssueEntry:
|
|||||||
active: bool
|
active: bool
|
||||||
breaks_in_ha_version: str | None
|
breaks_in_ha_version: str | None
|
||||||
created: datetime
|
created: datetime
|
||||||
|
data: dict[str, str | int | float | None] | None
|
||||||
dismissed_version: str | None
|
dismissed_version: str | None
|
||||||
domain: str
|
domain: str
|
||||||
is_fixable: bool | None
|
is_fixable: bool | None
|
||||||
@ -53,6 +54,7 @@ class IssueEntry:
|
|||||||
return {
|
return {
|
||||||
**result,
|
**result,
|
||||||
"breaks_in_ha_version": self.breaks_in_ha_version,
|
"breaks_in_ha_version": self.breaks_in_ha_version,
|
||||||
|
"data": self.data,
|
||||||
"is_fixable": self.is_fixable,
|
"is_fixable": self.is_fixable,
|
||||||
"is_persistent": True,
|
"is_persistent": True,
|
||||||
"issue_domain": self.issue_domain,
|
"issue_domain": self.issue_domain,
|
||||||
@ -106,6 +108,7 @@ class IssueRegistry:
|
|||||||
*,
|
*,
|
||||||
issue_domain: str | None = None,
|
issue_domain: str | None = None,
|
||||||
breaks_in_ha_version: str | None = None,
|
breaks_in_ha_version: str | None = None,
|
||||||
|
data: dict[str, str | int | float | None] | None = None,
|
||||||
is_fixable: bool,
|
is_fixable: bool,
|
||||||
is_persistent: bool,
|
is_persistent: bool,
|
||||||
learn_more_url: str | None = None,
|
learn_more_url: str | None = None,
|
||||||
@ -120,6 +123,7 @@ class IssueRegistry:
|
|||||||
active=True,
|
active=True,
|
||||||
breaks_in_ha_version=breaks_in_ha_version,
|
breaks_in_ha_version=breaks_in_ha_version,
|
||||||
created=dt_util.utcnow(),
|
created=dt_util.utcnow(),
|
||||||
|
data=data,
|
||||||
dismissed_version=None,
|
dismissed_version=None,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
is_fixable=is_fixable,
|
is_fixable=is_fixable,
|
||||||
@ -142,6 +146,7 @@ class IssueRegistry:
|
|||||||
issue,
|
issue,
|
||||||
active=True,
|
active=True,
|
||||||
breaks_in_ha_version=breaks_in_ha_version,
|
breaks_in_ha_version=breaks_in_ha_version,
|
||||||
|
data=data,
|
||||||
is_fixable=is_fixable,
|
is_fixable=is_fixable,
|
||||||
is_persistent=is_persistent,
|
is_persistent=is_persistent,
|
||||||
issue_domain=issue_domain,
|
issue_domain=issue_domain,
|
||||||
@ -204,6 +209,7 @@ class IssueRegistry:
|
|||||||
active=True,
|
active=True,
|
||||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||||
created=created,
|
created=created,
|
||||||
|
data=issue["data"],
|
||||||
dismissed_version=issue["dismissed_version"],
|
dismissed_version=issue["dismissed_version"],
|
||||||
domain=issue["domain"],
|
domain=issue["domain"],
|
||||||
is_fixable=issue["is_fixable"],
|
is_fixable=issue["is_fixable"],
|
||||||
@ -220,6 +226,7 @@ class IssueRegistry:
|
|||||||
active=False,
|
active=False,
|
||||||
breaks_in_ha_version=None,
|
breaks_in_ha_version=None,
|
||||||
created=created,
|
created=created,
|
||||||
|
data=None,
|
||||||
dismissed_version=issue["dismissed_version"],
|
dismissed_version=issue["dismissed_version"],
|
||||||
domain=issue["domain"],
|
domain=issue["domain"],
|
||||||
is_fixable=None,
|
is_fixable=None,
|
||||||
|
@ -19,11 +19,17 @@ class IssueSeverity(StrEnum):
|
|||||||
class RepairsFlow(data_entry_flow.FlowHandler):
|
class RepairsFlow(data_entry_flow.FlowHandler):
|
||||||
"""Handle a flow for fixing an issue."""
|
"""Handle a flow for fixing an issue."""
|
||||||
|
|
||||||
|
issue_id: str
|
||||||
|
data: dict[str, str | int | float | None] | None
|
||||||
|
|
||||||
|
|
||||||
class RepairsProtocol(Protocol):
|
class RepairsProtocol(Protocol):
|
||||||
"""Define the format of repairs platforms."""
|
"""Define the format of repairs platforms."""
|
||||||
|
|
||||||
async def async_create_fix_flow(
|
async def async_create_fix_flow(
|
||||||
self, hass: HomeAssistant, issue_id: str
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
issue_id: str,
|
||||||
|
data: dict[str, str | int | float | None] | None,
|
||||||
) -> RepairsFlow:
|
) -> RepairsFlow:
|
||||||
"""Create a flow to fix a fixable issue."""
|
"""Create a flow to fix a fixable issue."""
|
||||||
|
@ -64,7 +64,8 @@ def ws_list_issues(
|
|||||||
"""Return a list of issues."""
|
"""Return a list of issues."""
|
||||||
|
|
||||||
def ws_dict(kv_pairs: list[tuple[Any, Any]]) -> dict[Any, Any]:
|
def ws_dict(kv_pairs: list[tuple[Any, Any]]) -> dict[Any, Any]:
|
||||||
result = {k: v for k, v in kv_pairs if k not in ("active", "is_persistent")}
|
excluded_keys = ("active", "data", "is_persistent")
|
||||||
|
result = {k: v for k, v in kv_pairs if k not in excluded_keys}
|
||||||
result["ignored"] = result["dismissed_version"] is not None
|
result["ignored"] = result["dismissed_version"] is not None
|
||||||
result["created"] = result["created"].isoformat()
|
result["created"] = result["created"].isoformat()
|
||||||
return result
|
return result
|
||||||
|
@ -51,6 +51,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"breaks_in_ha_version": "2022.6",
|
"breaks_in_ha_version": "2022.6",
|
||||||
|
"data": {"entry_id": "123"},
|
||||||
"domain": "test",
|
"domain": "test",
|
||||||
"issue_id": "issue_4",
|
"issue_id": "issue_4",
|
||||||
"is_fixable": True,
|
"is_fixable": True,
|
||||||
@ -141,6 +142,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
|||||||
active=False,
|
active=False,
|
||||||
breaks_in_ha_version=None,
|
breaks_in_ha_version=None,
|
||||||
created=issue1.created,
|
created=issue1.created,
|
||||||
|
data=None,
|
||||||
dismissed_version=issue1.dismissed_version,
|
dismissed_version=issue1.dismissed_version,
|
||||||
domain=issue1.domain,
|
domain=issue1.domain,
|
||||||
is_fixable=None,
|
is_fixable=None,
|
||||||
@ -157,6 +159,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
|||||||
active=False,
|
active=False,
|
||||||
breaks_in_ha_version=None,
|
breaks_in_ha_version=None,
|
||||||
created=issue2.created,
|
created=issue2.created,
|
||||||
|
data=None,
|
||||||
dismissed_version=issue2.dismissed_version,
|
dismissed_version=issue2.dismissed_version,
|
||||||
domain=issue2.domain,
|
domain=issue2.domain,
|
||||||
is_fixable=None,
|
is_fixable=None,
|
||||||
@ -196,6 +199,7 @@ async def test_loading_issues_from_storage(hass: HomeAssistant, hass_storage) ->
|
|||||||
{
|
{
|
||||||
"breaks_in_ha_version": "2022.6",
|
"breaks_in_ha_version": "2022.6",
|
||||||
"created": "2022-07-19T19:41:13.746514+00:00",
|
"created": "2022-07-19T19:41:13.746514+00:00",
|
||||||
|
"data": {"entry_id": "123"},
|
||||||
"dismissed_version": None,
|
"dismissed_version": None,
|
||||||
"domain": "test",
|
"domain": "test",
|
||||||
"issue_domain": "blubb",
|
"issue_domain": "blubb",
|
||||||
|
@ -37,6 +37,17 @@ DEFAULT_ISSUES = [
|
|||||||
|
|
||||||
async def create_issues(hass, ws_client, issues=None):
|
async def create_issues(hass, ws_client, issues=None):
|
||||||
"""Create issues."""
|
"""Create issues."""
|
||||||
|
|
||||||
|
def api_issue(issue):
|
||||||
|
excluded_keys = ("data",)
|
||||||
|
return dict(
|
||||||
|
{key: issue[key] for key in issue if key not in excluded_keys},
|
||||||
|
created=ANY,
|
||||||
|
dismissed_version=None,
|
||||||
|
ignored=False,
|
||||||
|
issue_domain=None,
|
||||||
|
)
|
||||||
|
|
||||||
if issues is None:
|
if issues is None:
|
||||||
issues = DEFAULT_ISSUES
|
issues = DEFAULT_ISSUES
|
||||||
|
|
||||||
@ -46,6 +57,7 @@ async def create_issues(hass, ws_client, issues=None):
|
|||||||
issue["domain"],
|
issue["domain"],
|
||||||
issue["issue_id"],
|
issue["issue_id"],
|
||||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||||
|
data=issue.get("data"),
|
||||||
is_fixable=issue["is_fixable"],
|
is_fixable=issue["is_fixable"],
|
||||||
is_persistent=False,
|
is_persistent=False,
|
||||||
learn_more_url=issue["learn_more_url"],
|
learn_more_url=issue["learn_more_url"],
|
||||||
@ -58,22 +70,17 @@ async def create_issues(hass, ws_client, issues=None):
|
|||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
assert msg["result"] == {
|
assert msg["result"] == {"issues": [api_issue(issue) for issue in issues]}
|
||||||
"issues": [
|
|
||||||
dict(
|
|
||||||
issue,
|
|
||||||
created=ANY,
|
|
||||||
dismissed_version=None,
|
|
||||||
ignored=False,
|
|
||||||
issue_domain=None,
|
|
||||||
)
|
|
||||||
for issue in issues
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
EXPECTED_DATA = {
|
||||||
|
"issue_1": None,
|
||||||
|
"issue_2": {"blah": "bleh"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MockFixFlow(RepairsFlow):
|
class MockFixFlow(RepairsFlow):
|
||||||
"""Handler for an issue fixing flow."""
|
"""Handler for an issue fixing flow."""
|
||||||
|
|
||||||
@ -82,6 +89,9 @@ class MockFixFlow(RepairsFlow):
|
|||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle the first step of a fix flow."""
|
"""Handle the first step of a fix flow."""
|
||||||
|
|
||||||
|
assert self.issue_id in EXPECTED_DATA
|
||||||
|
assert self.data == EXPECTED_DATA[self.issue_id]
|
||||||
|
|
||||||
return await (self.async_step_custom_step())
|
return await (self.async_step_custom_step())
|
||||||
|
|
||||||
async def async_step_custom_step(
|
async def async_step_custom_step(
|
||||||
@ -99,7 +109,10 @@ async def mock_repairs_integration(hass):
|
|||||||
"""Mock a repairs integration."""
|
"""Mock a repairs integration."""
|
||||||
hass.config.components.add("fake_integration")
|
hass.config.components.add("fake_integration")
|
||||||
|
|
||||||
def async_create_fix_flow(hass, issue_id):
|
def async_create_fix_flow(hass, issue_id, data):
|
||||||
|
assert issue_id in EXPECTED_DATA
|
||||||
|
assert data == EXPECTED_DATA[issue_id]
|
||||||
|
|
||||||
return MockFixFlow()
|
return MockFixFlow()
|
||||||
|
|
||||||
mock_platform(
|
mock_platform(
|
||||||
@ -256,11 +269,18 @@ async def test_fix_issue(
|
|||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
|
||||||
issues = [{**DEFAULT_ISSUES[0], "domain": domain}]
|
issues = [
|
||||||
|
{
|
||||||
|
**DEFAULT_ISSUES[0],
|
||||||
|
"data": {"blah": "bleh"},
|
||||||
|
"domain": domain,
|
||||||
|
"issue_id": "issue_2",
|
||||||
|
}
|
||||||
|
]
|
||||||
await create_issues(hass, ws_client, issues=issues)
|
await create_issues(hass, ws_client, issues=issues)
|
||||||
|
|
||||||
url = "/api/repairs/issues/fix"
|
url = "/api/repairs/issues/fix"
|
||||||
resp = await client.post(url, json={"handler": domain, "issue_id": "issue_1"})
|
resp = await client.post(url, json={"handler": domain, "issue_id": "issue_2"})
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user