From 0ca199d8d03278916588cf7ed64c783b38c9b5ab Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Jun 2021 21:32:30 +0200 Subject: [PATCH] Add WS API for listing available statistic ids (#51984) * Add WS API for listing available statistic ids * Update homeassistant/components/history/__init__.py Co-authored-by: Paulus Schoutsen Co-authored-by: Bram Kragten --- homeassistant/components/history/__init__.py | 26 ++++++++- .../components/recorder/statistics.py | 24 +++++++++ tests/components/history/test_init.py | 54 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 2b30936d9ed..79d288398c5 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -14,7 +14,10 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.http import HomeAssistantView from homeassistant.components.recorder import history, models as history_models -from homeassistant.components.recorder.statistics import statistics_during_period +from homeassistant.components.recorder.statistics import ( + list_statistic_ids, + statistics_during_period, +) from homeassistant.components.recorder.util import session_scope from homeassistant.const import ( CONF_DOMAINS, @@ -105,6 +108,7 @@ async def async_setup(hass, config): hass.components.websocket_api.async_register_command( ws_get_statistics_during_period ) + hass.components.websocket_api.async_register_command(ws_get_list_statistic_ids) return True @@ -157,6 +161,26 @@ async def ws_get_statistics_during_period( connection.send_result(msg["id"], statistics) +@websocket_api.websocket_command( + { + vol.Required("type"): "history/list_statistic_ids", + vol.Optional("statistic_type"): str, + } +) +@websocket_api.require_admin +@websocket_api.async_response +async def ws_get_list_statistic_ids( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Fetch a list of available statistic_id.""" + statistics = await hass.async_add_executor_job( + list_statistic_ids, + hass, + msg.get("statistic_type"), + ) + connection.send_result(msg["id"], {"statistic_ids": statistics}) + + class HistoryPeriodView(HomeAssistantView): """Handle history period requests.""" diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 1e7f37f9f47..cd2b72ad43a 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -30,6 +30,10 @@ QUERY_STATISTICS = [ Statistics.sum, ] +QUERY_STATISTIC_IDS = [ + Statistics.statistic_id, +] + STATISTICS_BAKERY = "recorder_statistics_bakery" _LOGGER = logging.getLogger(__name__) @@ -74,6 +78,26 @@ def compile_statistics(instance: Recorder, start: datetime.datetime) -> bool: return True +def list_statistic_ids(hass, statistic_type=None): + """Return statistic_ids.""" + with session_scope(hass=hass) as session: + baked_query = hass.data[STATISTICS_BAKERY]( + lambda session: session.query(*QUERY_STATISTIC_IDS).distinct() + ) + + if statistic_type == "mean": + baked_query += lambda q: q.filter(Statistics.mean.isnot(None)) + if statistic_type == "sum": + baked_query += lambda q: q.filter(Statistics.sum.isnot(None)) + + baked_query += lambda q: q.order_by(Statistics.statistic_id) + + statistic_ids = [] + result = execute(baked_query(session)) + statistic_ids = [statistic_id[0] for statistic_id in result] + return statistic_ids + + def statistics_during_period(hass, start_time, end_time=None, statistic_ids=None): """Return states changes during UTC period start_time - end_time.""" with session_scope(hass=hass) as session: diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index d47d3d80393..32205512474 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -938,3 +938,57 @@ async def test_statistics_during_period_bad_end_time(hass, hass_ws_client): response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "invalid_end_time" + + +async def test_list_statistic_ids(hass, hass_ws_client): + """Test list_statistic_ids.""" + now = dt_util.utcnow() + + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "history", {"history": {}}) + await async_setup_component(hass, "sensor", {}) + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + hass.states.async_set( + "sensor.test", + 10, + attributes={"device_class": "temperature", "state_class": "measurement"}, + ) + await hass.async_block_till_done() + + await hass.async_add_executor_job(trigger_db_commit, hass) + await hass.async_block_till_done() + + client = await hass_ws_client() + await client.send_json({"id": 1, "type": "history/list_statistic_ids"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"statistic_ids": []} + + hass.data[recorder.DATA_INSTANCE].do_adhoc_statistics(period="hourly", start=now) + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + await client.send_json({"id": 2, "type": "history/list_statistic_ids"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"statistic_ids": ["sensor.test"]} + + await client.send_json( + {"id": 3, "type": "history/list_statistic_ids", "statistic_type": "dogs"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"statistic_ids": ["sensor.test"]} + + await client.send_json( + {"id": 4, "type": "history/list_statistic_ids", "statistic_type": "mean"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"statistic_ids": ["sensor.test"]} + + await client.send_json( + {"id": 5, "type": "history/list_statistic_ids", "statistic_type": "sum"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"statistic_ids": []}