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:
Mike Degatano 2022-09-02 11:40:10 -04:00 committed by GitHub
parent 2dab39bf90
commit 1f28e6ad93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 44 deletions

View File

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

View File

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

View File

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

View File

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

View File

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