From 3b425c3e140f5b1752fb4a2752fcc4d44e32b51b Mon Sep 17 00:00:00 2001
From: Nikolay Vasilchuk <Anonym.tsk@gmail.com>
Date: Thu, 11 Oct 2018 12:46:16 +0300
Subject: [PATCH] Logbook: filter by entity and period (#1728)

* Filter logbook by entity_id

* Filter logbook by period

* Filter logbook styles

* CI Fix

* Review

* Review

* CI Fix
---
 src/panels/logbook/ha-logbook-data.js  | 86 ++++++++++++++++------
 src/panels/logbook/ha-logbook.js       | 16 +++++
 src/panels/logbook/ha-panel-logbook.js | 99 +++++++++++++++++++++++---
 3 files changed, 167 insertions(+), 34 deletions(-)

diff --git a/src/panels/logbook/ha-logbook-data.js b/src/panels/logbook/ha-logbook-data.js
index c4188951c1..996e11bc74 100644
--- a/src/panels/logbook/ha-logbook-data.js
+++ b/src/panels/logbook/ha-logbook-data.js
@@ -1,6 +1,7 @@
 import { PolymerElement } from '@polymer/polymer/polymer-element.js';
 
-var DATE_CACHE = {};
+const DATA_CACHE = {};
+const ALL_ENTITIES = '*';
 
 class HaLogbookData extends PolymerElement {
   static get properties() {
@@ -12,7 +13,17 @@ class HaLogbookData extends PolymerElement {
 
       filterDate: {
         type: String,
-        observer: 'filterDateChanged',
+        observer: 'filterDataChanged',
+      },
+
+      filterPeriod: {
+        type: Number,
+        observer: 'filterDataChanged',
+      },
+
+      filterEntity: {
+        type: String,
+        observer: 'filterDataChanged',
       },
 
       isLoading: {
@@ -33,41 +44,70 @@ class HaLogbookData extends PolymerElement {
 
   hassChanged(newHass, oldHass) {
     if (!oldHass && this.filterDate) {
-      this.filterDateChanged(this.filterDate);
+      this.updateData();
     }
   }
 
-  filterDateChanged(filterDate) {
+  filterDataChanged(newValue, oldValue) {
+    if (oldValue !== undefined) {
+      this.updateData();
+    }
+  }
+
+  updateData() {
     if (!this.hass) return;
 
     this._setIsLoading(true);
 
-    this.getDate(filterDate).then(function (logbookEntries) {
-      this._setEntries(logbookEntries);
-      this._setIsLoading(false);
-    }.bind(this));
+    this.getDate(this.filterDate, this.filterPeriod, this.filterEntity)
+      .then((logbookEntries) => {
+        this._setEntries(logbookEntries);
+        this._setIsLoading(false);
+      });
   }
 
-  getDate(date) {
-    if (!DATE_CACHE[date]) {
-      DATE_CACHE[date] = this.hass.callApi('GET', 'logbook/' + date).then(
-        function (logbookEntries) {
-          logbookEntries.reverse();
-          return logbookEntries;
-        },
-        function () {
-          DATE_CACHE[date] = false;
-          return null;
-        }
-      );
+  getDate(date, period, entityId) {
+    if (!entityId) entityId = ALL_ENTITIES;
+
+    if (!DATA_CACHE[period]) DATA_CACHE[period] = [];
+    if (!DATA_CACHE[period][date]) DATA_CACHE[period][date] = [];
+
+    if (DATA_CACHE[period][date][entityId]) {
+      return DATA_CACHE[period][date][entityId];
     }
 
-    return DATE_CACHE[date];
+    if (entityId !== ALL_ENTITIES && DATA_CACHE[period][date][ALL_ENTITIES]) {
+      return DATA_CACHE[period][date][ALL_ENTITIES].then(function (entities) {
+        return entities.filter(function (entity) {
+          return entity.entity_id === entityId;
+        });
+      });
+    }
+
+    DATA_CACHE[period][date][entityId] = this._getFromServer(date, period, entityId);
+    return DATA_CACHE[period][date][entityId];
+  }
+
+  _getFromServer(date, period, entityId) {
+    let url = 'logbook/' + date + '?period=' + period;
+    if (entityId !== ALL_ENTITIES) {
+      url += '&entity=' + entityId;
+    }
+
+    return this.hass.callApi('GET', url).then(
+      function (logbookEntries) {
+        logbookEntries.reverse();
+        return logbookEntries;
+      },
+      function () {
+        return null;
+      }
+    );
   }
 
   refreshLogbook() {
-    DATE_CACHE[this.filterDate] = null;
-    this.filterDateChanged(this.filterDate);
+    DATA_CACHE[this.filterPeriod][this.filterDate] = [];
+    this.updateData();
   }
 }
 
diff --git a/src/panels/logbook/ha-logbook.js b/src/panels/logbook/ha-logbook.js
index 387056f61c..d2a600140b 100644
--- a/src/panels/logbook/ha-logbook.js
+++ b/src/panels/logbook/ha-logbook.js
@@ -5,6 +5,7 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
 
 
 import formatTime from '../../common/datetime/format_time.js';
+import formatDate from '../../common/datetime/format_date.js';
 import EventsMixin from '../../mixins/events-mixin.js';
 import domainIcon from '../../common/entity/domain_icon.js';
 
@@ -50,6 +51,10 @@ class HaLogbook extends EventsMixin(PolymerElement) {
     </template>
 
     <template is="dom-repeat" items="[[entries]]">
+      <template is="dom-if" if="{{_needHeader(entries.*, index)}}">
+        <h4 class="date">[[_formatDate(item.when)]]</h4>
+      </template>
+
       <div class="horizontal layout entry">
         <div class="time">[[_formatTime(item.when)]]</div>
         <iron-icon icon="[[_computeIcon(item.domain)]]"></iron-icon>
@@ -85,6 +90,17 @@ class HaLogbook extends EventsMixin(PolymerElement) {
     return formatTime(new Date(date), this.language);
   }
 
+  _formatDate(date) {
+    return formatDate(new Date(date), this.language);
+  }
+
+  _needHeader(change, index) {
+    if (!index) return true;
+    const current = this.get('when', change.base[index]);
+    const previous = this.get('when', change.base[index - 1]);
+    return current && previous && current.substr(0, 10) !== previous.substr(0, 10);
+  }
+
   _computeIcon(domain) {
     return domainIcon(domain);
   }
diff --git a/src/panels/logbook/ha-panel-logbook.js b/src/panels/logbook/ha-panel-logbook.js
index cab422b0ea..9220ebdf38 100644
--- a/src/panels/logbook/ha-panel-logbook.js
+++ b/src/panels/logbook/ha-panel-logbook.js
@@ -9,6 +9,7 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
 import '@vaadin/vaadin-date-picker/vaadin-date-picker.js';
 
 import '../../components/ha-menu-button.js';
+import '../../components/entity/ha-entity-picker.js';
 import '../../resources/ha-date-picker-style.js';
 import '../../resources/ha-style.js';
 
@@ -31,16 +32,36 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
 
     paper-spinner {
       position: absolute;
-      top: 15px;
-      left: 186px;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+    }
+
+    .wrap {
+      margin-bottom: 24px;
     }
 
     vaadin-date-picker {
       --vaadin-date-picker-clear-icon: {
         display: none;
       }
-      margin-bottom: 24px;
       max-width: 200px;
+      margin-right: 16px;
+    }
+
+    paper-dropdown-menu {
+      max-width: 100px;
+      margin-right: 16px;
+    }
+
+    paper-item {
+      cursor: pointer;
+    }
+
+    ha-entity-picker {
+      display: inline-block;
+      width: 100%;
+      max-width: 400px;
     }
 
     [hidden] {
@@ -53,6 +74,8 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
       is-loading='{{isLoading}}'
       entries='{{entries}}'
       filter-date='[[_computeFilterDate(_currentDate)]]'
+      filter-period='[[_computeFilterDays(_periodIndex)]]'
+      filter-entity='[[entityId]]'
     ></ha-logbook-data>
 
     <app-header-layout has-scrolling-region>
@@ -75,14 +98,38 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
           alt="[[localize('ui.common.loading')]]"
         ></paper-spinner>
 
-        <vaadin-date-picker
-          id='picker'
-          value='{{_currentDate}}'
-          label="[[localize('ui.panel.logbook.showing_entries')]]"
-          disabled='[[isLoading]]'
-          required
-        ></vaadin-date-picker>
-
+        <div class="flex layout horizontal wrap">
+          <vaadin-date-picker
+            id='picker'
+            value='{{_currentDate}}'
+            label="[[localize('ui.panel.logbook.showing_entries')]]"
+            disabled='[[isLoading]]'
+            required
+          ></vaadin-date-picker>
+  
+          <paper-dropdown-menu
+            label-float
+            label="[[localize('ui.panel.logbook.period')]]"
+            disabled='[[isLoading]]'
+          >
+            <paper-listbox
+              slot="dropdown-content"
+              selected="{{_periodIndex}}"
+            >
+              <paper-item>[[localize('ui.duration.day', 'count', 1)]]</paper-item>
+              <paper-item>[[localize('ui.duration.day', 'count', 3)]]</paper-item>
+              <paper-item>[[localize('ui.duration.week', 'count', 1)]]</paper-item>
+            </paper-listbox>
+          </paper-dropdown-menu>
+  
+          <ha-entity-picker
+            hass="[[hass]]"
+            value="{{_entityId}}"
+            label="[[localize('ui.components.entity.entity-picker.entity')]]"
+            disabled='[[isLoading]]'
+            on-change='_entityPicked'
+          ></ha-entity-picker>
+        </div>
 
         <ha-logbook hass='[[hass]]' entries="[[entries]]" hidden$='[[isLoading]]'></ha-logbook>
       </div>
@@ -116,6 +163,22 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
         }
       },
 
+      _periodIndex: {
+        type: Number,
+        value: 0,
+      },
+
+      _entityId: {
+        type: String,
+        value: '',
+      },
+
+      entityId: {
+        type: String,
+        value: '',
+        readOnly: true,
+      },
+
       isLoading: {
         type: Boolean,
       },
@@ -145,6 +208,20 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
     return new Date(parts[0], parts[1], parts[2]).toISOString();
   }
 
+  _computeFilterDays(periodIndex) {
+    switch (periodIndex) {
+      case 1:
+        return 3;
+      case 2:
+        return 7;
+      default: return 1;
+    }
+  }
+
+  _entityPicked(ev) {
+    this._setEntityId(ev.target.value);
+  }
+
   refreshLogbook() {
     this.shadowRoot.querySelector('ha-logbook-data').refreshLogbook();
   }