Merge pull request #180 from balloob/logbook-browser

Logbook - pick day to browse
This commit is contained in:
Paulus Schoutsen 2015-06-15 00:29:20 -07:00
commit d34ecd1c25
12 changed files with 2226 additions and 711 deletions

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "db1ec3e116565340804da0e590058d60" VERSION = "9472014df7b663c70bf33bb456bd8245"

File diff suppressed because one or more lines are too long

View File

@ -31,12 +31,13 @@
"paper-slider": "PolymerElements/paper-slider#^1.0.0", "paper-slider": "PolymerElements/paper-slider#^1.0.0",
"paper-checkbox": "PolymerElements/paper-checkbox#^1.0.0", "paper-checkbox": "PolymerElements/paper-checkbox#^1.0.0",
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#^1.0.0", "paper-drawer-panel": "PolymerElements/paper-drawer-panel#^1.0.0",
"paper-scroll-header-panel": "polymerelements/paper-scroll-header-panel#~1.0", "paper-scroll-header-panel": "polymerelements/paper-scroll-header-panel#^1.0.0",
"google-apis": "GoogleWebComponents/google-apis#0.8-preview", "google-apis": "GoogleWebComponents/google-apis#0.8-preview",
"moment": "^2.10.3", "moment": "^2.10.3",
"layout": "Polymer/layout", "layout": "Polymer/layout",
"color-picker-element": "~0.0.3", "color-picker-element": "~0.0.3",
"paper-styles": "polymerelements/paper-styles#~1.0" "paper-styles": "polymerelements/paper-styles#^1.0.0",
"paper-date-picker": "vsimonian/paper-date-picker#master"
}, },
"resolutions": { "resolutions": {
"polymer": "^1.0.0", "polymer": "^1.0.0",

View File

@ -10,8 +10,13 @@
} }
</style> </style>
<template> <template>
<template is='dom-repeat' items="[[entries]]"> <template is='dom-if' if='[[!entries]]'>
<logbook-entry entry-obj="[[item]]"></logbook-entry> No logbook entries found.
</template>
<template is='dom-if' if='[[entries]]'>
<template is='dom-repeat' items="[[entries]]">
<logbook-entry entry-obj="[[item]]"></logbook-entry>
</template>
</template> </template>
</template> </template>
</dom-module> </dom-module>

@ -1 +1 @@
Subproject commit 015edf9c28a63122aa8f6bc153f0c0ddfaad1caa Subproject commit cec99925399b1c8e8cea15bbbba5d873522ccbd6

View File

@ -1,17 +1,18 @@
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html"> <link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="../bower_components/paper-input/paper-input.html">
<link rel="import" href="./partial-base.html"> <link rel="import" href="./partial-base.html">
<link rel="import" href="../components/ha-logbook.html"> <link rel="import" href="../components/ha-logbook.html">
<link rel="import" href="../components/loading-box.html">
<dom-module id="partial-logbook"> <dom-module id="partial-logbook">
<style> <style>
.content { .selected-date-container {
background-color: white; padding: 0 16px;
padding: 8px; }
}
</style> </style>
<template> <template>
<partial-base narrow="[[narrow]]"> <partial-base narrow="[[narrow]]">
@ -20,7 +21,15 @@
<paper-icon-button icon="refresh" header-buttons <paper-icon-button icon="refresh" header-buttons
on-click="handleRefresh"></paper-icon-button> on-click="handleRefresh"></paper-icon-button>
<ha-logbook entries="[[entries]]"></ha-logbook> <div>
<div class='selected-date-container'>
<paper-input label='Showing entries for' on-click='handleShowDatePicker'
value='[[computeDateCaption(selectedDate)]]'></paper-input>
<loading-box hidden$='[[!isLoading]]'>Loading logbook entries</loading-box>
</div>
<ha-logbook entries="[[entries]]" hidden$='[[isLoading]]'></ha-logbook>
</div>
</partial-base> </partial-base>
</template> </template>
</dom-module> </dom-module>
@ -29,6 +38,12 @@
(function() { (function() {
var storeListenerMixIn = window.hass.storeListenerMixIn; var storeListenerMixIn = window.hass.storeListenerMixIn;
var logbookActions = window.hass.logbookActions; var logbookActions = window.hass.logbookActions;
var logbookStore = window.hass.logbookStore;
var uiActions = window.hass.uiActions;
function date_to_str(date) {
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
}
Polymer({ Polymer({
is: 'partial-logbook', is: 'partial-logbook',
@ -41,22 +56,50 @@
value: false, value: false,
}, },
selectedDate: {
type: String,
value: null,
observer: 'fetchIfNeeded',
},
isLoading: {
type: Boolean,
value: true,
},
entries: { entries: {
type: Array, type: Array,
value: [], value: null,
}, },
}, },
logbookStoreChanged: function(logbookStore) { logbookStoreChanged: function() {
if (logbookStore.isStale()) { this.isLoading = this.fetchIfNeeded();
logbookActions.fetch(); var entries = logbookStore.all.toArray();
} this.entries = entries.length > 0 ? entries : false;
},
this.entries = logbookStore.all.toArray(); computeDateCaption: function(selectedDate) {
return selectedDate || 'today';
},
fetchIfNeeded: function() {
if (logbookStore.shouldFetch(this.selectedDate)) {
this.isLoading = true;
logbookActions.fetch(this.selectedDate);
return true;
}
return false;
},
handleShowDatePicker: function() {
uiActions.showDatePicker(function(selectedDate) {
this.selectedDate = date_to_str(selectedDate);
}.bind(this), this.selectedDate);
}, },
handleRefresh: function() { handleRefresh: function() {
logbookActions.fetch(); logbookActions.fetch(this.selectedDate);
}, },
}); });
})(); })();

View File

@ -1,10 +1,14 @@
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/paper-date-picker/paper-date-picker-dialog.html">
<link rel="import" href="../dialogs/more-info-dialog.html"> <link rel="import" href="../dialogs/more-info-dialog.html">
<dom-module id="modal-manager"> <dom-module id="modal-manager">
<template> <template>
<more-info-dialog id="moreInfoDialog"></more-info-dialog> <more-info-dialog id="moreInfoDialog"></more-info-dialog>
<paper-date-picker-dialog id="datePicker"
on-value-changed='datePickerValueChanged'></paper-date-picker-dialog>
</template> </template>
</dom-module> </dom-module>
@ -16,15 +20,32 @@
Polymer({ Polymer({
is: 'modal-manager', is: 'modal-manager',
properties: {
datePickerCallback: {
type: Function,
value: null,
},
},
ready: function() { ready: function() {
dispatcher.register(function(payload) { dispatcher.register(function(payload) {
switch (payload.actionType) { switch (payload.actionType) {
case uiConstants.ACTION_SHOW_DIALOG_MORE_INFO: case uiConstants.ACTION_SHOW_DIALOG_MORE_INFO:
this.$.moreInfoDialog.show(payload.entityId); this.$.moreInfoDialog.show(payload.entityId);
break; break;
case uiConstants.ACTION_SHOW_DATE_PICKER:
this.datePickerCallback = payload.dateSelectedCallback;
this.$.date = payload.date;
this.$.datePicker.toggle();
break;
} }
}.bind(this)); }.bind(this));
}, },
datePickerValueChanged: function(ev) {
this.datePickerCallback(ev.target.value);
},
}); });
})(); })();
</script> </script>

View File

@ -36,6 +36,7 @@
window.hass.uiConstants = { window.hass.uiConstants = {
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO', ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
ACTION_SHOW_DATE_PICKER: 'ACTION_SHOW_DATE_PICKER',
STATE_FILTERS: { STATE_FILTERS: {
'group': 'Groups', 'group': 'Groups',
@ -52,6 +53,16 @@
}); });
}, },
showDatePicker: function(dateSelectedCallback, startDate) {
startDate = startDate || null;
dispatcher.dispatch({
actionType: window.hass.uiConstants.ACTION_SHOW_DATE_PICKER,
dateSelectedCallback: dateSelectedCallback,
startDate: startDate,
});
},
validateAuth: function(authToken, rememberLogin) { validateAuth: function(authToken, rememberLogin) {
authActions.validate(authToken, { authActions.validate(authToken, {
useStreaming: preferenceStore.useStreaming, useStreaming: preferenceStore.useStreaming,

View File

@ -4,12 +4,14 @@ homeassistant.components.logbook
Parses events and generates a human log. Parses events and generates a human log.
""" """
from datetime import timedelta
from itertools import groupby from itertools import groupby
import re
from homeassistant import State, DOMAIN as HA_DOMAIN from homeassistant import State, DOMAIN as HA_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF, EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.components.recorder as recorder import homeassistant.components.recorder as recorder
import homeassistant.components.sun as sun import homeassistant.components.sun as sun
@ -17,12 +19,10 @@ import homeassistant.components.sun as sun
DOMAIN = "logbook" DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'http'] DEPENDENCIES = ['recorder', 'http']
URL_LOGBOOK = '/api/logbook' URL_LOGBOOK = re.compile(r'/api/logbook(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
QUERY_EVENTS_AFTER = "SELECT * FROM events WHERE time_fired > ?"
QUERY_EVENTS_BETWEEN = """ QUERY_EVENTS_BETWEEN = """
SELECT * FROM events WHERE time_fired > ? AND time_fired < ? SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
ORDER BY time_fired
""" """
GROUP_BY_MINUTES = 15 GROUP_BY_MINUTES = 15
@ -37,11 +37,26 @@ def setup(hass, config):
def _handle_get_logbook(handler, path_match, data): def _handle_get_logbook(handler, path_match, data):
""" Return logbook entries. """ """ Return logbook entries. """
start_today = dt_util.now().replace(hour=0, minute=0, second=0) date_str = path_match.group('date')
handler.write_json(humanify( if date_str:
recorder.query_events( start_date = dt_util.date_str_to_date(date_str)
QUERY_EVENTS_AFTER, (dt_util.as_utc(start_today),))))
if start_date is None:
handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST)
return
start_day = dt_util.start_of_local_day(start_date)
else:
start_day = dt_util.start_of_local_day()
end_day = start_day + timedelta(days=1)
events = recorder.query_events(
QUERY_EVENTS_BETWEEN,
(dt_util.as_utc(start_day), dt_util.as_utc(end_day)))
handler.write_json(humanify(events))
class Entry(object): class Entry(object):

View File

@ -122,7 +122,7 @@ class PublicTransportData(object):
try: try:
return [ return [
dt_util.datetime_to_short_time_str( dt_util.datetime_to_time_str(
dt_util.as_local(dt_util.utc_from_timestamp( dt_util.as_local(dt_util.utc_from_timestamp(
item['from']['departureTimestamp'])) item['from']['departureTimestamp']))
) )

View File

@ -89,9 +89,9 @@ class TimeDateSensor(Entity):
""" Gets the latest data and updates the states. """ """ Gets the latest data and updates the states. """
time_date = dt_util.utcnow() time_date = dt_util.utcnow()
time = dt_util.datetime_to_short_time_str(dt_util.as_local(time_date)) time = dt_util.datetime_to_time_str(dt_util.as_local(time_date))
time_utc = dt_util.datetime_to_short_time_str(time_date) time_utc = dt_util.datetime_to_time_str(time_date)
date = dt_util.datetime_to_short_date_str(dt_util.as_local(time_date)) date = dt_util.datetime_to_date_str(dt_util.as_local(time_date))
# Calculate the beat (Swatch Internet Time) time without date. # Calculate the beat (Swatch Internet Time) time without date.
hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':') hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':')

View File

@ -9,9 +9,9 @@ import datetime as dt
import pytz import pytz
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y" DATETIME_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
DATE_SHORT_STR_FORMAT = "%Y-%m-%d" DATE_STR_FORMAT = "%Y-%m-%d"
TIME_SHORT_STR_FORMAT = "%H:%M" TIME_STR_FORMAT = "%H:%M"
UTC = DEFAULT_TIME_ZONE = pytz.utc UTC = DEFAULT_TIME_ZONE = pytz.utc
@ -34,7 +34,7 @@ def get_time_zone(time_zone_str):
def utcnow(): def utcnow():
""" Get now in UTC time. """ """ Get now in UTC time. """
return dt.datetime.now(pytz.utc) return dt.datetime.now(UTC)
def now(time_zone=None): def now(time_zone=None):
@ -45,12 +45,12 @@ def now(time_zone=None):
def as_utc(dattim): def as_utc(dattim):
""" Return a datetime as UTC time. """ Return a datetime as UTC time.
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """ Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """
if dattim.tzinfo == pytz.utc: if dattim.tzinfo == UTC:
return dattim return dattim
elif dattim.tzinfo is None: elif dattim.tzinfo is None:
dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE) dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE)
return dattim.astimezone(pytz.utc) return dattim.astimezone(UTC)
def as_local(dattim): def as_local(dattim):
@ -58,17 +58,28 @@ def as_local(dattim):
if dattim.tzinfo == DEFAULT_TIME_ZONE: if dattim.tzinfo == DEFAULT_TIME_ZONE:
return dattim return dattim
elif dattim.tzinfo is None: elif dattim.tzinfo is None:
dattim = dattim.replace(tzinfo=pytz.utc) dattim = dattim.replace(tzinfo=UTC)
return dattim.astimezone(DEFAULT_TIME_ZONE) return dattim.astimezone(DEFAULT_TIME_ZONE)
def utc_from_timestamp(timestamp): def utc_from_timestamp(timestamp):
""" Returns a UTC time from a timestamp. """ """ Returns a UTC time from a timestamp. """
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc) return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
def datetime_to_local_str(dattim, time_zone=None): def start_of_local_day(dt_or_d=None):
""" Return local datetime object of start of day from date or datetime. """
if dt_or_d is None:
dt_or_d = now().date()
elif isinstance(dt_or_d, dt.datetime):
dt_or_d = dt_or_d.date()
return dt.datetime.combine(dt_or_d, dt.time()).replace(
tzinfo=DEFAULT_TIME_ZONE)
def datetime_to_local_str(dattim):
""" Converts datetime to specified time_zone and returns a string. """ """ Converts datetime to specified time_zone and returns a string. """
return datetime_to_str(as_local(dattim)) return datetime_to_str(as_local(dattim))
@ -76,27 +87,27 @@ def datetime_to_local_str(dattim, time_zone=None):
def datetime_to_str(dattim): def datetime_to_str(dattim):
""" Converts datetime to a string format. """ Converts datetime to a string format.
@rtype : str
"""
return dattim.strftime(DATETIME_STR_FORMAT)
def datetime_to_time_str(dattim):
""" Converts datetime to a string containing only the time.
@rtype : str
"""
return dattim.strftime(TIME_STR_FORMAT)
def datetime_to_date_str(dattim):
""" Converts datetime to a string containing only the date.
@rtype : str @rtype : str
""" """
return dattim.strftime(DATE_STR_FORMAT) return dattim.strftime(DATE_STR_FORMAT)
def datetime_to_short_time_str(dattim):
""" Converts datetime to a string format as short time.
@rtype : str
"""
return dattim.strftime(TIME_SHORT_STR_FORMAT)
def datetime_to_short_date_str(dattim):
""" Converts datetime to a string format as short date.
@rtype : str
"""
return dattim.strftime(DATE_SHORT_STR_FORMAT)
def str_to_datetime(dt_str): def str_to_datetime(dt_str):
""" Converts a string to a UTC datetime object. """ Converts a string to a UTC datetime object.
@ -104,7 +115,15 @@ def str_to_datetime(dt_str):
""" """
try: try:
return dt.datetime.strptime( return dt.datetime.strptime(
dt_str, DATE_STR_FORMAT).replace(tzinfo=pytz.utc) dt_str, DATETIME_STR_FORMAT).replace(tzinfo=pytz.utc)
except ValueError: # If dt_str did not match our format
return None
def date_str_to_date(dt_str):
""" Converts a date string to a date object. """
try:
return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date()
except ValueError: # If dt_str did not match our format except ValueError: # If dt_str did not match our format
return None return None