From dda4cf4d580ca5b41f1d4b028494b1edccda6b84 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Sun, 30 Aug 2020 02:20:31 -0500
Subject: [PATCH] Tune logbook performance to accomodate recent changes
 (#39348)

---
 .../components/automation/logbook.py          | 11 +++---
 homeassistant/components/logbook/__init__.py  | 39 ++++++++++++++-----
 homeassistant/components/script/logbook.py    |  5 ++-
 tests/components/logbook/test_init.py         | 10 +++++
 4 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py
index cc44296eaa6..3c9671af18f 100644
--- a/homeassistant/components/automation/logbook.py
+++ b/homeassistant/components/automation/logbook.py
@@ -12,14 +12,15 @@ def async_describe_events(hass, async_describe_event):  # type: ignore
     @callback
     def async_describe_logbook_event(event):  # type: ignore
         """Describe a logbook event."""
+        data = event.data
         message = "has been triggered"
-        if ATTR_SOURCE in event.data:
-            message = f"{message} by {event.data[ATTR_SOURCE]}"
+        if ATTR_SOURCE in data:
+            message = f"{message} by {data[ATTR_SOURCE]}"
         return {
-            "name": event.data.get(ATTR_NAME),
+            "name": data.get(ATTR_NAME),
             "message": message,
-            "source": event.data.get(ATTR_SOURCE),
-            "entity_id": event.data.get(ATTR_ENTITY_ID),
+            "source": data.get(ATTR_SOURCE),
+            "entity_id": data.get(ATTR_ENTITY_ID),
         }
 
     async_describe_event(
diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py
index fb219de0d8c..03dc1ffdef9 100644
--- a/homeassistant/components/logbook/__init__.py
+++ b/homeassistant/components/logbook/__init__.py
@@ -3,6 +3,7 @@ from datetime import timedelta
 from itertools import groupby
 import json
 import logging
+import re
 
 import sqlalchemy
 from sqlalchemy.orm import aliased
@@ -50,6 +51,9 @@ from homeassistant.helpers.integration_platform import (
 from homeassistant.loader import bind_hass
 import homeassistant.util.dt as dt_util
 
+ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": "([^"]+)"')
+DOMAIN_JSON_EXTRACT = re.compile('"domain": "([^"]+)"')
+
 _LOGGER = logging.getLogger(__name__)
 
 ATTR_MESSAGE = "message"
@@ -485,20 +489,17 @@ def _keep_event(hass, event, entities_filter):
         entity_id = event.entity_id
     elif event.event_type in HOMEASSISTANT_EVENTS:
         entity_id = f"{HA_DOMAIN}."
-    elif event.event_type in hass.data[DOMAIN] and ATTR_ENTITY_ID not in event.data:
-        # If the entity_id isn't described, use the domain that describes
-        # the event for filtering.
-        domain = hass.data[DOMAIN][event.event_type][0]
-        if domain is None:
-            return False
-        entity_id = f"{domain}."
     elif event.event_type == EVENT_CALL_SERVICE:
         return False
     else:
-        event_data = event.data
-        entity_id = event_data.get(ATTR_ENTITY_ID)
+        entity_id = event.data_entity_id
         if not entity_id:
-            domain = event_data.get(ATTR_DOMAIN)
+            if event.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.event_type][0]
+            else:
+                domain = event.data_domain
             if domain is None:
                 return False
             entity_id = f"{domain}."
@@ -689,6 +690,24 @@ class LazyEventPartialState:
         self.context_user_id = self._row.context_user_id
         self.time_fired_minute = self._row.time_fired.minute
 
+    @property
+    def data_entity_id(self):
+        """Extract the entity id from the decoded data or json."""
+        if self._event_data:
+            return self._event_data.get(ATTR_ENTITY_ID)
+
+        result = ENTITY_ID_JSON_EXTRACT.search(self._row.event_data)
+        return result and result.group(1)
+
+    @property
+    def data_domain(self):
+        """Extract the domain from the decoded data or json."""
+        if self._event_data:
+            return self._event_data.get(ATTR_DOMAIN)
+
+        result = DOMAIN_JSON_EXTRACT.search(self._row.event_data)
+        return result and result.group(1)
+
     @property
     def attributes(self):
         """State attributes."""
diff --git a/homeassistant/components/script/logbook.py b/homeassistant/components/script/logbook.py
index 72ff0d15fc7..f75584540d3 100644
--- a/homeassistant/components/script/logbook.py
+++ b/homeassistant/components/script/logbook.py
@@ -12,10 +12,11 @@ def async_describe_events(hass, async_describe_event):
     @callback
     def async_describe_logbook_event(event):
         """Describe the logbook event."""
+        data = event.data
         return {
-            "name": event.data.get(ATTR_NAME),
+            "name": data.get(ATTR_NAME),
             "message": "started",
-            "entity_id": event.data.get(ATTR_ENTITY_ID),
+            "entity_id": data.get(ATTR_ENTITY_ID),
         }
 
     async_describe_event(DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event)
diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py
index 78753e2a67e..4edf630322a 100644
--- a/tests/components/logbook/test_init.py
+++ b/tests/components/logbook/test_init.py
@@ -2084,6 +2084,16 @@ async def test_logbook_context_from_template(hass, hass_client):
 class MockLazyEventPartialState(ha.Event):
     """Minimal mock of a Lazy event."""
 
+    @property
+    def data_entity_id(self):
+        """Lookup entity id."""
+        return self.data.get(ATTR_ENTITY_ID)
+
+    @property
+    def data_domain(self):
+        """Lookup domain."""
+        return self.data.get(ATTR_DOMAIN)
+
     @property
     def time_fired_minute(self):
         """Minute the event was fired."""