Allow creating fixable repairs issues without flows (#76224)

* Allow creating fixable repairs issues without flows

* Add test

* Adjust test
This commit is contained in:
Erik Montnemery 2022-08-05 12:07:51 +02:00 committed by GitHub
parent 861b694cff
commit b366090175
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 42 deletions

View File

@ -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

View File

@ -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()

View File

@ -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"
} }
} }
} }

View File

@ -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"
} }
} }

View File

@ -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",
] ]

View File

@ -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(

View File

@ -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,
},
] ]
} }

View File

@ -21,10 +21,7 @@ from homeassistant.setup import async_setup_component
from tests.common import mock_platform from tests.common import mock_platform
DEFAULT_ISSUES = [
async def create_issues(hass, ws_client):
"""Create issues."""
issues = [
{ {
"breaks_in_ha_version": "2022.9", "breaks_in_ha_version": "2022.9",
"domain": "fake_integration", "domain": "fake_integration",
@ -34,9 +31,15 @@ async def create_issues(hass, ws_client):
"severity": "error", "severity": "error",
"translation_key": "abc_123", "translation_key": "abc_123",
"translation_placeholders": {"abc": "123"}, "translation_placeholders": {"abc": "123"},
}, }
] ]
async def create_issues(hass, ws_client, issues=None):
"""Create issues."""
if issues is None:
issues = DEFAULT_ISSUES
for issue in issues: for issue in issues:
async_create_issue( async_create_issue(
hass, hass,
@ -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,
} }