Logbook context (#16937)

* Convert logbook to use attr

* Add context to events

* Enhance logbook

* Lint

* Fix logbook entry

* Don't use intermediary classes for logbook entries
This commit is contained in:
Paulus Schoutsen 2018-10-01 16:12:25 +02:00 committed by Pascal Vizeli
parent 2e6346ca43
commit fbc1c41673
4 changed files with 133 additions and 80 deletions

View File

@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID) SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
@ -369,8 +368,8 @@ def _async_get_action(hass, config, name):
async def action(entity_id, variables, context): async def action(entity_id, variables, context):
"""Execute an action.""" """Execute an action."""
_LOGGER.info('Executing %s', name) _LOGGER.info('Executing %s', name)
logbook.async_log_entry( hass.components.logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id) name, 'has been triggered', DOMAIN, entity_id)
await script_obj.async_run(variables, context) await script_obj.async_run(variables, context)
return action return action

View File

@ -10,6 +10,7 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.loader import bind_hass
from homeassistant.components import sun from homeassistant.components import sun
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ( from homeassistant.const import (
@ -17,8 +18,9 @@ from homeassistant.const import (
CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME,
STATE_OFF, STATE_ON) STATE_OFF, STATE_ON)
from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.core import (
from homeassistant.core import State, callback, split_entity_id DOMAIN as HA_DOMAIN, State, callback, split_entity_id)
from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -53,7 +55,8 @@ CONFIG_SCHEMA = vol.Schema({
ALL_EVENT_TYPES = [ ALL_EVENT_TYPES = [
EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_ALEXA_SMART_HOME
] ]
LOG_MESSAGE_SCHEMA = vol.Schema({ LOG_MESSAGE_SCHEMA = vol.Schema({
@ -64,11 +67,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({
}) })
@bind_hass
def log_entry(hass, name, message, domain=None, entity_id=None): def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook.""" """Add an entry to the logbook."""
hass.add_job(async_log_entry, hass, name, message, domain, entity_id) hass.add_job(async_log_entry, hass, name, message, domain, entity_id)
@bind_hass
def async_log_entry(hass, name, message, domain=None, entity_id=None): def async_log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook.""" """Add an entry to the logbook."""
data = { data = {
@ -140,30 +145,7 @@ class LogbookView(HomeAssistantView):
return await hass.async_add_job(json_events) return await hass.async_add_job(json_events)
class Entry: def humanify(hass, events):
"""A human readable version of the log."""
def __init__(self, when=None, name=None, message=None, domain=None,
entity_id=None):
"""Initialize the entry."""
self.when = when
self.name = name
self.message = message
self.domain = domain
self.entity_id = entity_id
def as_dict(self):
"""Convert entry to a dict to be used within JSON."""
return {
'when': self.when,
'name': self.name,
'message': self.message,
'domain': self.domain,
'entity_id': self.entity_id,
}
def humanify(events):
"""Generate a converted list of events into Entry objects. """Generate a converted list of events into Entry objects.
Will try to group events if possible: Will try to group events if possible:
@ -224,20 +206,28 @@ def humanify(events):
to_state.attributes.get('unit_of_measurement'): to_state.attributes.get('unit_of_measurement'):
continue continue
yield Entry( yield {
event.time_fired, 'when': event.time_fired,
name=to_state.name, 'name': to_state.name,
message=_entry_message_from_state(domain, to_state), 'message': _entry_message_from_state(domain, to_state),
domain=domain, 'domain': domain,
entity_id=to_state.entity_id) 'entity_id': to_state.entity_id,
'context_id': event.context.id,
'context_user_id': event.context.user_id
}
elif event.event_type == EVENT_HOMEASSISTANT_START: elif event.event_type == EVENT_HOMEASSISTANT_START:
if start_stop_events.get(event.time_fired.minute) == 2: if start_stop_events.get(event.time_fired.minute) == 2:
continue continue
yield Entry( yield {
event.time_fired, "Home Assistant", "started", 'when': event.time_fired,
domain=HA_DOMAIN) 'name': "Home Assistant",
'message': "started",
'domain': HA_DOMAIN,
'context_id': event.context.id,
'context_user_id': event.context.user_id
}
elif event.event_type == EVENT_HOMEASSISTANT_STOP: elif event.event_type == EVENT_HOMEASSISTANT_STOP:
if start_stop_events.get(event.time_fired.minute) == 2: if start_stop_events.get(event.time_fired.minute) == 2:
@ -245,9 +235,14 @@ def humanify(events):
else: else:
action = "stopped" action = "stopped"
yield Entry( yield {
event.time_fired, "Home Assistant", action, 'when': event.time_fired,
domain=HA_DOMAIN) 'name': "Home Assistant",
'message': action,
'domain': HA_DOMAIN,
'context_id': event.context.id,
'context_user_id': event.context.user_id
}
elif event.event_type == EVENT_LOGBOOK_ENTRY: elif event.event_type == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN) domain = event.data.get(ATTR_DOMAIN)
@ -258,10 +253,38 @@ def humanify(events):
except IndexError: except IndexError:
pass pass
yield Entry( yield {
event.time_fired, event.data.get(ATTR_NAME), 'when': event.time_fired,
event.data.get(ATTR_MESSAGE), domain, 'name': event.data.get(ATTR_NAME),
entity_id) 'message': event.data.get(ATTR_MESSAGE),
'domain': domain,
'entity_id': entity_id,
'context_id': event.context.id,
'context_user_id': event.context.user_id
}
elif event.event_type == EVENT_ALEXA_SMART_HOME:
data = event.data
entity_id = data.get('entity_id')
if entity_id:
state = hass.states.get(entity_id)
name = state.name if state else entity_id
message = "send command {}/{} for {}".format(
data['namespace'], data['name'], name)
else:
message = "send command {}/{}".format(
data['namespace'], data['name'])
yield {
'when': event.time_fired,
'name': 'Amazon Alexa',
'message': message,
'domain': 'alexa',
'entity_id': entity_id,
'context_id': event.context.id,
'context_user_id': event.context.user_id
}
def _get_events(hass, config, start_day, end_day): def _get_events(hass, config, start_day, end_day):
@ -279,7 +302,7 @@ def _get_events(hass, config, start_day, end_day):
.filter((States.last_updated == States.last_changed) .filter((States.last_updated == States.last_changed)
| (States.state_id.is_(None))) | (States.state_id.is_(None)))
events = execute(query) events = execute(query)
return humanify(_exclude_events(events, config)) return humanify(hass, _exclude_events(events, config))
def _exclude_events(events, config): def _exclude_events(events, config):

View File

@ -186,6 +186,6 @@ def _logbook_filtering(hass, last_changed, last_updated):
# pylint: disable=protected-access # pylint: disable=protected-access
events = logbook._exclude_events(events, {}) events = logbook._exclude_events(events, {})
list(logbook.humanify(events)) list(logbook.humanify(None, events))
return timer() - start return timer() - start

View File

@ -11,6 +11,7 @@ from homeassistant.const import (
ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF) ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF)
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components import logbook, recorder from homeassistant.components import logbook, recorder
from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
from homeassistant.setup import setup_component, async_setup_component from homeassistant.setup import setup_component, async_setup_component
from tests.common import ( from tests.common import (
@ -99,7 +100,7 @@ class TestComponentLogbook(unittest.TestCase):
eventB = self.create_state_changed_event(pointB, entity_id, 20) eventB = self.create_state_changed_event(pointB, entity_id, 20)
eventC = self.create_state_changed_event(pointC, entity_id, 30) eventC = self.create_state_changed_event(pointC, entity_id, 30)
entries = list(logbook.humanify((eventA, eventB, eventC))) entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC)))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -116,7 +117,7 @@ class TestComponentLogbook(unittest.TestCase):
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, entity_id, 10, attributes) pointA, entity_id, 10, attributes)
entries = list(logbook.humanify((eventA,))) entries = list(logbook.humanify(self.hass, (eventA,)))
self.assertEqual(0, len(entries)) self.assertEqual(0, len(entries))
@ -133,7 +134,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -155,7 +156,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -177,7 +178,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -203,7 +204,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -229,7 +230,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -266,7 +267,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -292,7 +293,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -318,7 +319,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -352,7 +353,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3, (ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3,
eventB1, eventB2), config[logbook.DOMAIN]) eventB1, eventB2), config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(3, len(entries)) self.assertEqual(3, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -373,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase):
{'auto': True}) {'auto': True})
events = logbook._exclude_events((eventA, eventB), {}) events = logbook._exclude_events((eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries)) self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch', self.assert_entry(entries[0], pointA, 'bla', domain='switch',
@ -391,29 +392,18 @@ class TestComponentLogbook(unittest.TestCase):
pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB) pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB)
events = logbook._exclude_events((eventA, eventB), {}) events = logbook._exclude_events((eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries)) self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch', self.assert_entry(entries[0], pointA, 'bla', domain='switch',
entity_id=entity_id) entity_id=entity_id)
def test_entry_to_dict(self):
"""Test conversion of entry to dict."""
entry = logbook.Entry(
dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch'
)
data = entry.as_dict()
self.assertEqual('Alarm', data.get(logbook.ATTR_NAME))
self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE))
self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN))
self.assertEqual('test_switch', data.get(logbook.ATTR_ENTITY_ID))
def test_home_assistant_start_stop_grouped(self): def test_home_assistant_start_stop_grouped(self):
"""Test if HA start and stop events are grouped. """Test if HA start and stop events are grouped.
Events that are occurring in the same minute. Events that are occurring in the same minute.
""" """
entries = list(logbook.humanify(( entries = list(logbook.humanify(self.hass, (
ha.Event(EVENT_HOMEASSISTANT_STOP), ha.Event(EVENT_HOMEASSISTANT_STOP),
ha.Event(EVENT_HOMEASSISTANT_START), ha.Event(EVENT_HOMEASSISTANT_START),
))) )))
@ -428,7 +418,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id = 'switch.bla' entity_id = 'switch.bla'
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entries = list(logbook.humanify(( entries = list(logbook.humanify(self.hass, (
ha.Event(EVENT_HOMEASSISTANT_START), ha.Event(EVENT_HOMEASSISTANT_START),
self.create_state_changed_event(pointA, entity_id, 10) self.create_state_changed_event(pointA, entity_id, 10)
))) )))
@ -509,7 +499,7 @@ class TestComponentLogbook(unittest.TestCase):
message = 'has a custom entry' message = 'has a custom entry'
entity_id = 'sun.sun' entity_id = 'sun.sun'
entries = list(logbook.humanify(( entries = list(logbook.humanify(self.hass, (
ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { ha.Event(logbook.EVENT_LOGBOOK_ENTRY, {
logbook.ATTR_NAME: name, logbook.ATTR_NAME: name,
logbook.ATTR_MESSAGE: message, logbook.ATTR_MESSAGE: message,
@ -526,19 +516,19 @@ class TestComponentLogbook(unittest.TestCase):
domain=None, entity_id=None): domain=None, entity_id=None):
"""Assert an entry is what is expected.""" """Assert an entry is what is expected."""
if when: if when:
self.assertEqual(when, entry.when) self.assertEqual(when, entry['when'])
if name: if name:
self.assertEqual(name, entry.name) self.assertEqual(name, entry['name'])
if message: if message:
self.assertEqual(message, entry.message) self.assertEqual(message, entry['message'])
if domain: if domain:
self.assertEqual(domain, entry.domain) self.assertEqual(domain, entry['domain'])
if entity_id: if entity_id:
self.assertEqual(entity_id, entry.entity_id) self.assertEqual(entity_id, entry['entity_id'])
def create_state_changed_event(self, event_time_fired, entity_id, state, def create_state_changed_event(self, event_time_fired, entity_id, state,
attributes=None, last_changed=None, attributes=None, last_changed=None,
@ -566,3 +556,44 @@ async def test_logbook_view(hass, aiohttp_client):
response = await client.get( response = await client.get(
'/api/logbook/{}'.format(dt_util.utcnow().isoformat())) '/api/logbook/{}'.format(dt_util.utcnow().isoformat()))
assert response.status == 200 assert response.status == 200
async def test_humanify_alexa_event(hass):
"""Test humanifying Alexa event."""
hass.states.async_set('light.kitchen', 'on', {
'friendly_name': 'Kitchen Light'
})
results = list(logbook.humanify(hass, [
ha.Event(EVENT_ALEXA_SMART_HOME, {
'namespace': 'Alexa.Discovery',
'name': 'Discover',
}),
ha.Event(EVENT_ALEXA_SMART_HOME, {
'namespace': 'Alexa.PowerController',
'name': 'TurnOn',
'entity_id': 'light.kitchen'
}),
ha.Event(EVENT_ALEXA_SMART_HOME, {
'namespace': 'Alexa.PowerController',
'name': 'TurnOn',
'entity_id': 'light.non_existing'
}),
]))
event1, event2, event3 = results
assert event1['name'] == 'Amazon Alexa'
assert event1['message'] == 'send command Alexa.Discovery/Discover'
assert event1['entity_id'] is None
assert event2['name'] == 'Amazon Alexa'
assert event2['message'] == \
'send command Alexa.PowerController/TurnOn for Kitchen Light'
assert event2['entity_id'] == 'light.kitchen'
assert event3['name'] == 'Amazon Alexa'
assert event3['message'] == \
'send command Alexa.PowerController/TurnOn for light.non_existing'
assert event3['entity_id'] == 'light.non_existing'