Improve statistics graph editor (#13630)

This commit is contained in:
Erik Montnemery 2022-09-16 13:48:10 +02:00 committed by GitHub
parent bc62e9372b
commit bb0529ecd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 152 additions and 28 deletions

View File

@ -3,6 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/ensure-array";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { stringCompare } from "../../common/string/compare"; import { stringCompare } from "../../common/string/compare";
@ -39,22 +40,20 @@ export class HaStatisticPicker extends LitElement {
type: Array, type: Array,
attribute: "include-statistics-unit-of-measurement", attribute: "include-statistics-unit-of-measurement",
}) })
public includeStatisticsUnitOfMeasurement?: string[]; public includeStatisticsUnitOfMeasurement?: string | string[];
/** /**
* Show only statistics displayed with these units of measurements. * Show only statistics displayed with these units of measurements.
* @type {Array}
* @attr include-display-unit-of-measurement * @attr include-display-unit-of-measurement
*/ */
@property({ type: Array, attribute: "include-display-unit-of-measurement" }) @property({ attribute: "include-display-unit-of-measurement" })
public includeDisplayUnitOfMeasurement?: string[]; public includeDisplayUnitOfMeasurement?: string | string[];
/** /**
* Show only statistics with these device classes. * Show only statistics with these device classes.
* @type {Array}
* @attr include-device-classes * @attr include-device-classes
*/ */
@property({ type: Array, attribute: "include-device-classes" }) @property({ attribute: "include-device-classes" })
public includeDeviceClasses?: string[]; public includeDeviceClasses?: string[];
/** /**
@ -97,8 +96,8 @@ export class HaStatisticPicker extends LitElement {
private _getStatistics = memoizeOne( private _getStatistics = memoizeOne(
( (
statisticIds: StatisticsMetaData[], statisticIds: StatisticsMetaData[],
includeStatisticsUnitOfMeasurement?: string[], includeStatisticsUnitOfMeasurement?: string | string[],
includeDisplayUnitOfMeasurement?: string[], includeDisplayUnitOfMeasurement?: string | string[],
includeDeviceClasses?: string[], includeDeviceClasses?: string[],
entitiesOnly?: boolean entitiesOnly?: boolean
): Array<{ id: string; name: string; state?: HassEntity }> => { ): Array<{ id: string; name: string; state?: HassEntity }> => {
@ -114,17 +113,15 @@ export class HaStatisticPicker extends LitElement {
} }
if (includeStatisticsUnitOfMeasurement) { if (includeStatisticsUnitOfMeasurement) {
const includeUnits = ensureArray(includeStatisticsUnitOfMeasurement);
statisticIds = statisticIds.filter((meta) => statisticIds = statisticIds.filter((meta) =>
includeStatisticsUnitOfMeasurement.includes( includeUnits.includes(meta.statistics_unit_of_measurement)
meta.statistics_unit_of_measurement
)
); );
} }
if (includeDisplayUnitOfMeasurement) { if (includeDisplayUnitOfMeasurement) {
const includeUnits = ensureArray(includeDisplayUnitOfMeasurement);
statisticIds = statisticIds.filter((meta) => statisticIds = statisticIds.filter((meta) =>
includeDisplayUnitOfMeasurement.includes( includeUnits.includes(meta.display_unit_of_measurement)
meta.display_unit_of_measurement
)
); );
} }

View File

@ -22,11 +22,52 @@ class HaStatisticsPicker extends LitElement {
@property({ attribute: "pick-statistic-label" }) @property({ attribute: "pick-statistic-label" })
public pickStatisticLabel?: string; public pickStatisticLabel?: string;
/**
* Show only statistics natively stored with these units of measurements.
* @attr include-statistics-unit-of-measurement
*/
@property({
attribute: "include-statistics-unit-of-measurement",
})
public includeStatisticsUnitOfMeasurement?: string[] | string;
/**
* Show only statistics displayed with these units of measurements.
* @attr include-display-unit-of-measurement
*/
@property({ attribute: "include-display-unit-of-measurement" })
public includeDisplayUnitOfMeasurement?: string[] | string;
/**
* Ignore filtering of statistics type and units when only a single statistic is selected.
* @type {boolean}
* @attr ignore-restrictions-on-first-statistic
*/
@property({
type: Boolean,
attribute: "ignore-restrictions-on-first-statistic",
})
public ignoreRestrictionsOnFirstStatistic = false;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }
const ignoreRestriction =
this.ignoreRestrictionsOnFirstStatistic &&
this._currentStatistics.length <= 1;
const includeDisplayUnitCurrent = ignoreRestriction
? undefined
: this.includeDisplayUnitOfMeasurement;
const includeStatisticsUnitCurrent = ignoreRestriction
? undefined
: this.includeStatisticsUnitOfMeasurement;
const includeStatisticTypesCurrent = ignoreRestriction
? undefined
: this.statisticTypes;
return html` return html`
${this._currentStatistics.map( ${this._currentStatistics.map(
(statisticId) => html` (statisticId) => html`
@ -34,8 +75,10 @@ class HaStatisticsPicker extends LitElement {
<ha-statistic-picker <ha-statistic-picker
.curValue=${statisticId} .curValue=${statisticId}
.hass=${this.hass} .hass=${this.hass}
.includeDisplayUnitOfMeasurement=${includeDisplayUnitCurrent}
.includeStatisticsUnitOfMeasurement=${includeStatisticsUnitCurrent}
.value=${statisticId} .value=${statisticId}
.statisticTypes=${this.statisticTypes} .statisticTypes=${includeStatisticTypesCurrent}
.statisticIds=${this.statisticIds} .statisticIds=${this.statisticIds}
.label=${this.pickedStatisticLabel} .label=${this.pickedStatisticLabel}
@value-changed=${this._statisticChanged} @value-changed=${this._statisticChanged}
@ -46,6 +89,10 @@ class HaStatisticsPicker extends LitElement {
<div> <div>
<ha-statistic-picker <ha-statistic-picker
.hass=${this.hass} .hass=${this.hass}
.includeDisplayUnitOfMeasurement=${this
.includeDisplayUnitOfMeasurement}
.includeStatisticsUnitOfMeasurement=${this
.includeStatisticsUnitOfMeasurement}
.statisticTypes=${this.statisticTypes} .statisticTypes=${this.statisticTypes}
.statisticIds=${this.statisticIds} .statisticIds=${this.statisticIds}
.label=${this.pickStatisticLabel} .label=${this.pickStatisticLabel}

View File

@ -184,6 +184,22 @@ export const statisticsHaveType = (
type: StatisticType type: StatisticType
) => stats.some((stat) => stat[type] !== null); ) => stats.some((stat) => stat[type] !== null);
const mean_stat_types: readonly StatisticType[] = ["mean", "min", "max"];
const sum_stat_types: readonly StatisticType[] = ["sum"];
export const statisticsMetaHasType = (
metadata: StatisticsMetaData,
type: StatisticType
) => {
if (mean_stat_types.includes(type) && metadata.has_mean) {
return true;
}
if (sum_stat_types.includes(type) && metadata.has_sum) {
return true;
}
return false;
};
export const adjustStatisticsSum = ( export const adjustStatisticsSum = (
hass: HomeAssistant, hass: HomeAssistant,
statistic_id: string, statistic_id: string,

View File

@ -1,5 +1,12 @@
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { import {
@ -23,6 +30,12 @@ import { processConfigEntities } from "../../common/process-config-entities";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { entitiesConfigStruct } from "../structs/entities-struct"; import { entitiesConfigStruct } from "../structs/entities-struct";
import {
getStatisticMetadata,
StatisticsMetaData,
statisticsMetaHasType,
} from "../../../../data/recorder";
import { deepEqual } from "../../../../common/util/deep-equal";
const statTypeStruct = union([ const statTypeStruct = union([
literal("sum"), literal("sum"),
@ -51,6 +64,13 @@ const cardConfigStruct = assign(
); );
const periods = ["5minute", "hour", "day", "month"] as const; const periods = ["5minute", "hour", "day", "month"] as const;
const stat_types = ["mean", "min", "max", "sum"] as const;
const stat_type_labels = {
mean: "Mean",
min: "Min",
max: "Max",
sum: "Sum",
} as const;
@customElement("hui-statistics-graph-card-editor") @customElement("hui-statistics-graph-card-editor")
export class HuiStatisticsGraphCardEditor export class HuiStatisticsGraphCardEditor
@ -63,6 +83,8 @@ export class HuiStatisticsGraphCardEditor
@state() private _configEntities?: string[]; @state() private _configEntities?: string[];
@state() private _metaDatas?: StatisticsMetaData[];
public setConfig(config: StatisticsGraphCardConfig): void { public setConfig(config: StatisticsGraphCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
this._config = config; this._config = config;
@ -71,8 +93,29 @@ export class HuiStatisticsGraphCardEditor
: []; : [];
} }
private _getStatisticsMetaData = async (statisticIds?: string[]) => {
this._metaDatas = await getStatisticMetadata(
this.hass!,
statisticIds || []
);
};
public willUpdate(changedProps: PropertyValues) {
if (
changedProps.has("_configEntities") &&
!deepEqual(this._configEntities, changedProps.get("_configEntities"))
) {
this._metaDatas = undefined;
this._getStatisticsMetaData(this._configEntities);
}
}
private _schema = memoizeOne( private _schema = memoizeOne(
(localize: LocalizeFunc) => (
localize: LocalizeFunc,
statisticIds: string[] | undefined,
metaDatas: StatisticsMetaData[] | undefined
) =>
[ [
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ {
@ -89,6 +132,13 @@ export class HuiStatisticsGraphCardEditor
label: localize( label: localize(
`ui.panel.lovelace.editor.card.statistics-graph.periods.${period}` `ui.panel.lovelace.editor.card.statistics-graph.periods.${period}`
), ),
disabled:
period === "5minute" &&
// External statistics don't support 5-minute statistics.
// External statistics is formatted as <domain>:<object_id>
statisticIds?.some((statistic_id) =>
statistic_id.includes(":")
),
})), })),
}, },
}, },
@ -101,13 +151,20 @@ export class HuiStatisticsGraphCardEditor
{ {
name: "stat_types", name: "stat_types",
required: true, required: true,
type: "multi_select", selector: {
options: [ select: {
["mean", "Mean"], multiple: true,
["min", "Min"], options: stat_types.map((stat_type) => ({
["max", "Max"], value: stat_type,
["sum", "Sum"], label: stat_type_labels[stat_type],
], disabled:
!metaDatas ||
!metaDatas?.every((metaData) =>
statisticsMetaHasType(metaData, stat_type)
),
})),
},
},
}, },
{ {
name: "chart_type", name: "chart_type",
@ -128,19 +185,24 @@ export class HuiStatisticsGraphCardEditor
return html``; return html``;
} }
const schema = this._schema(this.hass.localize); const schema = this._schema(
const stat_types = this._config!.stat_types this.hass.localize,
this._configEntities,
this._metaDatas
);
const configured_stat_types = this._config!.stat_types
? Array.isArray(this._config!.stat_types) ? Array.isArray(this._config!.stat_types)
? this._config!.stat_types ? this._config!.stat_types
: [this._config!.stat_types] : [this._config!.stat_types]
: ["mean", "min", "max", "sum"]; : stat_types;
const data = { const data = {
chart_type: "line", chart_type: "line",
period: "hour", period: "hour",
days_to_show: 30, days_to_show: 30,
...this._config, ...this._config,
stat_types, stat_types: configured_stat_types,
}; };
const displayUnit = this._metaDatas?.[0]?.display_unit_of_measurement;
return html` return html`
<ha-form <ha-form
@ -154,6 +216,8 @@ export class HuiStatisticsGraphCardEditor
.hass=${this.hass} .hass=${this.hass}
.pickStatisticLabel=${`Add a statistic`} .pickStatisticLabel=${`Add a statistic`}
.pickedStatisticLabel=${`Statistic`} .pickedStatisticLabel=${`Statistic`}
.includeDisplayUnitOfMeasurement=${displayUnit}
.ignoreRestrictionsOnFirstStatistic=${true}
.value=${this._configEntities} .value=${this._configEntities}
.configValue=${"entities"} .configValue=${"entities"}
@value-changed=${this._entitiesChanged} @value-changed=${this._entitiesChanged}