Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
781ec62
add next-flow to repairs
iluvdata Feb 24, 2026
c657339
add tests repairs flow fix next flow
iluvdata Feb 24, 2026
287c921
add next_flow to async_create_entry
iluvdata Mar 7, 2026
70ce27c
Merge remote-tracking branch 'upstream/dev' into repair-flow-next-flow
iluvdata Mar 7, 2026
9bd1c31
Merge branch 'home-assistant:dev' into repair-flow-next-flow
iluvdata Mar 7, 2026
2d05ad8
Merge branch 'repair-flow-next-flow' of https://github.com/iluvdata/h…
iluvdata Mar 7, 2026
649b3ed
comment grammar error
iluvdata Mar 7, 2026
9de1d4c
Update tests/components/repairs/test_websocket_api.py
iluvdata Mar 7, 2026
65d1b0c
Update tests/components/repairs/test_websocket_api.py
iluvdata Mar 7, 2026
453c862
suggested edits
iluvdata Mar 7, 2026
9239847
suggested fixes
iluvdata Mar 8, 2026
aba15cb
Move FlowType to NextFlowType
iluvdata Mar 9, 2026
dd23f75
Move FlowType to NextFlowType
iluvdata Mar 9, 2026
55a1edd
Merge branch 'home-assistant:dev' into repair-flow-next-flow
iluvdata Mar 9, 2026
4af986d
Merge branch 'home-assistant:dev' into repair-flow-next-flow
iluvdata Apr 1, 2026
5ddcb7c
Address mypy error, don't delete issues with next_flow
iluvdata Apr 3, 2026
0eeb6ab
change tests for repairs with next_flows (issues remain in registry)
iluvdata Apr 3, 2026
8454a60
Merge remote-tracking branch 'upstream/dev' into repair-flow-next-flow
iluvdata Apr 23, 2026
4f1da34
Add repair flows that trigger other different repair flows via next_flow
iluvdata Apr 27, 2026
0d65b9a
Add next_flow specificying repair_flow as the next_flow in repair flows
iluvdata Apr 29, 2026
2cb8163
Move all relevant methods to issue_handler.py and various grammar upd…
iluvdata Apr 29, 2026
ed18a35
Minor copilot review code changes
iluvdata Apr 29, 2026
3441f50
Merge branch 'dev' of https://github.com/home-assistant/core into rep…
iluvdata May 6, 2026
41cf6a4
Resolve merge conflicts
iluvdata May 6, 2026
2d43119
add copilot review changes
iluvdata May 6, 2026
72744d0
minor updates (revised tests)
iluvdata May 6, 2026
1a7d273
Merge branch 'dev' into repair-flow-next-flow
iluvdata May 11, 2026
7e45d2e
Merge branch 'home-assistant:dev' into repair-flow-next-flow
iluvdata May 18, 2026
67496ea
Add UnknownIssue Error, Fix Generic FlowResult Type in _BaseFlowManag…
iluvdata May 18, 2026
88258be
Remove setting flow.handler as overwritten anyway in super().async_in…
iluvdata May 18, 2026
2e03884
Add copilot edits
iluvdata May 18, 2026
b6100bf
Merge branch 'dev' of https://github.com/home-assistant/core into rep…
iluvdata May 19, 2026
fc1e169
Revert anthropic patch
iluvdata May 19, 2026
dba8ba4
comment clarification based on copilot review
iluvdata May 19, 2026
66657a2
Potential fix for pull request finding
iluvdata May 19, 2026
35be68a
ruff format error fix
iluvdata May 19, 2026
f050a82
Merge branch 'dev' into repair-flow-next-flow
iluvdata May 26, 2026
ec0d2c8
Minor update in deprecation warning language for clarity
iluvdata May 26, 2026
7bcd3df
Merge branch 'repair-flow-next-flow' of https://github.com/iluvdata/h…
iluvdata May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions homeassistant/components/repairs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
from homeassistant.helpers.typing import ConfigType

from . import issue_handler, websocket_api
from .const import DOMAIN
from .const import DOMAIN, NextFlowType
from .issue_handler import ConfirmRepairFlow, RepairsFlowManager
from .models import RepairsFlow
from .models import RepairsFlow, RepairsFlowResult

__all__ = [
"DOMAIN",
"ConfirmRepairFlow",
"NextFlowType",
"RepairsFlow",
"RepairsFlowManager",
"RepairsFlowResult",
"repairs_flow_manager",
]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/repairs/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
"""Constants for the Repairs integration."""

from enum import StrEnum

DOMAIN = "repairs"


class NextFlowType(StrEnum):
Comment thread
iluvdata marked this conversation as resolved.
Outdated
"""Extend FlowType to support additional next flow types."""

CONFIG_FLOW = "config_flow"
OPTIONS_FLOW = "options_flow"
CONFIG_SUBENTRIES_FLOW = "config_subentries_flow"
Comment thread
iluvdata marked this conversation as resolved.
26 changes: 16 additions & 10 deletions homeassistant/components/repairs/issue_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@
)

from .const import DOMAIN
from .models import RepairsFlow, RepairsProtocol
from .models import RepairsFlow, RepairsFlowResult, 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:
) -> RepairsFlowResult:
"""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:
) -> RepairsFlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
return self.async_create_entry(data={})
Expand All @@ -46,7 +46,9 @@ async def async_step_confirm(
)


class RepairsFlowManager(data_entry_flow.FlowManager):
class RepairsFlowManager(
data_entry_flow.FlowManager[data_entry_flow.FlowContext, RepairsFlowResult, str]
):
"""Manage repairs flows."""

async def async_create_flow(
Expand All @@ -63,7 +65,7 @@ async def async_create_flow(
issue_registry = ir.async_get(self.hass)
issue = issue_registry.async_get_issue(handler_key, issue_id)
if issue is None or not issue.is_fixable:
raise data_entry_flow.UnknownStep
raise data_entry_flow.UnknownStep("Issue not found in registry")

if "platforms" not in self.hass.data[DOMAIN]:
await async_process_repairs_platforms(self.hass)
Expand All @@ -79,15 +81,19 @@ async def async_create_flow(
flow.data = issue.data
return flow

async def async_finish_flow(
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult:
async def async_finish_flow( # type: ignore[override]
self, flow: RepairsFlow, result: RepairsFlowResult
) -> RepairsFlowResult:
Comment thread
iluvdata marked this conversation as resolved.
Outdated
"""Complete a fix flow.

This method is called when a flow step returns FlowResultType.ABORT or
FlowResultType.CREATE_ENTRY.
FlowResultType.CREATE_ENTRY. When next flow is set in result, leave the issue active as the integration needs to resolve the issue
not the active flow.
"""
if result.get("type") != data_entry_flow.FlowResultType.ABORT:
if (
result.get("type") != data_entry_flow.FlowResultType.ABORT
and result.get("next_flow") is None
):
ir.async_delete_issue(self.hass, flow.handler, flow.init_data["issue_id"])
return result

Expand Down
91 changes: 88 additions & 3 deletions homeassistant/components/repairs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,103 @@

from __future__ import annotations

from typing import Protocol
from collections.abc import Mapping
from typing import Any, Protocol

from homeassistant import data_entry_flow
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
ConfigEntry,
ConfigFlowResult,
SubentryFlowResult,
)
from homeassistant.core import HomeAssistant, callback

from .const import NextFlowType

class RepairsFlow(data_entry_flow.FlowHandler):

class RepairsFlowResult(
data_entry_flow.FlowResult[data_entry_flow.FlowContext, str],
total=False,
):
"""Typed context dict for repairs flow."""

next_flow: tuple[NextFlowType, str]
# Frontend needs this to render the dialog.
result: ConfigEntry


class RepairsFlow(
data_entry_flow.FlowHandler[data_entry_flow.FlowContext, RepairsFlowResult, str]
):
"""Handle a flow for fixing an issue."""

issue_id: str
data: dict[str, str | int | float | None] | None

@callback
def async_create_entry(
self,
*,
title: str | None = None,
data: Mapping[str, Any],
description: str | None = None,
description_placeholders: Mapping[str, str] | None = None,
next_flow: tuple[NextFlowType, str] | None = None,
) -> RepairsFlowResult:
"""Finish a repair flow."""
result: RepairsFlowResult = super().async_create_entry(
title=title,
data=data,
description=description,
description_placeholders=description_placeholders,
)

self._async_set_next_flow_if_valid(result, next_flow)

return result

def _async_set_next_flow_if_valid(
self,
result: RepairsFlowResult,
next_flow: tuple[NextFlowType, str] | None,
) -> None:
"""Validate and set next_flow in result if provided."""
if next_flow is None:
return
flow_type, flow_id = next_flow
if flow_type not in NextFlowType:
raise data_entry_flow.FlowError("Invalid next_flow type")
flow: ConfigFlowResult | SubentryFlowResult
entry_id: str
try:
Comment thread
iluvdata marked this conversation as resolved.
Outdated
if flow_type in {
NextFlowType.CONFIG_FLOW,
NextFlowType.CONFIG_SUBENTRIES_FLOW,
}:
if flow_type == NextFlowType.CONFIG_FLOW:
flow = self.hass.config_entries.flow.async_get(flow_id)
entry_id = flow["context"]["entry_id"]
else: # subentry flow
flow = self.hass.config_entries.subentries.async_get(flow_id)
entry_id, _ = flow["handler"]
if (
"context" not in flow
or flow["context"]["source"] != SOURCE_RECONFIGURE
): # check if reconfigure flow
raise data_entry_flow.FlowError(
"Next flow must be a reconfigure flow"
Comment thread
iluvdata marked this conversation as resolved.
Outdated
)
Comment thread
iluvdata marked this conversation as resolved.
Outdated
else:
flow = self.hass.config_entries.options.async_get(flow_id)
entry_id = flow["handler"]
except data_entry_flow.UnknownFlow as ex:
raise data_entry_flow.UnknownFlow(
f"Unknown next flow: {flow_type}: {flow_id}"
) from ex
result["result"] = self.hass.config_entries.async_get_known_entry(entry_id)
result["next_flow"] = next_flow


class RepairsProtocol(Protocol):
"""Define the format of repairs platforms."""
Expand Down
42 changes: 34 additions & 8 deletions homeassistant/components/repairs/websocket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections.abc import Callable
from http import HTTPStatus
from typing import Any

Expand All @@ -13,6 +14,7 @@
from homeassistant.components import websocket_api
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.http.decorators import require_admin
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.data_entry_flow import (
Expand All @@ -21,6 +23,8 @@
)

from .const import DOMAIN
from .issue_handler import RepairsFlowManager
from .models import RepairsFlowResult


@callback
Expand Down Expand Up @@ -107,7 +111,7 @@ def ws_list_issues(
connection.send_result(msg["id"], {"issues": issues})


class RepairsFlowIndexView(FlowManagerIndexView):
class RepairsFlowIndexView(FlowManagerIndexView[RepairsFlowManager]):
"""View to create issue fix flows."""

url = "/api/repairs/issues/fix"
Expand All @@ -130,19 +134,21 @@ async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response
data["handler"],
data={"issue_id": data["issue_id"]},
)
except data_entry_flow.UnknownHandler:
return self.json_message("Invalid handler specified", HTTPStatus.NOT_FOUND)
except data_entry_flow.UnknownStep:
return self.json_message(
"Handler does not support user", HTTPStatus.BAD_REQUEST
)
except data_entry_flow.UnknownStep as ex:
return self.json_message(str(ex), HTTPStatus.BAD_REQUEST)
Comment thread
iluvdata marked this conversation as resolved.
Outdated
except data_entry_flow.FlowError as ex:
return self.json_message(str(ex), HTTPStatus.BAD_REQUEST)
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.

return self.json(
self._prepare_result_json(result),
)

def _prepare_result_json(self, result: RepairsFlowResult) -> dict[str, Any]: # type: ignore[override]
"""Convert result to JSON serializable dict."""
return _prepare_repairs_flow_result_json(result, super()._prepare_result_json)


class RepairsFlowResourceView(FlowManagerResourceView):
class RepairsFlowResourceView(FlowManagerResourceView[RepairsFlowManager]):
"""View to interact with the option flow manager."""

url = "/api/repairs/issues/fix/{flow_id}"
Expand All @@ -157,3 +163,23 @@ async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request."""
return await super().post(request, flow_id)
Comment thread
iluvdata marked this conversation as resolved.

def _prepare_result_json(self, result: RepairsFlowResult) -> dict[str, Any]: # type: ignore[override]
"""Convert result to JSON serializable dict."""
return _prepare_repairs_flow_result_json(result, super()._prepare_result_json)
Comment thread
iluvdata marked this conversation as resolved.
Comment thread
iluvdata marked this conversation as resolved.


def _prepare_repairs_flow_result_json(
result: RepairsFlowResult,
prepare_result_json: Callable[[data_entry_flow.FlowResult], dict[str, Any]],
) -> dict[str, Any]:
"""Convert result to JSON."""
if (
result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY
or "next_flow" not in result
):
return prepare_result_json(result)
data = {key: val for key, val in result.items() if key not in ("data", "context")}
entry: ConfigEntry = result["result"]
data["result"] = entry.as_json_fragment
return data
1 change: 1 addition & 0 deletions homeassistant/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,7 @@ async def async_finish_flow(
self.config_entries._async_clean_up(existing_entry) # noqa: SLF001

result["result"] = entry

return result

async def async_create_flow(
Expand Down
Loading
Loading