Remove history use_include_order configuration option (#86365)

This commit is contained in:
J. Nick Koston
2023-01-22 06:43:05 -10:00
committed by GitHub
parent 332d3e0f19
commit 0b0e977ce9
6 changed files with 51 additions and 181 deletions

View File

@@ -34,13 +34,9 @@ from homeassistant.helpers.typing import ConfigType
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from . import websocket_api from . import websocket_api
from .const import ( from .const import DOMAIN
DOMAIN,
HISTORY_ENTITIES_FILTER,
HISTORY_FILTERS,
HISTORY_USE_INCLUDE_ORDER,
)
from .helpers import entities_may_have_state_changes_after from .helpers import entities_may_have_state_changes_after
from .models import HistoryConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -48,8 +44,11 @@ CONF_ORDER = "use_include_order"
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend( DOMAIN: vol.All(
{vol.Optional(CONF_ORDER, default=False): cv.boolean} cv.deprecated(CONF_ORDER),
INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend(
{vol.Optional(CONF_ORDER, default=False): cv.boolean}
),
) )
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
@@ -67,18 +66,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
possible_merged_entities_filter = convert_include_exclude_filter(merged_filter) possible_merged_entities_filter = convert_include_exclude_filter(merged_filter)
sqlalchemy_filter = None
entity_filter = None
if not possible_merged_entities_filter.empty_filter: if not possible_merged_entities_filter.empty_filter:
hass.data[ sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(conf)
HISTORY_FILTERS entity_filter = possible_merged_entities_filter
] = filters = sqlalchemy_filter_from_include_exclude_conf(conf)
hass.data[HISTORY_ENTITIES_FILTER] = possible_merged_entities_filter
else:
hass.data[HISTORY_FILTERS] = filters = None
hass.data[HISTORY_ENTITIES_FILTER] = None
hass.data[HISTORY_USE_INCLUDE_ORDER] = use_include_order = conf.get(CONF_ORDER) hass.data[DOMAIN] = HistoryConfig(sqlalchemy_filter, entity_filter)
hass.http.register_view(HistoryPeriodView(sqlalchemy_filter))
hass.http.register_view(HistoryPeriodView(filters, use_include_order))
frontend.async_register_built_in_panel(hass, "history", "history", "hass:chart-box") frontend.async_register_built_in_panel(hass, "history", "history", "hass:chart-box")
websocket_api.async_setup(hass) websocket_api.async_setup(hass)
return True return True
@@ -91,10 +86,9 @@ class HistoryPeriodView(HomeAssistantView):
name = "api:history:view-period" name = "api:history:view-period"
extra_urls = ["/api/history/period/{datetime}"] extra_urls = ["/api/history/period/{datetime}"]
def __init__(self, filters: Filters | None, use_include_order: bool) -> None: def __init__(self, filters: Filters | None) -> None:
"""Initialize the history period view.""" """Initialize the history period view."""
self.filters = filters self.filters = filters
self.use_include_order = use_include_order
async def get( async def get(
self, request: web.Request, datetime: str | None = None self, request: web.Request, datetime: str | None = None
@@ -194,15 +188,4 @@ class HistoryPeriodView(HomeAssistantView):
"Extracted %d states in %fs", sum(map(len, states.values())), elapsed "Extracted %d states in %fs", sum(map(len, states.values())), elapsed
) )
# Optionally reorder the result to respect the ordering given return self.json(list(states.values()))
# by any entities explicitly included in the configuration.
if not self.filters or not self.use_include_order:
return self.json(list(states.values()))
sorted_result = [
states.pop(order_entity)
for order_entity in self.filters.included_entities
if order_entity in states
]
sorted_result.extend(list(states.values()))
return self.json(sorted_result)

View File

@@ -1,9 +1,7 @@
"""History integration constants.""" """History integration constants."""
DOMAIN = "history" DOMAIN = "history"
HISTORY_FILTERS = "history_filters"
HISTORY_ENTITIES_FILTER = "history_entities_filter"
HISTORY_USE_INCLUDE_ORDER = "history_use_include_order"
EVENT_COALESCE_TIME = 0.35 EVENT_COALESCE_TIME = 0.35
MAX_PENDING_HISTORY_STATES = 2048 MAX_PENDING_HISTORY_STATES = 2048

View File

@@ -0,0 +1,15 @@
"""Models for the history integration."""
from __future__ import annotations
from dataclasses import dataclass
from homeassistant.components.recorder.filters import Filters
from homeassistant.helpers.entityfilter import EntityFilter
@dataclass
class HistoryConfig:
"""Configuration for the history integration."""
sqlalchemy_filter: Filters | None = None
entity_filter: EntityFilter | None = None

View File

@@ -38,14 +38,9 @@ from homeassistant.helpers.event import (
from homeassistant.helpers.json import JSON_DUMP from homeassistant.helpers.json import JSON_DUMP
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import ( from .const import DOMAIN, EVENT_COALESCE_TIME, MAX_PENDING_HISTORY_STATES
EVENT_COALESCE_TIME,
HISTORY_ENTITIES_FILTER,
HISTORY_FILTERS,
HISTORY_USE_INCLUDE_ORDER,
MAX_PENDING_HISTORY_STATES,
)
from .helpers import entities_may_have_state_changes_after from .helpers import entities_may_have_state_changes_after
from .models import HistoryConfig
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -75,38 +70,27 @@ def _ws_get_significant_states(
end_time: dt | None, end_time: dt | None,
entity_ids: list[str] | None, entity_ids: list[str] | None,
filters: Filters | None, filters: Filters | None,
use_include_order: bool | None,
include_start_time_state: bool, include_start_time_state: bool,
significant_changes_only: bool, significant_changes_only: bool,
minimal_response: bool, minimal_response: bool,
no_attributes: bool, no_attributes: bool,
) -> str: ) -> str:
"""Fetch history significant_states and convert them to json in the executor.""" """Fetch history significant_states and convert them to json in the executor."""
states = history.get_significant_states(
hass,
start_time,
end_time,
entity_ids,
filters,
include_start_time_state,
significant_changes_only,
minimal_response,
no_attributes,
True,
)
if not use_include_order or not filters:
return JSON_DUMP(messages.result_message(msg_id, states))
return JSON_DUMP( return JSON_DUMP(
messages.result_message( messages.result_message(
msg_id, msg_id,
{ history.get_significant_states(
order_entity: states.pop(order_entity) hass,
for order_entity in filters.included_entities start_time,
if order_entity in states end_time,
} entity_ids,
| states, filters,
include_start_time_state,
significant_changes_only,
minimal_response,
no_attributes,
True,
),
) )
) )
@@ -166,6 +150,7 @@ async def ws_get_history_during_period(
significant_changes_only = msg["significant_changes_only"] significant_changes_only = msg["significant_changes_only"]
minimal_response = msg["minimal_response"] minimal_response = msg["minimal_response"]
history_config: HistoryConfig = hass.data[DOMAIN]
connection.send_message( connection.send_message(
await get_instance(hass).async_add_executor_job( await get_instance(hass).async_add_executor_job(
@@ -175,8 +160,7 @@ async def ws_get_history_during_period(
start_time, start_time,
end_time, end_time,
entity_ids, entity_ids,
hass.data[HISTORY_FILTERS], history_config.sqlalchemy_filter,
hass.data[HISTORY_USE_INCLUDE_ORDER],
include_start_time_state, include_start_time_state,
significant_changes_only, significant_changes_only,
minimal_response, minimal_response,
@@ -413,9 +397,11 @@ async def ws_stream(
utc_now = dt_util.utcnow() utc_now = dt_util.utcnow()
filters: Filters | None = None filters: Filters | None = None
entities_filter: EntityFilter | None = None entities_filter: EntityFilter | None = None
if not entity_ids: if not entity_ids:
filters = hass.data[HISTORY_FILTERS] history_config: HistoryConfig = hass.data[DOMAIN]
entities_filter = hass.data[HISTORY_ENTITIES_FILTER] filters = history_config.sqlalchemy_filter
entities_filter = history_config.entity_filter
if start_time := dt_util.parse_datetime(start_time_str): if start_time := dt_util.parse_datetime(start_time_str):
start_time = dt_util.as_utc(start_time) start_time = dt_util.as_utc(start_time)

View File

@@ -1295,59 +1295,3 @@ async def test_history_during_period_bad_end_time(recorder_mock, hass, hass_ws_c
response = await client.receive_json() response = await client.receive_json()
assert not response["success"] assert not response["success"]
assert response["error"]["code"] == "invalid_end_time" assert response["error"]["code"] == "invalid_end_time"
async def test_history_during_period_with_use_include_order(
recorder_mock, hass, hass_ws_client
):
"""Test history_during_period."""
now = dt_util.utcnow()
sort_order = ["sensor.two", "sensor.four", "sensor.one"]
await async_setup_component(
hass,
"history",
{
history.DOMAIN: {
history.CONF_ORDER: True,
CONF_INCLUDE: {
CONF_ENTITIES: sort_order,
CONF_DOMAINS: ["sensor"],
},
}
},
)
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "changed"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.four", "off", attributes={"any": "again"})
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "history/history_during_period",
"start_time": now.isoformat(),
"include_start_time_state": True,
"significant_changes_only": False,
"no_attributes": True,
"minimal_response": True,
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 1
assert list(response["result"]) == [
*sort_order,
"sensor.three",
]

View File

@@ -446,62 +446,6 @@ async def test_history_during_period_bad_end_time(recorder_mock, hass, hass_ws_c
assert response["error"]["code"] == "invalid_end_time" assert response["error"]["code"] == "invalid_end_time"
async def test_history_during_period_with_use_include_order(
recorder_mock, hass, hass_ws_client
):
"""Test history_during_period."""
now = dt_util.utcnow()
sort_order = ["sensor.two", "sensor.four", "sensor.one"]
await async_setup_component(
hass,
"history",
{
history.DOMAIN: {
history.CONF_ORDER: True,
CONF_INCLUDE: {
CONF_ENTITIES: sort_order,
CONF_DOMAINS: ["sensor"],
},
}
},
)
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "changed"})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.four", "off", attributes={"any": "again"})
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "history/history_during_period",
"start_time": now.isoformat(),
"include_start_time_state": True,
"significant_changes_only": False,
"no_attributes": True,
"minimal_response": True,
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 1
assert list(response["result"]) == [
*sort_order,
"sensor.three",
]
async def test_history_stream_historical_only(recorder_mock, hass, hass_ws_client): async def test_history_stream_historical_only(recorder_mock, hass, hass_ws_client):
"""Test history stream.""" """Test history stream."""
now = dt_util.utcnow() now = dt_util.utcnow()