mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Convert history header/footer to use streaming history (#15136)
* Add support for streaming history * Add support for streaming history * Add support for streaming history * Add support for streaming history * fixes * cleanup * redraw * naming is hard * drop cached history * backport * Convert history header/footer to use streaming history needs #15112 * Update src/data/history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Update src/data/history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * review * review * review * review * review * review * review * review * adjust * Revert "adjust" This reverts commit 6ba31da4a5a619a0da1bfbcfe18723de595e19aa. * move setInterval Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
815d4c165d
commit
2b2dd74672
@ -1,4 +1,5 @@
|
||||
import { strokeWidth } from "../../../../data/graph";
|
||||
import { EntityHistoryState } from "../../../../data/history";
|
||||
|
||||
const average = (items: any[]): number =>
|
||||
items.reduce((sum, entry) => sum + parseFloat(entry.state), 0) / items.length;
|
||||
@ -105,3 +106,25 @@ export const coordinates = (
|
||||
|
||||
return calcPoints(history, hours, width, detail, min, max);
|
||||
};
|
||||
|
||||
interface NumericEntityHistoryState {
|
||||
state: number;
|
||||
last_changed: number;
|
||||
}
|
||||
|
||||
export const coordinatesMinimalResponseCompressedState = (
|
||||
history: EntityHistoryState[],
|
||||
hours: number,
|
||||
width: number,
|
||||
detail: number,
|
||||
limits?: { min?: number; max?: number }
|
||||
): number[][] | undefined => {
|
||||
const numericHistory: NumericEntityHistoryState[] = history.map((item) => ({
|
||||
state: Number(item.s),
|
||||
// With minimal response and compressed state, we don't have last_changed,
|
||||
// so we use last_updated since its always the same as last_changed since
|
||||
// we already filtered out states that are the same.
|
||||
last_changed: item.lu * 1000,
|
||||
}));
|
||||
return coordinates(numericHistory, hours, width, detail, limits);
|
||||
};
|
||||
|
@ -9,18 +9,18 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import { fetchRecent } from "../../../data/history";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { coordinates } from "../common/graph/coordinates";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { coordinatesMinimalResponseCompressedState } from "../common/graph/coordinates";
|
||||
import "../components/hui-graph-base";
|
||||
import { LovelaceHeaderFooter, LovelaceHeaderFooterEditor } from "../types";
|
||||
import { GraphHeaderFooterConfig } from "./types";
|
||||
|
||||
const MINUTE = 60000;
|
||||
const HOUR = MINUTE * 60;
|
||||
const HOUR = 60 * MINUTE;
|
||||
const includeDomains = ["counter", "input_number", "number", "sensor"];
|
||||
|
||||
@customElement("hui-graph-header-footer")
|
||||
@ -66,11 +66,11 @@ export class HuiGraphHeaderFooter
|
||||
|
||||
@state() private _coordinates?: number[][];
|
||||
|
||||
private _date?: Date;
|
||||
private _error?: string;
|
||||
|
||||
private _stateHistory?: HassEntity[];
|
||||
private _interval?: number;
|
||||
|
||||
private _fetching = false;
|
||||
private _subscribed?: Promise<(() => Promise<void>) | void>;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
@ -104,6 +104,10 @@ export class HuiGraphHeaderFooter
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (this._error) {
|
||||
return html`<div class="errors">${this._error}</div>`;
|
||||
}
|
||||
|
||||
if (!this._coordinates) {
|
||||
return html`
|
||||
<div class="container">
|
||||
@ -125,89 +129,91 @@ export class HuiGraphHeaderFooter
|
||||
`;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._subscribeHistoryTimeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribeHistoryTimeWindow();
|
||||
}
|
||||
|
||||
private _subscribeHistoryTimeWindow() {
|
||||
if (!isComponentLoaded(this.hass!, "history") || this._subscribed) {
|
||||
return;
|
||||
}
|
||||
this._subscribed = subscribeHistoryStatesTimeWindow(
|
||||
this.hass!,
|
||||
(combinedHistory) => {
|
||||
if (!this._subscribed) {
|
||||
// Message came in before we had a chance to unload
|
||||
return;
|
||||
}
|
||||
this._coordinates =
|
||||
coordinatesMinimalResponseCompressedState(
|
||||
combinedHistory[this._config!.entity],
|
||||
this._config!.hours_to_show!,
|
||||
500,
|
||||
this._config!.detail!,
|
||||
this._config!.limits
|
||||
) || [];
|
||||
},
|
||||
this._config!.hours_to_show!,
|
||||
[this._config!.entity]
|
||||
).catch((err) => {
|
||||
this._subscribed = undefined;
|
||||
this._error = err;
|
||||
});
|
||||
this._setRedrawTimer();
|
||||
}
|
||||
|
||||
private _redrawGraph() {
|
||||
if (this._coordinates) {
|
||||
this._coordinates = [...this._coordinates];
|
||||
}
|
||||
}
|
||||
|
||||
private _setRedrawTimer() {
|
||||
// redraw the graph every minute to update the time axis
|
||||
clearInterval(this._interval);
|
||||
this._interval = window.setInterval(
|
||||
() => this._redrawGraph(),
|
||||
this._config!.hours_to_show! > 24 ? HOUR : MINUTE
|
||||
);
|
||||
}
|
||||
|
||||
private _unsubscribeHistoryTimeWindow() {
|
||||
clearInterval(this._interval);
|
||||
if (!this._subscribed) {
|
||||
return;
|
||||
}
|
||||
this._subscribed.then((unsubscribe) => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
this._subscribed = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
(this._fetching && !changedProps.has("_config"))
|
||||
) {
|
||||
if (!this._config || !this.hass || !changedProps.has("_config")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("_config")) {
|
||||
const oldConfig = changedProps.get("_config") as GraphHeaderFooterConfig;
|
||||
if (!oldConfig || oldConfig.entity !== this._config.entity) {
|
||||
this._stateHistory = [];
|
||||
}
|
||||
|
||||
this._getCoordinates();
|
||||
} else if (Date.now() - this._date!.getTime() >= MINUTE) {
|
||||
this._getCoordinates();
|
||||
const oldConfig = changedProps.get("_config") as GraphHeaderFooterConfig;
|
||||
if (
|
||||
!oldConfig ||
|
||||
!this._subscribed ||
|
||||
oldConfig.entity !== this._config.entity
|
||||
) {
|
||||
this._unsubscribeHistoryTimeWindow();
|
||||
this._subscribeHistoryTimeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private async _getCoordinates(): Promise<void> {
|
||||
this._fetching = true;
|
||||
const endTime = new Date();
|
||||
const startTime =
|
||||
!this._date || !this._stateHistory?.length
|
||||
? new Date(
|
||||
new Date().setHours(
|
||||
endTime.getHours() - this._config!.hours_to_show!
|
||||
)
|
||||
)
|
||||
: this._date;
|
||||
|
||||
if (this._stateHistory!.length) {
|
||||
const inHoursToShow: HassEntity[] = [];
|
||||
const outHoursToShow: HassEntity[] = [];
|
||||
// Split into inside and outside of "hours to show".
|
||||
this._stateHistory!.forEach((entity) =>
|
||||
(endTime.getTime() - new Date(entity.last_changed).getTime() <=
|
||||
this._config!.hours_to_show! * HOUR
|
||||
? inHoursToShow
|
||||
: outHoursToShow
|
||||
).push(entity)
|
||||
);
|
||||
|
||||
if (outHoursToShow.length) {
|
||||
// If we have values that are now outside of "hours to show", re-add the last entry. This could e.g. be
|
||||
// the "initial state" from the history backend. Without it, it would look like there is no history data
|
||||
// at the start at all in the database = graph would start suddenly instead of on the left side of the card.
|
||||
inHoursToShow.push(outHoursToShow[outHoursToShow.length - 1]);
|
||||
}
|
||||
this._stateHistory = inHoursToShow;
|
||||
}
|
||||
|
||||
const stateHistory = await fetchRecent(
|
||||
this.hass!,
|
||||
this._config!.entity,
|
||||
startTime,
|
||||
endTime,
|
||||
Boolean(this._stateHistory!.length)
|
||||
);
|
||||
|
||||
if (stateHistory.length && stateHistory[0].length) {
|
||||
this._stateHistory!.push(...stateHistory[0]);
|
||||
}
|
||||
|
||||
this._coordinates =
|
||||
coordinates(
|
||||
this._stateHistory,
|
||||
this._config!.hours_to_show!,
|
||||
500,
|
||||
this._config!.detail!,
|
||||
this._config!.limits
|
||||
) || [];
|
||||
|
||||
this._date = endTime;
|
||||
this._fetching = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-circular-progress {
|
||||
|
Loading…
x
Reference in New Issue
Block a user