From ce730e2127bad8fbb15a46006ccb032eb28dcf7d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 13 Aug 2021 11:10:27 -0700 Subject: [PATCH 01/10] Add basic validation UI --- src/data/energy.ts | 20 +++++++++++ .../components/ha-energy-battery-settings.ts | 31 +++++++++++++++-- .../components/ha-energy-device-settings.ts | 15 +++++++++ .../components/ha-energy-gas-settings.ts | 31 +++++++++++++++-- .../components/ha-energy-grid-settings.ts | 33 ++++++++++++++++--- .../components/ha-energy-solar-settings.ts | 32 ++++++++++++++++-- .../ha-energy-validation-message.ts | 15 +++++++++ src/panels/config/energy/ha-config-energy.ts | 26 ++++++++++++++- 8 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/panels/config/energy/components/ha-energy-validation-message.ts diff --git a/src/data/energy.ts b/src/data/energy.ts index 8e801ced29c5..a4484c024cf0 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -144,11 +144,31 @@ export interface EnergyInfo { cost_sensors: Record; } +export interface EnergyValidationMessage { + message: string; + link: string | null; +} + +export interface EnergyValidationResult { + errors: EnergyValidationMessage[]; + warnings: EnergyValidationMessage[]; +} + +export interface EnergyPreferencesValidation extends EnergyValidationResult { + energy_sources: EnergyValidationResult[]; + device_consumption: EnergyValidationResult[]; +} + export const getEnergyInfo = (hass: HomeAssistant) => hass.callWS({ type: "energy/info", }); +export const getEnergyPreferenceValidation = (hass: HomeAssistant) => + hass.callWS({ + type: "energy/validate", + }); + export const getEnergyPreferences = (hass: HomeAssistant) => hass.callWS({ type: "energy/get_prefs", diff --git a/src/panels/config/energy/components/ha-energy-battery-settings.ts b/src/panels/config/energy/components/ha-energy-battery-settings.ts index 9caa3b8e604a..675a6efa68b6 100644 --- a/src/panels/config/energy/components/ha-energy-battery-settings.ts +++ b/src/panels/config/energy/components/ha-energy-battery-settings.ts @@ -10,7 +10,8 @@ import "../../../../components/ha-settings-row"; import { BatterySourceTypeEnergyPreference, EnergyPreferences, - energySourcesByType, + EnergyPreferencesValidation, + EnergyValidationResult, saveEnergyPreferences, } from "../../../../data/energy"; import { @@ -21,6 +22,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsBatteryDialog } from "../dialogs/show-dialogs-energy"; +import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-battery-settings") @@ -30,10 +32,23 @@ export class EnergyBatterySettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public validationResult?: EnergyPreferencesValidation; + protected render(): TemplateResult { - const types = energySourcesByType(this.preferences); + const batterySources: BatterySourceTypeEnergyPreference[] = []; + const batteryValidation: EnergyValidationResult[] = []; - const batterySources = types.battery || []; + this.preferences.energy_sources.forEach((source, idx) => { + if (source.type !== "battery") { + return; + } + batterySources.push(source); + + if (this.validationResult) { + batteryValidation.push(this.validationResult.energy_sources[idx]); + } + }); return html` @@ -54,6 +69,16 @@ export class EnergyBatterySettings extends LitElement { )}

+ ${batteryValidation.map( + (result) => html` + ${result.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${result.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )} + ` + )}

Battery systems

${batterySources.map((source) => { const fromEntityState = this.hass.states[source.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index 561973a0e014..caa786d3c14f 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -9,6 +9,7 @@ import "../../../../components/ha-card"; import { DeviceConsumptionEnergyPreference, EnergyPreferences, + EnergyPreferencesValidation, saveEnergyPreferences, } from "../../../../data/energy"; import { @@ -19,6 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsDeviceDialog } from "../dialogs/show-dialogs-energy"; +import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-device-settings") @@ -28,6 +30,9 @@ export class EnergyDeviceSettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public validationResult?: EnergyPreferencesValidation; + protected render(): TemplateResult { return html` @@ -55,6 +60,16 @@ export class EnergyDeviceSettings extends LitElement { )}

+ ${this.validationResult?.device_consumption.map( + (result) => html` + ${result.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${result.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )} + ` + )}

Devices

${this.preferences.device_consumption.map((device) => { const entityState = this.hass.states[device.stat_consumption]; diff --git a/src/panels/config/energy/components/ha-energy-gas-settings.ts b/src/panels/config/energy/components/ha-energy-gas-settings.ts index 00fc47e1dfe0..34e390e8c661 100644 --- a/src/panels/config/energy/components/ha-energy-gas-settings.ts +++ b/src/panels/config/energy/components/ha-energy-gas-settings.ts @@ -7,9 +7,10 @@ import { computeStateName } from "../../../../common/entity/compute_state_name"; import "../../../../components/ha-card"; import { EnergyPreferences, - energySourcesByType, saveEnergyPreferences, GasSourceTypeEnergyPreference, + EnergyPreferencesValidation, + EnergyValidationResult, } from "../../../../data/energy"; import { showConfirmationDialog, @@ -19,6 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsGasDialog } from "../dialogs/show-dialogs-energy"; +import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-gas-settings") @@ -28,10 +30,23 @@ export class EnergyGasSettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public validationResult?: EnergyPreferencesValidation; + protected render(): TemplateResult { - const types = energySourcesByType(this.preferences); + const gasSources: GasSourceTypeEnergyPreference[] = []; + const gasValidation: EnergyValidationResult[] = []; - const gasSources = types.gas || []; + this.preferences.energy_sources.forEach((source, idx) => { + if (source.type !== "gas") { + return; + } + gasSources.push(source); + + if (this.validationResult) { + gasValidation.push(this.validationResult.energy_sources[idx]); + } + }); return html` @@ -50,6 +65,16 @@ export class EnergyGasSettings extends LitElement { >${this.hass.localize("ui.panel.config.energy.gas.learn_more")}

+ ${gasValidation.map( + (result) => html` + ${result.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${result.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )} + ` + )}

Gas consumption

${gasSources.map((source) => { const entityState = this.hass.states[source.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index 5bf861f9d4ca..db27ce6cb31f 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -19,7 +19,9 @@ import { import { emptyGridSourceEnergyPreference, EnergyPreferences, + EnergyPreferencesValidation, energySourcesByType, + EnergyValidationResult, FlowFromGridSourceEnergyPreference, FlowToGridSourceEnergyPreference, GridSourceTypeEnergyPreference, @@ -38,6 +40,7 @@ import { showEnergySettingsGridFlowFromDialog, showEnergySettingsGridFlowToDialog, } from "../dialogs/show-dialogs-energy"; +import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-grid-settings") @@ -47,6 +50,9 @@ export class EnergyGridSettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public validationResult?: EnergyPreferencesValidation; + @state() private _configEntries?: ConfigEntry[]; protected firstUpdated() { @@ -54,11 +60,23 @@ export class EnergyGridSettings extends LitElement { } protected render(): TemplateResult { - const types = energySourcesByType(this.preferences); + const gridIdx = this.preferences.energy_sources.findIndex( + (source) => source.type === "grid" + ); - const gridSource = types.grid - ? types.grid[0] - : emptyGridSourceEnergyPreference(); + let gridSource: GridSourceTypeEnergyPreference; + let gridValidation: EnergyValidationResult | undefined; + + if (gridIdx === -1) { + gridSource = emptyGridSourceEnergyPreference(); + } else { + gridSource = this.preferences.energy_sources[ + gridIdx + ] as GridSourceTypeEnergyPreference; + if (this.validationResult) { + gridValidation = this.validationResult.energy_sources[gridIdx]; + } + } return html` @@ -82,6 +100,13 @@ export class EnergyGridSettings extends LitElement { )}

+ ${gridValidation?.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${gridValidation?.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )} +

Grid consumption

${gridSource.flow_from.map((flow) => { const entityState = this.hass.states[flow.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-solar-settings.ts b/src/panels/config/energy/components/ha-energy-solar-settings.ts index 109c61d36ef1..5a0fd7b60e12 100644 --- a/src/panels/config/energy/components/ha-energy-solar-settings.ts +++ b/src/panels/config/energy/components/ha-energy-solar-settings.ts @@ -7,7 +7,8 @@ import { computeStateName } from "../../../../common/entity/compute_state_name"; import "../../../../components/ha-card"; import { EnergyPreferences, - energySourcesByType, + EnergyPreferencesValidation, + EnergyValidationResult, saveEnergyPreferences, SolarSourceTypeEnergyPreference, } from "../../../../data/energy"; @@ -19,6 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsSolarDialog } from "../dialogs/show-dialogs-energy"; +import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-solar-settings") @@ -28,10 +30,23 @@ export class EnergySolarSettings extends LitElement { @property({ attribute: false }) public preferences!: EnergyPreferences; + @property({ attribute: false }) + public validationResult?: EnergyPreferencesValidation; + protected render(): TemplateResult { - const types = energySourcesByType(this.preferences); + const solarSources: SolarSourceTypeEnergyPreference[] = []; + const solarValidation: EnergyValidationResult[] = []; - const solarSources = types.solar || []; + this.preferences.energy_sources.forEach((source, idx) => { + if (source.type !== "solar") { + return; + } + solarSources.push(source); + + if (this.validationResult) { + solarValidation.push(this.validationResult.energy_sources[idx]); + } + }); return html` @@ -55,6 +70,17 @@ export class EnergySolarSettings extends LitElement { )}

+ ${solarValidation.map( + (result) => html` + ${result.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${result.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )} + ` + )} +

Solar production

${solarSources.map((source) => { const entityState = this.hass.states[source.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-validation-message.ts b/src/panels/config/energy/components/ha-energy-validation-message.ts new file mode 100644 index 000000000000..01b2fc6e9be9 --- /dev/null +++ b/src/panels/config/energy/components/ha-energy-validation-message.ts @@ -0,0 +1,15 @@ +import { html } from "lit"; +import { EnergyValidationMessage } from "../../../../data/energy"; + +export const renderEnergyValidationMessage = ( + type: "error" | "warning", + message: EnergyValidationMessage +) => + html`

+ [${type}] + ${message.message}${!message.link + ? "" + : html` Learn more`} +

`; diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index 96a98e125146..ba4086a23d93 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -1,7 +1,12 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../../components/ha-svg-icon"; -import { EnergyPreferences, getEnergyPreferences } from "../../../data/energy"; +import { + EnergyPreferences, + EnergyPreferencesValidation, + getEnergyPreferences, + getEnergyPreferenceValidation, +} from "../../../data/energy"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; @@ -12,6 +17,7 @@ import "./components/ha-energy-grid-settings"; import "./components/ha-energy-solar-settings"; import "./components/ha-energy-battery-settings"; import "./components/ha-energy-gas-settings"; +import { renderEnergyValidationMessage } from "./components/ha-energy-validation-message"; const INITIAL_CONFIG: EnergyPreferences = { energy_sources: [], @@ -34,6 +40,8 @@ class HaConfigEnergy extends LitElement { @state() private _preferences?: EnergyPreferences; + @state() private _validationResult?: EnergyPreferencesValidation; + @state() private _error?: string; protected firstUpdated() { @@ -70,22 +78,31 @@ class HaConfigEnergy extends LitElement {
After setting up a new device, it can take up to 2 hours for new data to arrive in your energy dashboard. + ${this._validationResult?.errors.map((msg) => + renderEnergyValidationMessage("error", msg) + )} + ${this._validationResult?.warnings.map((msg) => + renderEnergyValidationMessage("warning", msg) + )}
@@ -104,6 +122,7 @@ class HaConfigEnergy extends LitElement { } private async _fetchConfig() { + const validationPromise = getEnergyPreferenceValidation(this.hass); try { this._preferences = await getEnergyPreferences(this.hass); } catch (e) { @@ -113,6 +132,11 @@ class HaConfigEnergy extends LitElement { this._error = e.message; } } + try { + this._validationResult = await validationPromise; + } catch (e) { + this._error = e.message; + } } private _prefsChanged(ev: CustomEvent) { From 70ba7030bc5c3752f5a9bc424d655b1090459b5b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 13 Aug 2021 11:25:36 -0700 Subject: [PATCH 02/10] Also refresh validation results when prefs change --- src/panels/config/energy/ha-config-energy.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index ba4086a23d93..1dd2b42e4a8b 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -139,8 +139,14 @@ class HaConfigEnergy extends LitElement { } } - private _prefsChanged(ev: CustomEvent) { + private async _prefsChanged(ev: CustomEvent) { this._preferences = ev.detail.value; + this._validationResult = undefined; + try { + this._validationResult = await getEnergyPreferenceValidation(this.hass); + } catch (e) { + this._error = e.message; + } } static get styles(): CSSResultGroup { From c8ee2e7722514e882f0e21b9d3299e51f360b152 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 15:25:55 -0700 Subject: [PATCH 03/10] Update look --- src/common/util/group-by.ts | 14 ++ src/data/energy.ts | 18 +-- .../components/ha-energy-battery-settings.ts | 22 +-- .../components/ha-energy-device-settings.ts | 17 ++- .../components/ha-energy-gas-settings.ts | 21 ++- .../components/ha-energy-grid-settings.ts | 20 +-- .../components/ha-energy-solar-settings.ts | 21 ++- .../ha-energy-validation-message.ts | 126 +++++++++++++++--- src/panels/config/energy/ha-config-energy.ts | 7 - src/translations/en.json | 11 +- 10 files changed, 192 insertions(+), 85 deletions(-) create mode 100644 src/common/util/group-by.ts diff --git a/src/common/util/group-by.ts b/src/common/util/group-by.ts new file mode 100644 index 000000000000..4bf63e459e6b --- /dev/null +++ b/src/common/util/group-by.ts @@ -0,0 +1,14 @@ +export const groupBy = ( + list: T[], + keySelector: (item: T) => string +): { [key: string]: T[] } => { + const result = {}; + for (const item of list) { + const key = keySelector(item); + if (!result[key]) { + result[key] = []; + } + result[key].push(item); + } + return result; +}; diff --git a/src/data/energy.ts b/src/data/energy.ts index a4484c024cf0..f5316bdda045 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -144,19 +144,15 @@ export interface EnergyInfo { cost_sensors: Record; } -export interface EnergyValidationMessage { - message: string; - link: string | null; +export interface EnergyValidationIssue { + type: string; + identifier: string; + value?: unknown; } -export interface EnergyValidationResult { - errors: EnergyValidationMessage[]; - warnings: EnergyValidationMessage[]; -} - -export interface EnergyPreferencesValidation extends EnergyValidationResult { - energy_sources: EnergyValidationResult[]; - device_consumption: EnergyValidationResult[]; +export interface EnergyPreferencesValidation { + energy_sources: EnergyValidationIssue[][]; + device_consumption: EnergyValidationIssue[][]; } export const getEnergyInfo = (hass: HomeAssistant) => diff --git a/src/panels/config/energy/components/ha-energy-battery-settings.ts b/src/panels/config/energy/components/ha-energy-battery-settings.ts index 675a6efa68b6..22c1fa6cfb3b 100644 --- a/src/panels/config/energy/components/ha-energy-battery-settings.ts +++ b/src/panels/config/energy/components/ha-energy-battery-settings.ts @@ -11,7 +11,7 @@ import { BatterySourceTypeEnergyPreference, EnergyPreferences, EnergyPreferencesValidation, - EnergyValidationResult, + EnergyValidationIssue, saveEnergyPreferences, } from "../../../../data/energy"; import { @@ -22,7 +22,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsBatteryDialog } from "../dialogs/show-dialogs-energy"; -import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; +import "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-battery-settings") @@ -37,7 +37,7 @@ export class EnergyBatterySettings extends LitElement { protected render(): TemplateResult { const batterySources: BatterySourceTypeEnergyPreference[] = []; - const batteryValidation: EnergyValidationResult[] = []; + const batteryValidation: EnergyValidationIssue[][] = []; this.preferences.energy_sources.forEach((source, idx) => { if (source.type !== "battery") { @@ -70,15 +70,15 @@ export class EnergyBatterySettings extends LitElement { >

${batteryValidation.map( - (result) => html` - ${result.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${result.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )} - ` + (result) => + html` + + ` )} +

Battery systems

${batterySources.map((source) => { const fromEntityState = this.hass.states[source.stat_energy_from]; diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index caa786d3c14f..06a22fcd4f43 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsDeviceDialog } from "../dialogs/show-dialogs-energy"; -import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; +import "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-device-settings") @@ -61,14 +61,13 @@ export class EnergyDeviceSettings extends LitElement { >

${this.validationResult?.device_consumption.map( - (result) => html` - ${result.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${result.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )} - ` + (result) => + html` + + ` )}

Devices

${this.preferences.device_consumption.map((device) => { diff --git a/src/panels/config/energy/components/ha-energy-gas-settings.ts b/src/panels/config/energy/components/ha-energy-gas-settings.ts index 34e390e8c661..2a80cbb4de4f 100644 --- a/src/panels/config/energy/components/ha-energy-gas-settings.ts +++ b/src/panels/config/energy/components/ha-energy-gas-settings.ts @@ -10,7 +10,7 @@ import { saveEnergyPreferences, GasSourceTypeEnergyPreference, EnergyPreferencesValidation, - EnergyValidationResult, + EnergyValidationIssue, } from "../../../../data/energy"; import { showConfirmationDialog, @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsGasDialog } from "../dialogs/show-dialogs-energy"; -import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; +import "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-gas-settings") @@ -35,7 +35,7 @@ export class EnergyGasSettings extends LitElement { protected render(): TemplateResult { const gasSources: GasSourceTypeEnergyPreference[] = []; - const gasValidation: EnergyValidationResult[] = []; + const gasValidation: EnergyValidationIssue[][] = []; this.preferences.energy_sources.forEach((source, idx) => { if (source.type !== "gas") { @@ -66,14 +66,13 @@ export class EnergyGasSettings extends LitElement { >

${gasValidation.map( - (result) => html` - ${result.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${result.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )} - ` + (result) => + html` + + ` )}

Gas consumption

${gasSources.map((source) => { diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index db27ce6cb31f..f26e8dbe4d44 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -21,7 +21,7 @@ import { EnergyPreferences, EnergyPreferencesValidation, energySourcesByType, - EnergyValidationResult, + EnergyValidationIssue, FlowFromGridSourceEnergyPreference, FlowToGridSourceEnergyPreference, GridSourceTypeEnergyPreference, @@ -40,7 +40,7 @@ import { showEnergySettingsGridFlowFromDialog, showEnergySettingsGridFlowToDialog, } from "../dialogs/show-dialogs-energy"; -import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; +import "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-grid-settings") @@ -65,7 +65,7 @@ export class EnergyGridSettings extends LitElement { ); let gridSource: GridSourceTypeEnergyPreference; - let gridValidation: EnergyValidationResult | undefined; + let gridValidation: EnergyValidationIssue[] | undefined; if (gridIdx === -1) { gridSource = emptyGridSourceEnergyPreference(); @@ -100,12 +100,14 @@ export class EnergyGridSettings extends LitElement { )}

- ${gridValidation?.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${gridValidation?.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )} + ${gridValidation + ? html` + + ` + : ""}

Grid consumption

${gridSource.flow_from.map((flow) => { diff --git a/src/panels/config/energy/components/ha-energy-solar-settings.ts b/src/panels/config/energy/components/ha-energy-solar-settings.ts index 5a0fd7b60e12..43b1f5506334 100644 --- a/src/panels/config/energy/components/ha-energy-solar-settings.ts +++ b/src/panels/config/energy/components/ha-energy-solar-settings.ts @@ -8,7 +8,7 @@ import "../../../../components/ha-card"; import { EnergyPreferences, EnergyPreferencesValidation, - EnergyValidationResult, + EnergyValidationIssue, saveEnergyPreferences, SolarSourceTypeEnergyPreference, } from "../../../../data/energy"; @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsSolarDialog } from "../dialogs/show-dialogs-energy"; -import { renderEnergyValidationMessage } from "./ha-energy-validation-message"; +import "./ha-energy-validation-message"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-solar-settings") @@ -35,7 +35,7 @@ export class EnergySolarSettings extends LitElement { protected render(): TemplateResult { const solarSources: SolarSourceTypeEnergyPreference[] = []; - const solarValidation: EnergyValidationResult[] = []; + const solarValidation: EnergyValidationIssue[][] = []; this.preferences.energy_sources.forEach((source, idx) => { if (source.type !== "solar") { @@ -71,14 +71,13 @@ export class EnergySolarSettings extends LitElement { >

${solarValidation.map( - (result) => html` - ${result.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${result.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )} - ` + (result) => + html` + + ` )}

Solar production

diff --git a/src/panels/config/energy/components/ha-energy-validation-message.ts b/src/panels/config/energy/components/ha-energy-validation-message.ts index 01b2fc6e9be9..41278514eaff 100644 --- a/src/panels/config/energy/components/ha-energy-validation-message.ts +++ b/src/panels/config/energy/components/ha-energy-validation-message.ts @@ -1,15 +1,111 @@ -import { html } from "lit"; -import { EnergyValidationMessage } from "../../../../data/energy"; - -export const renderEnergyValidationMessage = ( - type: "error" | "warning", - message: EnergyValidationMessage -) => - html`

- [${type}] - ${message.message}${!message.link - ? "" - : html` Learn more`} -

`; +import { mdiAlertOutline } from "@mdi/js"; +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { groupBy } from "../../../../common/util/group-by"; +import "../../../../components/ha-svg-icon"; +import { EnergyValidationIssue } from "../../../../data/energy"; +import { HomeAssistant } from "../../../../types"; + +@customElement("ha-energy-validation-result") +class EnergyValidationMessage extends LitElement { + @property({ attribute: false }) + public hass!: HomeAssistant; + + @property() + public issues!: EnergyValidationIssue[]; + + public render() { + if (this.issues.length === 0) { + return html``; + } + + const grouped = groupBy(this.issues, (issue) => issue.type); + + return Object.entries(grouped).map( + ([issueType, gIssues]) => html` +
+
+ +
+
+
+ ${this.hass.localize( + `ui.panel.config.energy.validation.issues.${issueType}.title` + ) || issueType} +
+ + ${this.hass.localize( + `ui.panel.config.energy.validation.issues.${issueType}.description` + )} + ${issueType === "entity_not_tracked" || true + ? html` + ${this.hass.localize( + "ui.panel.config.common.learn_more" + )} + ` + : ""} + +
    + ${gIssues.map( + (issue) => + html`
  • + ${issue.identifier}${issue.value + ? html` (${issue.value})` + : ""} +
  • ` + )} +
+
+
+ ` + ); + } + + static styles = css` + .issue-type { + position: relative; + padding: 4px; + display: flex; + } + .issue-type::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--warning-color); + opacity: 0.12; + pointer-events: none; + content: ""; + } + .icon { + margin: 4px 8px; + width: 24px; + } + ha-svg-icon { + color: var(--warning-color); + } + .title { + font-weight: bold; + margin-top: 5px; + } + ul { + padding-left: 24px; + margin: 4px 0; + } + a { + color: var(--primary-color); + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-energy-validation-result": EnergyValidationMessage; + } +} diff --git a/src/panels/config/energy/ha-config-energy.ts b/src/panels/config/energy/ha-config-energy.ts index 1dd2b42e4a8b..bd1efaa2ab79 100644 --- a/src/panels/config/energy/ha-config-energy.ts +++ b/src/panels/config/energy/ha-config-energy.ts @@ -17,7 +17,6 @@ import "./components/ha-energy-grid-settings"; import "./components/ha-energy-solar-settings"; import "./components/ha-energy-battery-settings"; import "./components/ha-energy-gas-settings"; -import { renderEnergyValidationMessage } from "./components/ha-energy-validation-message"; const INITIAL_CONFIG: EnergyPreferences = { energy_sources: [], @@ -78,12 +77,6 @@ class HaConfigEnergy extends LitElement {
After setting up a new device, it can take up to 2 hours for new data to arrive in your energy dashboard. - ${this._validationResult?.errors.map((msg) => - renderEnergyValidationMessage("error", msg) - )} - ${this._validationResult?.warnings.map((msg) => - renderEnergyValidationMessage("warning", msg) - )}
diff --git a/src/translations/en.json b/src/translations/en.json index 4dbc1ce1e006..c983284df0ce 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -934,7 +934,8 @@ "common": { "editor": { "confirm_unsaved": "You have unsaved changes. Are you sure you want to leave?" - } + }, + "learn_more": "Learn more" }, "areas": { "caption": "Areas", @@ -1079,6 +1080,14 @@ "dialog": { "selected_stat_intro": "Select the entity that represents the device energy usage." } + }, + "validation": { + "issues": { + "entity_not_defined": { + "title": "Entity not defined", + "description": "One of the configured entities is not currently defined in Home Assistant." + } + } } }, "helpers": { From 4a349fc1072f2a30e4b70a3c9eb9f7f8c0ab3908 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 15:27:05 -0700 Subject: [PATCH 04/10] Remove || true --- .../config/energy/components/ha-energy-validation-message.ts | 2 +- src/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/config/energy/components/ha-energy-validation-message.ts b/src/panels/config/energy/components/ha-energy-validation-message.ts index 41278514eaff..004986c250f8 100644 --- a/src/panels/config/energy/components/ha-energy-validation-message.ts +++ b/src/panels/config/energy/components/ha-energy-validation-message.ts @@ -37,7 +37,7 @@ class EnergyValidationMessage extends LitElement { ${this.hass.localize( `ui.panel.config.energy.validation.issues.${issueType}.description` )} - ${issueType === "entity_not_tracked" || true + ${issueType === "entity_not_tracked" ? html` Date: Tue, 17 Aug 2021 15:34:49 -0700 Subject: [PATCH 05/10] Add missing errors --- .../ha-energy-validation-message.ts | 9 +++++---- src/translations/en.json | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/panels/config/energy/components/ha-energy-validation-message.ts b/src/panels/config/energy/components/ha-energy-validation-message.ts index 004986c250f8..8dcd10276058 100644 --- a/src/panels/config/energy/components/ha-energy-validation-message.ts +++ b/src/panels/config/energy/components/ha-energy-validation-message.ts @@ -39,14 +39,14 @@ class EnergyValidationMessage extends LitElement { )} ${issueType === "entity_not_tracked" ? html` - ${this.hass.localize( "ui.panel.config.common.learn_more" )} + >) ` : ""} @@ -86,10 +86,11 @@ class EnergyValidationMessage extends LitElement { .icon { margin: 4px 8px; width: 24px; - } - ha-svg-icon { color: var(--warning-color); } + .content { + padding-right: 4px; + } .title { font-weight: bold; margin-top: 5px; diff --git a/src/translations/en.json b/src/translations/en.json index 60a2efb2e007..7cadc8c765a9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1086,6 +1086,26 @@ "entity_not_defined": { "title": "Entity not defined", "description": "Check the integration or your configuration that provides:" + }, + "recorder_untracked": { + "title": "Entity not tracked", + "description": "The recorder has been configured to exclude these configured entities:" + }, + "entity_unavailable": { + "title": "Entity unavailable", + "description": "The state of these configured entities are currently not available:" + }, + "entity_state_non_numeric": { + "title": "Entity has non-numeric state", + "description": "The following entities have a state that cannot be parsed as a number:" + }, + "entity_negative_state": { + "title": "Entity has a negative state", + "description": "The following entities have a negative state while a positive state is expected:" + }, + "entity_unexpected_unit": { + "title": "Unexpected unit of measurement", + "description": "The following entities do not have expected units of measurement kWh or Wh:" } } } From 024ecc85b4683a153aad4b268dd4a24472c8758f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 16:11:20 -0700 Subject: [PATCH 06/10] Validate state class --- src/translations/en.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 7cadc8c765a9..97ca82fb7f1e 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1103,9 +1103,17 @@ "title": "Entity has a negative state", "description": "The following entities have a negative state while a positive state is expected:" }, - "entity_unexpected_unit": { + "entity_unexpected_unit_energy": { "title": "Unexpected unit of measurement", "description": "The following entities do not have expected units of measurement kWh or Wh:" + }, + "entity_unexpected_unit_price": { + "title": "Unexpected unit of measurement", + "description": "The following entities do not have expected units of measurement that ends with /kWh or /Wh:" + }, + "entity_unexpected_state_class": { + "title": "Unexpected state class", + "description": "The following entities do not have expeted state class \"total_increasing\"" } } } From 98c582f00e541b1973ce5e1c75092bc5986bd62a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 20:57:45 -0700 Subject: [PATCH 07/10] Rename file --- .../config/energy/components/ha-energy-battery-settings.ts | 2 +- .../config/energy/components/ha-energy-device-settings.ts | 2 +- src/panels/config/energy/components/ha-energy-gas-settings.ts | 2 +- src/panels/config/energy/components/ha-energy-grid-settings.ts | 2 +- src/panels/config/energy/components/ha-energy-solar-settings.ts | 2 +- ...rgy-validation-message.ts => ha-energy-validation-result.ts} | 0 src/translations/en.json | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename src/panels/config/energy/components/{ha-energy-validation-message.ts => ha-energy-validation-result.ts} (100%) diff --git a/src/panels/config/energy/components/ha-energy-battery-settings.ts b/src/panels/config/energy/components/ha-energy-battery-settings.ts index 22c1fa6cfb3b..d1360fd61d5b 100644 --- a/src/panels/config/energy/components/ha-energy-battery-settings.ts +++ b/src/panels/config/energy/components/ha-energy-battery-settings.ts @@ -22,7 +22,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsBatteryDialog } from "../dialogs/show-dialogs-energy"; -import "./ha-energy-validation-message"; +import "./ha-energy-validation-result"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-battery-settings") diff --git a/src/panels/config/energy/components/ha-energy-device-settings.ts b/src/panels/config/energy/components/ha-energy-device-settings.ts index 06a22fcd4f43..c99b36849d0f 100644 --- a/src/panels/config/energy/components/ha-energy-device-settings.ts +++ b/src/panels/config/energy/components/ha-energy-device-settings.ts @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsDeviceDialog } from "../dialogs/show-dialogs-energy"; -import "./ha-energy-validation-message"; +import "./ha-energy-validation-result"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-device-settings") diff --git a/src/panels/config/energy/components/ha-energy-gas-settings.ts b/src/panels/config/energy/components/ha-energy-gas-settings.ts index 2a80cbb4de4f..4b57b0ee3de7 100644 --- a/src/panels/config/energy/components/ha-energy-gas-settings.ts +++ b/src/panels/config/energy/components/ha-energy-gas-settings.ts @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsGasDialog } from "../dialogs/show-dialogs-energy"; -import "./ha-energy-validation-message"; +import "./ha-energy-validation-result"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-gas-settings") diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index f26e8dbe4d44..6f59fe6a187c 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -40,7 +40,7 @@ import { showEnergySettingsGridFlowFromDialog, showEnergySettingsGridFlowToDialog, } from "../dialogs/show-dialogs-energy"; -import "./ha-energy-validation-message"; +import "./ha-energy-validation-result"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-grid-settings") diff --git a/src/panels/config/energy/components/ha-energy-solar-settings.ts b/src/panels/config/energy/components/ha-energy-solar-settings.ts index 43b1f5506334..d58550e8d8f1 100644 --- a/src/panels/config/energy/components/ha-energy-solar-settings.ts +++ b/src/panels/config/energy/components/ha-energy-solar-settings.ts @@ -20,7 +20,7 @@ import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { documentationUrl } from "../../../../util/documentation-url"; import { showEnergySettingsSolarDialog } from "../dialogs/show-dialogs-energy"; -import "./ha-energy-validation-message"; +import "./ha-energy-validation-result"; import { energyCardStyles } from "./styles"; @customElement("ha-energy-solar-settings") diff --git a/src/panels/config/energy/components/ha-energy-validation-message.ts b/src/panels/config/energy/components/ha-energy-validation-result.ts similarity index 100% rename from src/panels/config/energy/components/ha-energy-validation-message.ts rename to src/panels/config/energy/components/ha-energy-validation-result.ts diff --git a/src/translations/en.json b/src/translations/en.json index 97ca82fb7f1e..a7910856ea71 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1111,7 +1111,7 @@ "title": "Unexpected unit of measurement", "description": "The following entities do not have expected units of measurement that ends with /kWh or /Wh:" }, - "entity_unexpected_state_class": { + "entity_unexpected_state_class_total_increasing": { "title": "Unexpected state class", "description": "The following entities do not have expeted state class \"total_increasing\"" } From 5e2e4b806831c438308f171f03696f67cd7cd41d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 21:18:11 -0700 Subject: [PATCH 08/10] Simplify energySourcesByType --- src/common/util/group-by.ts | 7 ++++--- src/data/energy.ts | 14 +++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/common/util/group-by.ts b/src/common/util/group-by.ts index 4bf63e459e6b..0f3f76dfff8c 100644 --- a/src/common/util/group-by.ts +++ b/src/common/util/group-by.ts @@ -5,10 +5,11 @@ export const groupBy = ( const result = {}; for (const item of list) { const key = keySelector(item); - if (!result[key]) { - result[key] = []; + if (key in result) { + result[key].push(item); + } else { + result[key] = [item]; } - result[key].push(item); } return result; }; diff --git a/src/data/energy.ts b/src/data/energy.ts index f5316bdda045..799bb284f7ff 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -6,6 +6,7 @@ import { startOfYesterday, } from "date-fns"; import { Collection, getCollection } from "home-assistant-js-websocket"; +import { groupBy } from "../common/util/group-by"; import { subscribeOne } from "../common/util/subscribe-one"; import { HomeAssistant } from "../types"; import { ConfigEntry, getConfigEntries } from "./config_entries"; @@ -189,17 +190,8 @@ interface EnergySourceByType { gas?: GasSourceTypeEnergyPreference[]; } -export const energySourcesByType = (prefs: EnergyPreferences) => { - const types: EnergySourceByType = {}; - for (const source of prefs.energy_sources) { - if (source.type in types) { - types[source.type]!.push(source as any); - } else { - types[source.type] = [source as any]; - } - } - return types; -}; +export const energySourcesByType = (prefs: EnergyPreferences) => + groupBy(prefs.energy_sources, (item) => item.type) as EnergySourceByType; export interface EnergyData { start: Date; From 8daa55f38e4228d0e34d7de4515c5e71f71f3056 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 21:28:29 -0700 Subject: [PATCH 09/10] Update src/translations/en.json --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index a7910856ea71..a06485886e31 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1113,7 +1113,7 @@ }, "entity_unexpected_state_class_total_increasing": { "title": "Unexpected state class", - "description": "The following entities do not have expeted state class \"total_increasing\"" + "description": "The following entities do not have expected state class \"total_increasing\"" } } } From 8c53b46401d4349b9f04bac04d36a8fdd2b3e0e2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Aug 2021 23:20:51 -0700 Subject: [PATCH 10/10] Update ha-energy-validation-result.ts --- .../config/energy/components/ha-energy-validation-result.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/panels/config/energy/components/ha-energy-validation-result.ts b/src/panels/config/energy/components/ha-energy-validation-result.ts index 8dcd10276058..344b6d85b321 100644 --- a/src/panels/config/energy/components/ha-energy-validation-result.ts +++ b/src/panels/config/energy/components/ha-energy-validation-result.ts @@ -71,6 +71,7 @@ class EnergyValidationMessage extends LitElement { position: relative; padding: 4px; display: flex; + margin: 4px 0; } .issue-type::before { position: absolute; @@ -82,6 +83,7 @@ class EnergyValidationMessage extends LitElement { opacity: 0.12; pointer-events: none; content: ""; + border-radius: 4px; } .icon { margin: 4px 8px;