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 { 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)
);
}

View File

@ -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 {
<ha-statistic-picker
.curValue=${statisticId}
.hass=${this.hass}
.includeDisplayUnitOfMeasurement=${includeDisplayUnitCurrent}
.includeStatisticsUnitOfMeasurement=${includeStatisticsUnitCurrent}
.value=${statisticId}
.statisticTypes=${this.statisticTypes}
.statisticTypes=${includeStatisticTypesCurrent}
.statisticIds=${this.statisticIds}
.label=${this.pickedStatisticLabel}
@value-changed=${this._statisticChanged}
@ -46,6 +89,10 @@ class HaStatisticsPicker extends LitElement {
<div>
<ha-statistic-picker
.hass=${this.hass}
.includeDisplayUnitOfMeasurement=${this
.includeDisplayUnitOfMeasurement}
.includeStatisticsUnitOfMeasurement=${this
.includeStatisticsUnitOfMeasurement}
.statisticTypes=${this.statisticTypes}
.statisticIds=${this.statisticIds}
.label=${this.pickStatisticLabel}

View File

@ -184,6 +184,22 @@ export const statisticsHaveType = (
type: StatisticType
) => 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,

View File

@ -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 <domain>:<object_id>
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`
<ha-form
@ -154,6 +216,8 @@ export class HuiStatisticsGraphCardEditor
.hass=${this.hass}
.pickStatisticLabel=${`Add a statistic`}
.pickedStatisticLabel=${`Statistic`}
.includeDisplayUnitOfMeasurement=${displayUnit}
.ignoreRestrictionsOnFirstStatistic=${true}
.value=${this._configEntities}
.configValue=${"entities"}
@value-changed=${this._entitiesChanged}