mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 09:46:29 +00:00
Fire issue events on HA's bus (#3837)
* Fire issue events on HA's bus * Convey event type with event field * Message for humans
This commit is contained in:
parent
2dab39bf90
commit
1f28e6ad93
@ -443,8 +443,6 @@ class BusEvent(str, Enum):
|
|||||||
|
|
||||||
HARDWARE_NEW_DEVICE = "hardware_new_device"
|
HARDWARE_NEW_DEVICE = "hardware_new_device"
|
||||||
HARDWARE_REMOVE_DEVICE = "hardware_remove_device"
|
HARDWARE_REMOVE_DEVICE = "hardware_remove_device"
|
||||||
ISSUE_CHANGED = "issue_changed"
|
|
||||||
ISSUE_REMOVED = "issue_removed"
|
|
||||||
DOCKER_CONTAINER_STATE_CHANGE = "docker_container_state_change"
|
DOCKER_CONTAINER_STATE_CHANGE = "docker_container_state_change"
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,4 +32,6 @@ class WSEvent(str, Enum):
|
|||||||
"""Websocket events."""
|
"""Websocket events."""
|
||||||
|
|
||||||
ADDON = "addon"
|
ADDON = "addon"
|
||||||
|
ISSUE_CHANGED = "issue_changed"
|
||||||
|
ISSUE_REMOVED = "issue_removed"
|
||||||
SUPERVISOR_UPDATE = "supervisor_update"
|
SUPERVISOR_UPDATE = "supervisor_update"
|
||||||
|
@ -262,7 +262,7 @@ class HomeAssistantWebSocket(CoreSysAttributes):
|
|||||||
except HomeAssistantWSNotSupported:
|
except HomeAssistantWSNotSupported:
|
||||||
pass
|
pass
|
||||||
except HomeAssistantWSError as err:
|
except HomeAssistantWSError as err:
|
||||||
_LOGGER.error(err)
|
_LOGGER.error("Could not send message to Home Assistant due to %s", err)
|
||||||
|
|
||||||
def supervisor_update_event(
|
def supervisor_update_event(
|
||||||
self,
|
self,
|
||||||
@ -279,3 +279,28 @@ class HomeAssistantWebSocket(CoreSysAttributes):
|
|||||||
if self.sys_core.state in CLOSING_STATES:
|
if self.sys_core.state in CLOSING_STATES:
|
||||||
return
|
return
|
||||||
self.sys_create_task(self.async_send_message(message))
|
self.sys_create_task(self.async_send_message(message))
|
||||||
|
|
||||||
|
async def async_supervisor_event(
|
||||||
|
self, event: WSEvent, data: dict[str, Any] | None = None
|
||||||
|
):
|
||||||
|
"""Send a supervisor/event command to Home Assistant."""
|
||||||
|
try:
|
||||||
|
await self.async_send_message(
|
||||||
|
{
|
||||||
|
ATTR_TYPE: WSType.SUPERVISOR_EVENT,
|
||||||
|
ATTR_DATA: {
|
||||||
|
ATTR_EVENT: event,
|
||||||
|
ATTR_DATA: data or {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except HomeAssistantWSNotSupported:
|
||||||
|
pass
|
||||||
|
except HomeAssistantWSError as err:
|
||||||
|
_LOGGER.error("Could not send message to Home Assistant due to %s", err)
|
||||||
|
|
||||||
|
def supervisor_event(self, event: WSEvent, data: dict[str, Any] | None = None):
|
||||||
|
"""Send a supervisor/event command to Home Assistant."""
|
||||||
|
if self.sys_core.state in CLOSING_STATES:
|
||||||
|
return
|
||||||
|
self.sys_create_task(self.async_supervisor_event(event, data))
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ..const import BusEvent
|
import attr
|
||||||
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import ResolutionError, ResolutionNotFound
|
from ..exceptions import ResolutionError, ResolutionNotFound
|
||||||
|
from ..homeassistant.const import WSEvent
|
||||||
from ..utils.common import FileConfiguration
|
from ..utils.common import FileConfiguration
|
||||||
from .check import ResolutionCheck
|
from .check import ResolutionCheck
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -84,7 +86,9 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
|||||||
self._issues.append(issue)
|
self._issues.append(issue)
|
||||||
|
|
||||||
# Event on issue creation
|
# Event on issue creation
|
||||||
self.sys_bus.fire_event(BusEvent.ISSUE_CHANGED, issue)
|
self.sys_homeassistant.websocket.supervisor_event(
|
||||||
|
WSEvent.ISSUE_CHANGED, attr.asdict(issue)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def suggestions(self) -> list[Suggestion]:
|
def suggestions(self) -> list[Suggestion]:
|
||||||
@ -107,7 +111,9 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
|||||||
|
|
||||||
# Event on suggestion added to issue
|
# Event on suggestion added to issue
|
||||||
for issue in self.issues_for_suggestion(suggestion):
|
for issue in self.issues_for_suggestion(suggestion):
|
||||||
self.sys_bus.fire_event(BusEvent.ISSUE_CHANGED, issue)
|
self.sys_homeassistant.websocket.supervisor_event(
|
||||||
|
WSEvent.ISSUE_CHANGED, attr.asdict(issue)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unsupported(self) -> list[UnsupportedReason]:
|
def unsupported(self) -> list[UnsupportedReason]:
|
||||||
@ -200,7 +206,9 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
|||||||
|
|
||||||
# Event on suggestion removed from issues
|
# Event on suggestion removed from issues
|
||||||
for issue in self.issues_for_suggestion(suggestion):
|
for issue in self.issues_for_suggestion(suggestion):
|
||||||
self.sys_bus.fire_event(BusEvent.ISSUE_CHANGED, issue)
|
self.sys_homeassistant.websocket.supervisor_event(
|
||||||
|
WSEvent.ISSUE_CHANGED, attr.asdict(issue)
|
||||||
|
)
|
||||||
|
|
||||||
def dismiss_issue(self, issue: Issue) -> None:
|
def dismiss_issue(self, issue: Issue) -> None:
|
||||||
"""Dismiss suggested action."""
|
"""Dismiss suggested action."""
|
||||||
@ -211,7 +219,9 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
|||||||
self._issues.remove(issue)
|
self._issues.remove(issue)
|
||||||
|
|
||||||
# Event on issue removal
|
# Event on issue removal
|
||||||
self.sys_bus.fire_event(BusEvent.ISSUE_REMOVED, issue)
|
self.sys_homeassistant.websocket.supervisor_event(
|
||||||
|
WSEvent.ISSUE_REMOVED, attr.asdict(issue)
|
||||||
|
)
|
||||||
|
|
||||||
def dismiss_unsupported(self, reason: Issue) -> None:
|
def dismiss_unsupported(self, reason: Issue) -> None:
|
||||||
"""Dismiss a reason for unsupported."""
|
"""Dismiss a reason for unsupported."""
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"""Tests for resolution manager."""
|
"""Tests for resolution manager."""
|
||||||
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
import attr
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.const import BusEvent
|
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
from supervisor.exceptions import ResolutionError
|
from supervisor.exceptions import ResolutionError
|
||||||
from supervisor.resolution.const import (
|
from supervisor.resolution.const import (
|
||||||
@ -18,7 +20,6 @@ from supervisor.resolution.data import Issue, Suggestion
|
|||||||
|
|
||||||
def test_properies_unsupported(coresys: CoreSys):
|
def test_properies_unsupported(coresys: CoreSys):
|
||||||
"""Test resolution manager properties unsupported."""
|
"""Test resolution manager properties unsupported."""
|
||||||
|
|
||||||
assert coresys.core.supported
|
assert coresys.core.supported
|
||||||
|
|
||||||
coresys.resolution.unsupported = UnsupportedReason.OS
|
coresys.resolution.unsupported = UnsupportedReason.OS
|
||||||
@ -27,7 +28,6 @@ def test_properies_unsupported(coresys: CoreSys):
|
|||||||
|
|
||||||
def test_properies_unhealthy(coresys: CoreSys):
|
def test_properies_unhealthy(coresys: CoreSys):
|
||||||
"""Test resolution manager properties unhealthy."""
|
"""Test resolution manager properties unhealthy."""
|
||||||
|
|
||||||
assert coresys.core.healthy
|
assert coresys.core.healthy
|
||||||
|
|
||||||
coresys.resolution.unhealthy = UnhealthyReason.SUPERVISOR
|
coresys.resolution.unhealthy = UnhealthyReason.SUPERVISOR
|
||||||
@ -180,46 +180,68 @@ async def test_issues_for_suggestion(coresys: CoreSys):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _supervisor_event_message(event: str, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Make mock supervisor event message for ha websocket."""
|
||||||
|
return {
|
||||||
|
"type": "supervisor/event",
|
||||||
|
"data": {
|
||||||
|
"event": event,
|
||||||
|
"data": data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_events_on_issue_changes(coresys: CoreSys):
|
async def test_events_on_issue_changes(coresys: CoreSys):
|
||||||
"""Test events fired when an issue changes."""
|
"""Test events fired when an issue changes."""
|
||||||
coresys.bus.register_event(BusEvent.ISSUE_CHANGED, change_handler := AsyncMock())
|
with patch.object(
|
||||||
coresys.bus.register_event(BusEvent.ISSUE_REMOVED, remove_handler := AsyncMock())
|
type(coresys.homeassistant.websocket), "async_send_message"
|
||||||
|
) as send_message:
|
||||||
|
# Creating an issue with a suggestion should fire exactly one issue changed event
|
||||||
|
assert coresys.resolution.issues == []
|
||||||
|
assert coresys.resolution.suggestions == []
|
||||||
|
coresys.resolution.create_issue(
|
||||||
|
IssueType.CORRUPT_REPOSITORY,
|
||||||
|
ContextType.STORE,
|
||||||
|
"test_repo",
|
||||||
|
[SuggestionType.EXECUTE_RESET],
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
# Creating an issue with a suggestion should fire exactly one event
|
assert len(coresys.resolution.issues) == 1
|
||||||
assert coresys.resolution.issues == []
|
assert len(coresys.resolution.suggestions) == 1
|
||||||
assert coresys.resolution.suggestions == []
|
issue = coresys.resolution.issues[0]
|
||||||
coresys.resolution.create_issue(
|
suggestion = coresys.resolution.suggestions[0]
|
||||||
IssueType.CORRUPT_REPOSITORY,
|
send_message.assert_called_once_with(
|
||||||
ContextType.STORE,
|
_supervisor_event_message("issue_changed", attr.asdict(issue))
|
||||||
"test_repo",
|
)
|
||||||
[SuggestionType.EXECUTE_RESET],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(coresys.resolution.issues) == 1
|
# Adding a suggestion that fixes the issue changes it
|
||||||
assert len(coresys.resolution.suggestions) == 1
|
send_message.reset_mock()
|
||||||
issue = coresys.resolution.issues[0]
|
coresys.resolution.suggestions = execute_remove = Suggestion(
|
||||||
suggestion = coresys.resolution.suggestions[0]
|
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, "test_repo"
|
||||||
change_handler.assert_called_once_with(issue)
|
)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
send_message.assert_called_once_with(
|
||||||
|
_supervisor_event_message("issue_changed", attr.asdict(issue))
|
||||||
|
)
|
||||||
|
|
||||||
# Adding and removing a suggestion that fixes the issue should fire another
|
# Removing a suggestion that fixes the issue changes it again
|
||||||
change_handler.reset_mock()
|
send_message.reset_mock()
|
||||||
coresys.resolution.suggestions = execute_remove = Suggestion(
|
coresys.resolution.dismiss_suggestion(execute_remove)
|
||||||
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, "test_repo"
|
await asyncio.sleep(0)
|
||||||
)
|
send_message.assert_called_once_with(
|
||||||
change_handler.assert_called_once_with(issue)
|
_supervisor_event_message("issue_changed", attr.asdict(issue))
|
||||||
|
)
|
||||||
|
|
||||||
change_handler.reset_mock()
|
# Applying a suggestion should only fire an issue removed event
|
||||||
coresys.resolution.dismiss_suggestion(execute_remove)
|
send_message.reset_mock()
|
||||||
change_handler.assert_called_once_with(issue)
|
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
|
||||||
remove_handler.assert_not_called()
|
await coresys.resolution.apply_suggestion(suggestion)
|
||||||
|
|
||||||
# Applying a suggestion should only fire the issue removed event
|
await asyncio.sleep(0)
|
||||||
change_handler.reset_mock()
|
send_message.assert_called_once_with(
|
||||||
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))):
|
_supervisor_event_message("issue_removed", attr.asdict(issue))
|
||||||
await coresys.resolution.apply_suggestion(suggestion)
|
)
|
||||||
|
|
||||||
change_handler.assert_not_called()
|
|
||||||
remove_handler.assert_called_once_with(issue)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_resolution_apply_suggestion_multiple_copies(coresys: CoreSys):
|
async def test_resolution_apply_suggestion_multiple_copies(coresys: CoreSys):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user