diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4555a336cc3..b769b0329cc 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -281,6 +281,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): } if self.action_script.supports_max: attrs[ATTR_MAX] = self.action_script.max_runs + if self._id is not None: + attrs[CONF_ID] = self._id return attrs @property @@ -534,14 +536,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity): variables, ) - @property - def extra_state_attributes(self): - """Return automation attributes.""" - if self._id is None: - return None - - return {CONF_ID: self._id} - async def _async_process_config( hass: HomeAssistant, diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index 3c9671af18f..b5dbada1b7a 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -1,26 +1,29 @@ """Describe logbook events.""" +from homeassistant.components.logbook import LazyEventPartialState from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from . import ATTR_SOURCE, DOMAIN, EVENT_AUTOMATION_TRIGGERED @callback -def async_describe_events(hass, async_describe_event): # type: ignore +def async_describe_events(hass: HomeAssistant, async_describe_event): # type: ignore """Describe logbook events.""" @callback - def async_describe_logbook_event(event): # type: ignore + def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore """Describe a logbook event.""" data = event.data message = "has been triggered" if ATTR_SOURCE in data: message = f"{message} by {data[ATTR_SOURCE]}" + return { "name": data.get(ATTR_NAME), "message": message, "source": data.get(ATTR_SOURCE), "entity_id": data.get(ATTR_ENTITY_ID), + "context_id": event.context_id, } async_describe_event( diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 4aac3d327b8..68cca5a4a41 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -38,7 +38,7 @@ class AutomationTrace: self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._config: Dict[str, Any] = config - self._context: Context = context + self.context: Context = context self._error: Optional[Exception] = None self._state: str = "running" self.run_id: str = str(next(self._run_ids)) @@ -88,7 +88,7 @@ class AutomationTrace: "action_trace": action_traces, "condition_trace": condition_traces, "config": self._config, - "context": self._context, + "context": self.context, "variables": self._variables, } ) diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index eba56f94e7d..7bd1b57d064 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -24,6 +24,7 @@ from homeassistant.helpers.script import ( ) from .trace import ( + DATA_AUTOMATION_TRACE, TraceJSONEncoder, get_debug_trace, get_debug_traces, @@ -38,6 +39,7 @@ def async_setup(hass: HomeAssistant) -> None: """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_automation_trace_get) websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_trace_contexts) websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) @@ -86,6 +88,34 @@ def websocket_automation_trace_list(hass, connection, msg): connection.send_result(msg["id"], automation_traces) +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/trace/contexts", + vol.Optional("automation_id"): str, + } +) +def websocket_automation_trace_contexts(hass, connection, msg): + """Retrieve contexts we have traces for.""" + automation_id = msg.get("automation_id") + + if automation_id is not None: + values = { + automation_id: hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}) + } + else: + values = hass.data[DATA_AUTOMATION_TRACE] + + contexts = { + trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} + for automation_id, traces in values.items() + for trace in traces.values() + } + + connection.send_result(msg["id"], contexts) + + @callback @websocket_api.require_admin @websocket_api.websocket_command( diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py new file mode 100644 index 00000000000..e13ebdc17a1 --- /dev/null +++ b/tests/components/automation/test_logbook.py @@ -0,0 +1,54 @@ +"""Test automation logbook.""" +from homeassistant.components import automation, logbook +from homeassistant.core import Context +from homeassistant.setup import async_setup_component + +from tests.components.logbook.test_init import MockLazyEventPartialState + + +async def test_humanify_automation_trigger_event(hass): + """Test humanifying Shelly click event.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "automation", {}) + assert await async_setup_component(hass, "logbook", {}) + entity_attr_cache = logbook.EntityAttributeCache(hass) + context = Context() + + event1, event2 = list( + logbook.humanify( + hass, + [ + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + "source": "state change of input_boolean.yo", + }, + context=context, + ), + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + }, + context=context, + ), + ], + entity_attr_cache, + {}, + ) + ) + + assert event1["name"] == "Bla" + assert event1["message"] == "has been triggered by state change of input_boolean.yo" + assert event1["source"] == "state change of input_boolean.yo" + assert event1["context_id"] == context.id + assert event1["entity_id"] == "automation.bla" + + assert event2["name"] == "Bla" + assert event2["message"] == "has been triggered" + assert event2["source"] is None + assert event2["context_id"] == context.id + assert event2["entity_id"] == "automation.bla" diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 84c85e224e5..ab69e89416a 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -65,6 +65,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await async_setup_component(hass, "config", {}) client = await hass_ws_client() + contexts = {} # Trigger "sun" automation context = Context() @@ -102,6 +103,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event'" assert trace["unique_id"] == "sun" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -139,6 +144,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with failing condition hass.bus.async_fire("test_event3") @@ -173,6 +182,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event3'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -210,6 +223,16 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } + + # Check contexts + await client.send_json({"id": next_id(), "type": "automation/trace/contexts"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == contexts async def test_automation_trace_overflow(hass, hass_ws_client):