mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Allow creating fixable repairs issues without flows (#76224)
* Allow creating fixable repairs issues without flows * Add test * Adjust test
This commit is contained in:
parent
861b694cff
commit
b366090175
@ -211,6 +211,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
translation_key="unfixable_problem",
|
translation_key="unfixable_problem",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"bad_psu",
|
||||||
|
is_fixable=True,
|
||||||
|
learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU",
|
||||||
|
severity=IssueSeverity.CRITICAL,
|
||||||
|
translation_key="bad_psu",
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.repairs import RepairsFlow
|
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||||
|
|
||||||
|
|
||||||
class DemoFixFlow(RepairsFlow):
|
class DemoFixFlow(RepairsFlow):
|
||||||
@ -23,11 +23,16 @@ class DemoFixFlow(RepairsFlow):
|
|||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle the confirm step of a fix flow."""
|
"""Handle the confirm step of a fix flow."""
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
return self.async_create_entry(title="Fixed issue", data={})
|
return self.async_create_entry(title="", data={})
|
||||||
|
|
||||||
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, issue_id):
|
||||||
"""Create flow."""
|
"""Create flow."""
|
||||||
|
if issue_id == "bad_psu":
|
||||||
|
# The bad_psu issue doesn't have its own flow
|
||||||
|
return ConfirmRepairFlow()
|
||||||
|
|
||||||
|
# Other issues have a custom flow
|
||||||
return DemoFixFlow()
|
return DemoFixFlow()
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
{
|
{
|
||||||
"title": "Demo",
|
"title": "Demo",
|
||||||
"issues": {
|
"issues": {
|
||||||
|
"bad_psu": {
|
||||||
|
"title": "The power supply is not stable",
|
||||||
|
"fix_flow": {
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "The power supply needs to be replaced",
|
||||||
|
"description": "Press SUBMIT to confirm the power supply has been replaced"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"out_of_blinker_fluid": {
|
"out_of_blinker_fluid": {
|
||||||
"title": "The blinker fluid is empty and needs to be refilled",
|
"title": "The blinker fluid is empty and needs to be refilled",
|
||||||
"fix_flow": {
|
"fix_flow": {
|
||||||
"step": {
|
"step": {
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"title": "Blinker fluid needs to be refilled",
|
"title": "Blinker fluid needs to be refilled",
|
||||||
"description": "Press OK when blinker fluid has been refilled"
|
"description": "Press SUBMIT when blinker fluid has been refilled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
{
|
{
|
||||||
"issues": {
|
"issues": {
|
||||||
|
"bad_psu": {
|
||||||
|
"fix_flow": {
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"description": "Press SUBMIT to confirm the power supply has been replaced",
|
||||||
|
"title": "The power supply needs to be replaced"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "The power supply is not stable"
|
||||||
|
},
|
||||||
"out_of_blinker_fluid": {
|
"out_of_blinker_fluid": {
|
||||||
"fix_flow": {
|
"fix_flow": {
|
||||||
"step": {
|
"step": {
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"description": "Press OK when blinker fluid has been refilled",
|
"description": "Press SUBMIT when blinker fluid has been refilled",
|
||||||
"title": "Blinker fluid needs to be refilled"
|
"title": "Blinker fluid needs to be refilled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from . import issue_handler, websocket_api
|
from . import issue_handler, websocket_api
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .issue_handler import (
|
from .issue_handler import (
|
||||||
|
ConfirmRepairFlow,
|
||||||
async_create_issue,
|
async_create_issue,
|
||||||
async_delete_issue,
|
async_delete_issue,
|
||||||
create_issue,
|
create_issue,
|
||||||
@ -21,6 +22,7 @@ __all__ = [
|
|||||||
"create_issue",
|
"create_issue",
|
||||||
"delete_issue",
|
"delete_issue",
|
||||||
"DOMAIN",
|
"DOMAIN",
|
||||||
|
"ConfirmRepairFlow",
|
||||||
"IssueSeverity",
|
"IssueSeverity",
|
||||||
"RepairsFlow",
|
"RepairsFlow",
|
||||||
]
|
]
|
||||||
|
@ -5,6 +5,7 @@ import functools as ft
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
|
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -19,6 +20,26 @@ from .issue_registry import async_get as async_get_issue_registry
|
|||||||
from .models import IssueSeverity, RepairsFlow, RepairsProtocol
|
from .models import IssueSeverity, RepairsFlow, RepairsProtocol
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmRepairFlow(RepairsFlow):
|
||||||
|
"""Handler for an issue fixing flow without any side effects."""
|
||||||
|
|
||||||
|
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:
|
||||||
|
return self.async_create_entry(title="", data={})
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
|
||||||
|
|
||||||
|
|
||||||
class RepairsFlowManager(data_entry_flow.FlowManager):
|
class RepairsFlowManager(data_entry_flow.FlowManager):
|
||||||
"""Manage repairs flows."""
|
"""Manage repairs flows."""
|
||||||
|
|
||||||
@ -30,14 +51,6 @@ class RepairsFlowManager(data_entry_flow.FlowManager):
|
|||||||
data: dict[str, Any] | None = None,
|
data: dict[str, Any] | None = None,
|
||||||
) -> RepairsFlow:
|
) -> RepairsFlow:
|
||||||
"""Create a flow. platform is a repairs module."""
|
"""Create a flow. platform is a repairs module."""
|
||||||
if "platforms" not in self.hass.data[DOMAIN]:
|
|
||||||
await async_process_repairs_platforms(self.hass)
|
|
||||||
|
|
||||||
platforms: dict[str, RepairsProtocol] = self.hass.data[DOMAIN]["platforms"]
|
|
||||||
if handler_key not in platforms:
|
|
||||||
raise data_entry_flow.UnknownHandler
|
|
||||||
platform = platforms[handler_key]
|
|
||||||
|
|
||||||
assert data and "issue_id" in data
|
assert data and "issue_id" in data
|
||||||
issue_id = data["issue_id"]
|
issue_id = data["issue_id"]
|
||||||
|
|
||||||
@ -46,6 +59,14 @@ class RepairsFlowManager(data_entry_flow.FlowManager):
|
|||||||
if issue is None or not issue.is_fixable:
|
if issue is None or not issue.is_fixable:
|
||||||
raise data_entry_flow.UnknownStep
|
raise data_entry_flow.UnknownStep
|
||||||
|
|
||||||
|
if "platforms" not in self.hass.data[DOMAIN]:
|
||||||
|
await async_process_repairs_platforms(self.hass)
|
||||||
|
|
||||||
|
platforms: dict[str, RepairsProtocol] = self.hass.data[DOMAIN]["platforms"]
|
||||||
|
if handler_key not in platforms:
|
||||||
|
return ConfirmRepairFlow()
|
||||||
|
platform = platforms[handler_key]
|
||||||
|
|
||||||
return await platform.async_create_fix_flow(self.hass, issue_id)
|
return await platform.async_create_fix_flow(self.hass, issue_id)
|
||||||
|
|
||||||
async def async_finish_flow(
|
async def async_finish_flow(
|
||||||
|
@ -129,6 +129,20 @@ async def test_issues_created(hass, hass_client, hass_ws_client):
|
|||||||
"translation_key": "unfixable_problem",
|
"translation_key": "unfixable_problem",
|
||||||
"translation_placeholders": None,
|
"translation_placeholders": None,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"breaks_in_ha_version": None,
|
||||||
|
"created": ANY,
|
||||||
|
"dismissed_version": None,
|
||||||
|
"domain": "demo",
|
||||||
|
"ignored": False,
|
||||||
|
"is_fixable": True,
|
||||||
|
"issue_domain": None,
|
||||||
|
"issue_id": "bad_psu",
|
||||||
|
"learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU",
|
||||||
|
"severity": "critical",
|
||||||
|
"translation_key": "bad_psu",
|
||||||
|
"translation_placeholders": None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +178,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client):
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"flow_id": flow_id,
|
"flow_id": flow_id,
|
||||||
"handler": "demo",
|
"handler": "demo",
|
||||||
"title": "Fixed issue",
|
"title": "",
|
||||||
"type": "create_entry",
|
"type": "create_entry",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
}
|
}
|
||||||
@ -203,5 +217,19 @@ async def test_issues_created(hass, hass_client, hass_ws_client):
|
|||||||
"translation_key": "unfixable_problem",
|
"translation_key": "unfixable_problem",
|
||||||
"translation_placeholders": None,
|
"translation_placeholders": None,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"breaks_in_ha_version": None,
|
||||||
|
"created": ANY,
|
||||||
|
"dismissed_version": None,
|
||||||
|
"domain": "demo",
|
||||||
|
"ignored": False,
|
||||||
|
"is_fixable": True,
|
||||||
|
"issue_domain": None,
|
||||||
|
"issue_id": "bad_psu",
|
||||||
|
"learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU",
|
||||||
|
"severity": "critical",
|
||||||
|
"translation_key": "bad_psu",
|
||||||
|
"translation_placeholders": None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -21,21 +21,24 @@ from homeassistant.setup import async_setup_component
|
|||||||
|
|
||||||
from tests.common import mock_platform
|
from tests.common import mock_platform
|
||||||
|
|
||||||
|
DEFAULT_ISSUES = [
|
||||||
|
{
|
||||||
|
"breaks_in_ha_version": "2022.9",
|
||||||
|
"domain": "fake_integration",
|
||||||
|
"issue_id": "issue_1",
|
||||||
|
"is_fixable": True,
|
||||||
|
"learn_more_url": "https://theuselessweb.com",
|
||||||
|
"severity": "error",
|
||||||
|
"translation_key": "abc_123",
|
||||||
|
"translation_placeholders": {"abc": "123"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
async def create_issues(hass, ws_client):
|
|
||||||
|
async def create_issues(hass, ws_client, issues=None):
|
||||||
"""Create issues."""
|
"""Create issues."""
|
||||||
issues = [
|
if issues is None:
|
||||||
{
|
issues = DEFAULT_ISSUES
|
||||||
"breaks_in_ha_version": "2022.9",
|
|
||||||
"domain": "fake_integration",
|
|
||||||
"issue_id": "issue_1",
|
|
||||||
"is_fixable": True,
|
|
||||||
"learn_more_url": "https://theuselessweb.com",
|
|
||||||
"severity": "error",
|
|
||||||
"translation_key": "abc_123",
|
|
||||||
"translation_placeholders": {"abc": "123"},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
async_create_issue(
|
async_create_issue(
|
||||||
@ -79,23 +82,22 @@ 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."""
|
||||||
|
|
||||||
return await (self.async_step_confirm())
|
return await (self.async_step_custom_step())
|
||||||
|
|
||||||
async def async_step_confirm(
|
async def async_step_custom_step(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle the confirm step of a fix flow."""
|
"""Handle a custom_step step of a fix flow."""
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
return self.async_create_entry(title=None, data=None)
|
return self.async_create_entry(title="", data={})
|
||||||
|
|
||||||
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
|
return self.async_show_form(step_id="custom_step", data_schema=vol.Schema({}))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
async def mock_repairs_integration(hass):
|
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")
|
||||||
hass.config.components.add("integration_without_diagnostics")
|
|
||||||
|
|
||||||
def async_create_fix_flow(hass, issue_id):
|
def async_create_fix_flow(hass, issue_id):
|
||||||
return MockFixFlow()
|
return MockFixFlow()
|
||||||
@ -107,7 +109,7 @@ async def mock_repairs_integration(hass):
|
|||||||
)
|
)
|
||||||
mock_platform(
|
mock_platform(
|
||||||
hass,
|
hass,
|
||||||
"integration_without_diagnostics.repairs",
|
"integration_without_repairs.repairs",
|
||||||
Mock(spec=[]),
|
Mock(spec=[]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -237,7 +239,16 @@ async def test_fix_non_existing_issue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> None:
|
@pytest.mark.parametrize(
|
||||||
|
"domain, step",
|
||||||
|
(
|
||||||
|
("fake_integration", "custom_step"),
|
||||||
|
("fake_integration_default_handler", "confirm"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_fix_issue(
|
||||||
|
hass: HomeAssistant, hass_client, hass_ws_client, domain, step
|
||||||
|
) -> None:
|
||||||
"""Test we can fix an issue."""
|
"""Test we can fix an issue."""
|
||||||
assert await async_setup_component(hass, "http", {})
|
assert await async_setup_component(hass, "http", {})
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
@ -245,12 +256,11 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No
|
|||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
|
||||||
await create_issues(hass, ws_client)
|
issues = [{**DEFAULT_ISSUES[0], "domain": domain}]
|
||||||
|
await create_issues(hass, ws_client, issues=issues)
|
||||||
|
|
||||||
url = "/api/repairs/issues/fix"
|
url = "/api/repairs/issues/fix"
|
||||||
resp = await client.post(
|
resp = await client.post(url, json={"handler": domain, "issue_id": "issue_1"})
|
||||||
url, json={"handler": "fake_integration", "issue_id": "issue_1"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
@ -261,9 +271,9 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"flow_id": ANY,
|
"flow_id": ANY,
|
||||||
"handler": "fake_integration",
|
"handler": domain,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
"step_id": "confirm",
|
"step_id": step,
|
||||||
"type": "form",
|
"type": "form",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,8 +296,8 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No
|
|||||||
"description": None,
|
"description": None,
|
||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"flow_id": flow_id,
|
"flow_id": flow_id,
|
||||||
"handler": "fake_integration",
|
"handler": domain,
|
||||||
"title": None,
|
"title": "",
|
||||||
"type": "create_entry",
|
"type": "create_entry",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user