mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-08 18:06:36 +00:00
Statistics dev tools (#10074)
* Statistics dev tools * Show all statistics * Update src/data/history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Update history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
03dc3e52b7
commit
a89caccd32
@ -550,7 +550,7 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private _handleRowClick(ev: Event) {
|
||||
const target = ev.target as HTMLElement;
|
||||
if (target.tagName === "HA-CHECKBOX") {
|
||||
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
||||
return;
|
||||
}
|
||||
const rowId = (ev.currentTarget as any).rowId;
|
||||
|
@ -76,6 +76,23 @@ export interface StatisticsMetaData {
|
||||
statistic_id: string;
|
||||
}
|
||||
|
||||
export type StatisticsValidationResult =
|
||||
| StatisticsValidationResultUnsupportedUnit
|
||||
| StatisticsValidationResultUnitsChanged;
|
||||
|
||||
export interface StatisticsValidationResultUnsupportedUnit {
|
||||
type: "unsupported_unit";
|
||||
data: { statistic_id: string; device_class: string; state_unit: string };
|
||||
}
|
||||
|
||||
export interface StatisticsValidationResultUnitsChanged {
|
||||
type: "units_changed";
|
||||
data: { statistic_id: string; state_unit: string; metadata_unit: string };
|
||||
}
|
||||
export interface StatisticsValidationResults {
|
||||
[statisticId: string]: StatisticsValidationResult[];
|
||||
}
|
||||
|
||||
export const fetchRecent = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@ -305,6 +322,28 @@ export const fetchStatistics = (
|
||||
period,
|
||||
});
|
||||
|
||||
export const validateStatistics = (hass: HomeAssistant) =>
|
||||
hass.callWS<StatisticsValidationResults>({
|
||||
type: "recorder/validate_statistics",
|
||||
});
|
||||
|
||||
export const updateStatisticsMetadata = (
|
||||
hass: HomeAssistant,
|
||||
statistic_id: string,
|
||||
unit_of_measurement: string | null
|
||||
) =>
|
||||
hass.callWS<void>({
|
||||
type: "recorder/update_statistics_metadata",
|
||||
statistic_id,
|
||||
unit_of_measurement,
|
||||
});
|
||||
|
||||
export const clearStatistics = (hass: HomeAssistant, statistic_ids: string[]) =>
|
||||
hass.callWS<void>({
|
||||
type: "recorder/clear_statistics",
|
||||
statistic_ids,
|
||||
});
|
||||
|
||||
export const calculateStatisticSumGrowth = (
|
||||
values: StatisticValue[]
|
||||
): number | null => {
|
||||
|
@ -37,6 +37,10 @@ class DeveloperToolsRouter extends HassRouterPage {
|
||||
tag: "developer-tools-template",
|
||||
load: () => import("./template/developer-tools-template"),
|
||||
},
|
||||
statistics: {
|
||||
tag: "developer-tools-statistics",
|
||||
load: () => import("./statistics/developer-tools-statistics"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -63,6 +63,11 @@ class PanelDeveloperTools extends LitElement {
|
||||
"ui.panel.developer-tools.tabs.events.title"
|
||||
)}
|
||||
</paper-tab>
|
||||
<paper-tab page-name="statistics">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.title"
|
||||
)}
|
||||
</paper-tab>
|
||||
</ha-tabs>
|
||||
</app-header>
|
||||
<developer-tools-router
|
||||
|
@ -0,0 +1,204 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import type { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import {
|
||||
getStatisticIds,
|
||||
StatisticsMetaData,
|
||||
StatisticsValidationResult,
|
||||
validateStatistics,
|
||||
} from "../../../data/history";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showFixStatisticsUnitsChangedDialog } from "./show-dialog-statistics-fix-units-changed";
|
||||
|
||||
@customElement("developer-tools-statistics")
|
||||
class HaPanelDevStatistics extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() private _data: (StatisticsMetaData & {
|
||||
issues?: StatisticsValidationResult[];
|
||||
state?: HassEntity;
|
||||
})[] = [] as StatisticsMetaData[];
|
||||
|
||||
protected firstUpdated() {
|
||||
this._validateStatistics();
|
||||
}
|
||||
|
||||
private _columns: DataTableColumnContainer = {
|
||||
state: {
|
||||
title: "Entity",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
template: (entityState, data: any) =>
|
||||
html`${entityState
|
||||
? computeStateName(entityState)
|
||||
: data.statistic_id}`,
|
||||
},
|
||||
statistic_id: {
|
||||
title: "Statistic id",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
hidden: this.narrow,
|
||||
width: "30%",
|
||||
},
|
||||
unit_of_measurement: {
|
||||
title: "Unit",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "10%",
|
||||
},
|
||||
issues: {
|
||||
title: "Issue",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
width: "30%",
|
||||
template: (issues) =>
|
||||
html`${issues
|
||||
? issues.map(
|
||||
(issue) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.developer-tools.tabs.statistics.issues.${issue.type}`,
|
||||
issue.data
|
||||
) || issue.type
|
||||
)
|
||||
: ""}`,
|
||||
},
|
||||
fix: {
|
||||
title: "",
|
||||
template: (_, data: any) =>
|
||||
html`${data.issues
|
||||
? html`<mwc-button @click=${this._fixIssue} .data=${data.issues}
|
||||
>Fix issue</mwc-button
|
||||
>`
|
||||
: ""}`,
|
||||
width: "113px",
|
||||
},
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns}
|
||||
.data=${this._data}
|
||||
noDataText="No issues found!"
|
||||
id="statistic_id"
|
||||
clickable
|
||||
@row-click=${this._rowClicked}
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private _rowClicked(ev) {
|
||||
const id = ev.detail.id;
|
||||
if (id in this.hass.states) {
|
||||
fireEvent(this, "hass-more-info", { entityId: id });
|
||||
}
|
||||
}
|
||||
|
||||
private async _validateStatistics() {
|
||||
const [statisticIds, issues] = await Promise.all([
|
||||
getStatisticIds(this.hass),
|
||||
validateStatistics(this.hass),
|
||||
]);
|
||||
|
||||
this._data = statisticIds.map((statistic) => ({
|
||||
...statistic,
|
||||
state: this.hass.states[statistic.statistic_id],
|
||||
issues: issues[statistic.statistic_id],
|
||||
}));
|
||||
}
|
||||
|
||||
private _fixIssue(ev) {
|
||||
const issue = ev.currentTarget.data[0] as StatisticsValidationResult;
|
||||
if (issue.type === "unsupported_unit") {
|
||||
showAlertDialog(this, {
|
||||
title: "Unsupported unit",
|
||||
text: html`The unit of your entity is not a suppported unit for the
|
||||
device class of the entity, ${issue.data.device_class}.
|
||||
<br />Statistics can not be generated until this entity has a
|
||||
supported unit. <br /><br />If this unit was provided by an
|
||||
integration, this is a bug. Please report an issue. <br /><br />If you
|
||||
have set this unit yourself, and want to have statistics generated,
|
||||
make sure the unit matched the device class. The supported units are
|
||||
documented in the
|
||||
<a
|
||||
href="https://developers.home-assistant.io/docs/core/entity/sensor"
|
||||
target="_blank"
|
||||
>
|
||||
developer documentation</a
|
||||
>.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (issue.type === "units_changed") {
|
||||
showFixStatisticsUnitsChangedDialog(this, {
|
||||
issue,
|
||||
fixedCallback: () => {
|
||||
this._validateStatistics();
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: "Fix issue",
|
||||
text: "Fixing this issue is not supported yet.",
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
font-size: var(
|
||||
--paper-input-container-shared-input-style_-_font-size
|
||||
);
|
||||
}
|
||||
|
||||
:host([rtl]) th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
tr {
|
||||
vertical-align: top;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: var(--table-row-background-color, #fff);
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--table-row-alternative-background-color, #eee);
|
||||
}
|
||||
td {
|
||||
padding: 4px;
|
||||
min-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"developer-tools-statistics": HaPanelDevStatistics;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { LitElement, TemplateResult, html, CSSResultGroup } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-dialog";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
clearStatistics,
|
||||
updateStatisticsMetadata,
|
||||
} from "../../../data/history";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-radio";
|
||||
import { DialogStatisticsUnitsChangedParams } from "./show-dialog-statistics-fix-units-changed";
|
||||
|
||||
@customElement("dialog-statistics-fix-units-changed")
|
||||
export class DialogStatisticsFixUnitsChanged extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: DialogStatisticsUnitsChangedParams;
|
||||
|
||||
@state() private _action?: "update" | "clear";
|
||||
|
||||
public showDialog(params: DialogStatisticsUnitsChangedParams): void {
|
||||
this._params = params;
|
||||
this._action = "update";
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._action = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.units_changed.title"
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
The unit of this entity changed, we can't store values in multiple
|
||||
units. <br />If the historic statistic values have a wrong unit, you
|
||||
can update the units of the old values. The values will not be
|
||||
updated.<br />Otherwise you can choose to delete all historic
|
||||
statistic values, and start over.
|
||||
</p>
|
||||
|
||||
<h3>How do you want to fix this issue?</h3>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.units_changed.update",
|
||||
this._params.issue.data
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
value="update"
|
||||
name="action"
|
||||
.checked=${this._action === "update"}
|
||||
@change=${this._handleActionChanged}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.developer-tools.tabs.statistics.fix_issue.units_changed.clear`
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
value="clear"
|
||||
name="action"
|
||||
.checked=${this._action === "clear"}
|
||||
@change=${this._handleActionChanged}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
|
||||
<mwc-button slot="primaryAction" @click=${this._fixIssue}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.units_changed.fix"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleActionChanged(ev): void {
|
||||
this._action = ev.target.value;
|
||||
}
|
||||
|
||||
private async _fixIssue(): Promise<void> {
|
||||
if (this._action === "clear") {
|
||||
await clearStatistics(this.hass, [this._params!.issue.data.statistic_id]);
|
||||
} else {
|
||||
await updateStatisticsMetadata(
|
||||
this.hass,
|
||||
this._params!.issue.data.statistic_id,
|
||||
this._params!.issue.data.state_unit
|
||||
);
|
||||
}
|
||||
this._params?.fixedCallback();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [haStyle, haStyleDialog];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-statistics-fix-units-changed": DialogStatisticsFixUnitsChanged;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { StatisticsValidationResultUnitsChanged } from "../../../data/history";
|
||||
|
||||
export const loadFixUnitsDialog = () =>
|
||||
import("./dialog-statistics-fix-units-changed");
|
||||
|
||||
export interface DialogStatisticsUnitsChangedParams {
|
||||
issue: StatisticsValidationResultUnitsChanged;
|
||||
fixedCallback: () => void;
|
||||
}
|
||||
|
||||
export const showFixStatisticsUnitsChangedDialog = (
|
||||
element: HTMLElement,
|
||||
detailParams: DialogStatisticsUnitsChangedParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-statistics-fix-units-changed",
|
||||
dialogImport: loadFixUnitsDialog,
|
||||
dialogParams: detailParams,
|
||||
});
|
||||
};
|
@ -3732,6 +3732,23 @@
|
||||
"listeners": "This template listens for the following state changed events:",
|
||||
"entity": "Entity",
|
||||
"domain": "Domain"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Statistics",
|
||||
"entity": "Entity",
|
||||
"issue": "Issue",
|
||||
"issues": {
|
||||
"units_changed": "The unit of this entity changed from ''{metadata_unit}'' to ''{state_unit}''.",
|
||||
"unsupported_unit": "The unit (''{state_unit}'') of this entity doesn't match a unit of device class ''{device_class}''."
|
||||
},
|
||||
"fix_issue": {
|
||||
"units_changed": {
|
||||
"title": "The unit of this entity changed",
|
||||
"update": "Update the historic statistic values from ''{metadata_unit}'' to ''{state_unit}''",
|
||||
"clear": "Delete all old statistic data for this entity",
|
||||
"fix": "Fix issue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user