diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 1cfabba3d5..ebf129efaa 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -3,6 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { ensureArray } from "../../common/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; import { computeStateName } from "../../common/entity/compute_state_name"; import { stringCompare } from "../../common/string/compare"; @@ -39,22 +40,20 @@ export class HaStatisticPicker extends LitElement { type: Array, attribute: "include-statistics-unit-of-measurement", }) - public includeStatisticsUnitOfMeasurement?: string[]; + public includeStatisticsUnitOfMeasurement?: string | string[]; /** * Show only statistics displayed with these units of measurements. - * @type {Array} * @attr include-display-unit-of-measurement */ - @property({ type: Array, attribute: "include-display-unit-of-measurement" }) - public includeDisplayUnitOfMeasurement?: string[]; + @property({ attribute: "include-display-unit-of-measurement" }) + public includeDisplayUnitOfMeasurement?: string | string[]; /** * Show only statistics with these device classes. - * @type {Array} * @attr include-device-classes */ - @property({ type: Array, attribute: "include-device-classes" }) + @property({ attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; /** @@ -97,8 +96,8 @@ export class HaStatisticPicker extends LitElement { private _getStatistics = memoizeOne( ( statisticIds: StatisticsMetaData[], - includeStatisticsUnitOfMeasurement?: string[], - includeDisplayUnitOfMeasurement?: string[], + includeStatisticsUnitOfMeasurement?: string | string[], + includeDisplayUnitOfMeasurement?: string | string[], includeDeviceClasses?: string[], entitiesOnly?: boolean ): Array<{ id: string; name: string; state?: HassEntity }> => { @@ -114,17 +113,15 @@ export class HaStatisticPicker extends LitElement { } if (includeStatisticsUnitOfMeasurement) { + const includeUnits = ensureArray(includeStatisticsUnitOfMeasurement); statisticIds = statisticIds.filter((meta) => - includeStatisticsUnitOfMeasurement.includes( - meta.statistics_unit_of_measurement - ) + includeUnits.includes(meta.statistics_unit_of_measurement) ); } if (includeDisplayUnitOfMeasurement) { + const includeUnits = ensureArray(includeDisplayUnitOfMeasurement); statisticIds = statisticIds.filter((meta) => - includeDisplayUnitOfMeasurement.includes( - meta.display_unit_of_measurement - ) + includeUnits.includes(meta.display_unit_of_measurement) ); } diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index aa25e45803..ef450d4ae2 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -22,11 +22,52 @@ class HaStatisticsPicker extends LitElement { @property({ attribute: "pick-statistic-label" }) 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 { if (!this.hass) { 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` ${this._currentStatistics.map( (statisticId) => html` @@ -34,8 +75,10 @@ class HaStatisticsPicker extends LitElement { 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 = ( hass: HomeAssistant, statistic_id: string, diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index 879a6c8a41..0d4dd1b21d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -1,5 +1,12 @@ 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 memoizeOne from "memoize-one"; import { @@ -23,6 +30,12 @@ import { processConfigEntities } from "../../common/process-config-entities"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { entitiesConfigStruct } from "../structs/entities-struct"; +import { + getStatisticMetadata, + StatisticsMetaData, + statisticsMetaHasType, +} from "../../../../data/recorder"; +import { deepEqual } from "../../../../common/util/deep-equal"; const statTypeStruct = union([ literal("sum"), @@ -51,6 +64,13 @@ const cardConfigStruct = assign( ); 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") export class HuiStatisticsGraphCardEditor @@ -63,6 +83,8 @@ export class HuiStatisticsGraphCardEditor @state() private _configEntities?: string[]; + @state() private _metaDatas?: StatisticsMetaData[]; + public setConfig(config: StatisticsGraphCardConfig): void { assert(config, cardConfigStruct); 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( - (localize: LocalizeFunc) => + ( + localize: LocalizeFunc, + statisticIds: string[] | undefined, + metaDatas: StatisticsMetaData[] | undefined + ) => [ { name: "title", selector: { text: {} } }, { @@ -89,6 +132,13 @@ export class HuiStatisticsGraphCardEditor label: localize( `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 : + statisticIds?.some((statistic_id) => + statistic_id.includes(":") + ), })), }, }, @@ -101,13 +151,20 @@ export class HuiStatisticsGraphCardEditor { name: "stat_types", required: true, - type: "multi_select", - options: [ - ["mean", "Mean"], - ["min", "Min"], - ["max", "Max"], - ["sum", "Sum"], - ], + selector: { + select: { + multiple: true, + options: stat_types.map((stat_type) => ({ + value: stat_type, + label: stat_type_labels[stat_type], + disabled: + !metaDatas || + !metaDatas?.every((metaData) => + statisticsMetaHasType(metaData, stat_type) + ), + })), + }, + }, }, { name: "chart_type", @@ -128,19 +185,24 @@ export class HuiStatisticsGraphCardEditor return html``; } - const schema = this._schema(this.hass.localize); - const stat_types = this._config!.stat_types + const schema = this._schema( + this.hass.localize, + this._configEntities, + this._metaDatas + ); + const configured_stat_types = this._config!.stat_types ? Array.isArray(this._config!.stat_types) ? this._config!.stat_types : [this._config!.stat_types] - : ["mean", "min", "max", "sum"]; + : stat_types; const data = { chart_type: "line", period: "hour", days_to_show: 30, ...this._config, - stat_types, + stat_types: configured_stat_types, }; + const displayUnit = this._metaDatas?.[0]?.display_unit_of_measurement; return html`