From 2a2a7a62c525e1feda6557c48a881c997133eef7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 May 2022 16:16:33 -0400 Subject: [PATCH] Avoid matching entity_id/domain attributes in logbook when there is no entities_filter (#71825) --- homeassistant/components/logbook/__init__.py | 57 ++++++++++---------- tests/components/logbook/test_init.py | 19 ++++++- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 4b40e398f6e..fae979445fd 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -426,7 +426,8 @@ def _humanify( elif event_type == EVENT_LOGBOOK_ENTRY: event = event_cache.get(row) - event_data = event.data + if not (event_data := event.data): + continue domain = event_data.get(ATTR_DOMAIN) entity_id = event_data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: @@ -474,6 +475,22 @@ def _get_events( def yield_rows(query: Query) -> Generator[Row, None, None]: """Yield Events that are not filtered away.""" + + def _keep_row(row: Row, event_type: str) -> bool: + """Check if the entity_filter rejects a row.""" + assert entities_filter is not None + if entity_id := _row_event_data_extract(row, ENTITY_ID_JSON_EXTRACT): + return entities_filter(entity_id) + + if event_type in external_events: + # If the entity_id isn't described, use the domain that describes + # the event for filtering. + domain: str | None = external_events[event_type][0] + else: + domain = _row_event_data_extract(row, DOMAIN_JSON_EXTRACT) + + return domain is not None and entities_filter(f"{domain}._") + # end_day - start_day intentionally checks .days and not .total_seconds() # since we don't want to switch over to buffered if they go # over one day by a few hours since the UI makes it so easy to do that. @@ -490,16 +507,17 @@ def _get_events( # so we don't switch over until we request > 1 day+ of data. # rows = query.yield_per(1024) + for row in rows: context_lookup.setdefault(row.context_id, row) - if row.context_only: - continue - event_type = row.event_type - if event_type != EVENT_CALL_SERVICE and ( - event_type == EVENT_STATE_CHANGED - or _keep_row(hass, event_type, row, entities_filter) - ): - yield row + if not row.context_only: + event_type = row.event_type + if event_type != EVENT_CALL_SERVICE and ( + entities_filter is None + or event_type == EVENT_STATE_CHANGED + or _keep_row(row, event_type) + ): + yield row if entity_ids is not None: entities_filter = generate_filter([], entity_ids, [], []) @@ -526,27 +544,6 @@ def _get_events( ) -def _keep_row( - hass: HomeAssistant, - event_type: str, - row: Row, - entities_filter: EntityFilter | Callable[[str], bool] | None = None, -) -> bool: - if entity_id := _row_event_data_extract(row, ENTITY_ID_JSON_EXTRACT): - return entities_filter is None or entities_filter(entity_id) - - if event_type in hass.data[DOMAIN]: - # If the entity_id isn't described, use the domain that describes - # the event for filtering. - domain = hass.data[DOMAIN][event_type][0] - else: - domain = _row_event_data_extract(row, DOMAIN_JSON_EXTRACT) - - return domain is not None and ( - entities_filter is None or entities_filter(f"{domain}._") - ) - - class ContextAugmenter: """Augment data with context trace.""" diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 82857e6735c..eb58a61835e 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1761,13 +1761,21 @@ async def test_fire_logbook_entries(hass, hass_client, recorder_mock): logbook.EVENT_LOGBOOK_ENTRY, {}, ) + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_DOMAIN: "switch", + }, + ) await async_wait_recording_done(hass) client = await hass_client() response_json = await _async_fetch_logbook(client) # The empty events should be skipped - assert len(response_json) == 10 + assert len(response_json) == 11 async def test_exclude_events_domain(hass, hass_client, recorder_mock): @@ -1986,6 +1994,15 @@ async def test_include_events_domain_glob(hass, hass_client, recorder_mock): ) await async_recorder_block_till_done(hass) + # Should get excluded by domain + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_DOMAIN: "switch", + }, + ) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(