From c7050e4676e5469aa7713247bba03e316dcbe6b2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 29 Mar 2022 20:40:17 -0700 Subject: [PATCH] Update adjust statistic dialog (#12118) * Update text for adjust statistic dialog * Change everything * Import type * Max show 5 * Revert back the API change * Hide adjust button if no sum * Adjustments * Update src/panels/developer-tools/statistics/developer-tools-statistics.ts * Render optional Co-authored-by: Zack --- src/data/history.ts | 2 + .../statistics/developer-tools-statistics.ts | 48 +-- .../dialog-statistics-adjust-sum.ts | 388 ++++++++++++++---- 3 files changed, 325 insertions(+), 113 deletions(-) diff --git a/src/data/history.ts b/src/data/history.ts index 8b8e708f5c..a631c3423a 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -84,6 +84,8 @@ export interface StatisticsMetaData { statistic_id: string; source: string; name?: string | null; + has_sum: boolean; + has_mean: boolean; } export type StatisticsValidationResult = diff --git a/src/panels/developer-tools/statistics/developer-tools-statistics.ts b/src/panels/developer-tools/statistics/developer-tools-statistics.ts index 3183133beb..b9b2359dcf 100644 --- a/src/panels/developer-tools/statistics/developer-tools-statistics.ts +++ b/src/panels/developer-tools/statistics/developer-tools-statistics.ts @@ -6,7 +6,6 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/ha-icon-overflow-menu"; import "../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import { subscribeEntityRegistry } from "../../../data/entity_registry"; @@ -24,9 +23,9 @@ import { import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; +import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum"; import { showFixStatisticsUnitsChangedDialog } from "./show-dialog-statistics-fix-units-changed"; import { showFixStatisticsUnsupportedUnitMetadataDialog } from "./show-dialog-statistics-fix-unsupported-unit-meta"; -import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum"; const FIX_ISSUES_ORDER = { no_state: 0, @@ -116,27 +115,21 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { }, actions: { title: "", - type: "overflow-menu", - template: ( - _info, - statistic: StatisticsMetaData - ) => html` - showStatisticsAdjustSumDialog(this, { - statistic: statistic, - }), - }, - ]} - style="color: var(--secondary-text-color)" - >`, + label: localize("ui.panel.developer-tools.tabs.statistics.adjust_sum"), + type: "icon-button", + template: (_info, statistic: StatisticsMetaData) => + statistic.has_sum + ? html` + + ` + : "", }, }) ); @@ -154,6 +147,13 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { `; } + private _showStatisticsAdjustSumDialog(ev) { + ev.stopPropagation(); + showStatisticsAdjustSumDialog(this, { + statistic: ev.currentTarget.statistic, + }); + } + private _rowClicked(ev) { const id = ev.detail.id; if (id in this.hass.states) { @@ -212,6 +212,8 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { source: "", state: this.hass.states[statisticId], issues: issues[statisticId], + has_mean: false, + has_sum: false, }); } }); diff --git a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts index 9819177799..bae0576f9e 100644 --- a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts +++ b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts @@ -1,24 +1,37 @@ import "@material/mwc-button/mwc-button"; -import { LitElement, TemplateResult, html, CSSResultGroup } from "lit"; +import "@material/mwc-list/mwc-list-item"; +import { mdiChevronRight } from "@mdi/js"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import "../../../components/ha-dialog"; +import { formatDateTime } from "../../../common/datetime/format_date_time"; import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-dialog"; +import "../../../components/ha-form/ha-form"; +import "../../../components/ha-selector/ha-selector-datetime"; +import "../../../components/ha-selector/ha-selector-number"; +import "../../../components/ha-svg-icon"; +import { + adjustStatisticsSum, + fetchStatistics, + StatisticValue, +} from "../../../data/history"; +import type { DateTimeSelector, NumberSelector } from "../../../data/selector"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { haStyle, haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../../../components/ha-formfield"; -import "../../../components/ha-radio"; -import "../../../components/ha-form/ha-form"; -import type { DialogStatisticsAdjustSumParams } from "./show-dialog-statistics-adjust-sum"; -import type { - HaFormBaseSchema, - HaFormSchema, -} from "../../../components/ha-form/types"; -import { adjustStatisticsSum } from "../../../data/history"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showToast } from "../../../util/toast"; +import type { DialogStatisticsAdjustSumParams } from "./show-dialog-statistics-adjust-sum"; -let lastMoment: string | undefined; +/* eslint-disable lit/no-template-arrow */ @customElement("dialog-statistics-adjust-sum") export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { @@ -26,29 +39,54 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { @state() private _params?: DialogStatisticsAdjustSumParams; - @state() private _data?: { - moment: string; - amount: number; + @state() private _busy = false; + + @state() private _moment?: string; + + @state() private _stats5min?: StatisticValue[]; + + @state() private _statsHour?: StatisticValue[]; + + @state() private _chosenStat?: StatisticValue; + + private _origAmount?: number; + + @state() private _amount?: number; + + private _dateTimeSelector: DateTimeSelector = { + datetime: {}, }; - @state() private _busy = false; + private _amountSelector = memoizeOne( + (unit_of_measurement: string): NumberSelector => ({ + number: { + step: 0.01, + unit_of_measurement, + mode: "box", + }, + }) + ); public showDialog(params: DialogStatisticsAdjustSumParams): void { this._params = params; - this._busy = false; const now = new Date(); - this._data = { - moment: - lastMoment || - `${now.getFullYear()}-${ - now.getMonth() + 1 - }-${now.getDate()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`, - amount: 0, - }; + this._moment = `${now.getFullYear()}-${ + now.getMonth() + 1 + }-${now.getDate()} ${now.getHours()}:${ + now.getMinutes() - (now.getMinutes() % 5) + }:00`; + this._fetchStats(); } public closeDialog(): void { this._params = undefined; + this._moment = undefined; + this._stats5min = undefined; + this._statsHour = undefined; + this._origAmount = undefined; + this._amount = undefined; + this._chosenStat = undefined; + this._busy = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -57,78 +95,201 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { return html``; } + let content: TemplateResult; + + if (!this._chosenStat) { + content = this._renderPickStatistic(); + } else { + content = this._renderAdjustStat(); + } + return html` - - - - + ${content} `; } - private _getSchema = memoizeOne((statistic): HaFormSchema[] => [ - { - type: "constant", - name: "name", - value: statistic.name || statistic.statistic_id, - }, - { - name: "moment", - required: true, - selector: { - datetime: {}, - }, - }, - { - name: "amount", - required: true, - default: 0, - selector: { - number: { - mode: "box", - step: 0.1, - unit_of_measurement: statistic.unit_of_measurement, - }, - }, - }, - ]); - - private _computeLabel(value: HaFormBaseSchema) { - switch (value.name) { - case "name": - return "Statistic"; - case "moment": - return "Moment to adjust"; - case "amount": - return "Amount"; - default: - return value.name; + protected shouldUpdate(changedProps: PropertyValues): boolean { + if (changedProps.size !== 1 || !changedProps.has("hass")) { + return true; } + // We only respond to hass changes if the translations changed + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + return !oldHass || oldHass.localize !== this.hass.localize; } - private _valueChanged(ev) { - this._data = ev.detail.value; + private _renderPickStatistic() { + let stats: TemplateResult; + + if (!this._stats5min || !this._statsHour) { + stats = html``; + } else if (this._statsHour.length < 2 && this._stats5min.length < 2) { + stats = html`

No statistics found for this period.

`; + } else { + const data = + this._stats5min.length >= 2 ? this._stats5min : this._statsHour; + const unit = this._params!.statistic.unit_of_measurement; + const rows: TemplateResult[] = []; + for (let i = 1; i < data.length; i++) { + const stat = data[i]; + const growth = Math.round((stat.sum! - data[i - 1].sum!) * 100) / 100; + rows.push(html` + { + this._chosenStat = stat; + this._origAmount = growth; + this._amount = growth; + }} + > + ${growth} ${unit} + + ${formatDateTime(new Date(stat.start), this.hass.locale)} + + + + `); + } + stats = html`${rows}`; + } + + return html` +
+ Sometimes the statistics end up being incorrect for a specific point in + time. This can mess up your beautiful graphs! Select a time below to + find the bad moment and adjust the data. +
+ +
${stats}
+ + `; + } + + private _dateTimeSelectorChanged(ev) { + this._moment = ev.detail.value; + this._fetchStats(); + } + + private _renderAdjustStat() { + return html` +
+ ${this._params!.statistic.name || this._params!.statistic.statistic_id} +
+ +
+ Start + ${formatDateTime( + new Date(this._chosenStat!.start), + this.hass.locale + )} +
+ +
+ End + ${formatDateTime( + new Date(this._chosenStat!.end), + this.hass.locale + )} +
+ + { + this._amount = ev.detail.value; + }} + > + + { + this._fixIssue(); + }} + > + { + this._chosenStat = undefined; + }} + > + `; + } + + private async _fetchStats(): Promise { + this._stats5min = undefined; + this._statsHour = undefined; + const statId = this._params!.statistic.statistic_id; + const moment = new Date(this._moment!); + + // Search 3 hours before and 3 hours after chosen time + const hourStatStart = new Date(moment.getTime()); + hourStatStart.setTime(hourStatStart.getTime() - 3 * 3600 * 1000); + const hourStatEnd = new Date(moment.getTime()); + hourStatEnd.setTime(hourStatEnd.getTime() + 3 * 3600 * 1000); + + const statsHourData = await fetchStatistics( + this.hass, + hourStatStart, + hourStatEnd, + [statId], + "hour" + ); + this._statsHour = + statId in statsHourData ? statsHourData[statId].slice(0, 6) : []; + + // Can't have 5 min data if no hourly data + if (this._statsHour.length === 0) { + this._stats5min = []; + return; + } + + // Search 15 minutes before and 15 minutes after chosen time + const minStatStart = new Date(moment.getTime()); + minStatStart.setTime(minStatStart.getTime() - 15 * 60 * 1000); + const minStatEnd = new Date(moment.getTime()); + minStatEnd.setTime(minStatEnd.getTime() + 15 * 60 * 1000); + + const stats5MinData = await fetchStatistics( + this.hass, + minStatStart, + minStatEnd, + [statId], + "5minute" + ); + + this._stats5min = + statId in stats5MinData ? stats5MinData[statId].slice(0, 6) : []; } private async _fixIssue(): Promise { @@ -137,8 +298,8 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { await adjustStatisticsSum( this.hass, this._params!.statistic.statistic_id, - this._data!.moment, - this._data!.amount + this._chosenStat!.start, + this._amount! - this._origAmount! ); } catch (err: any) { this._busy = false; @@ -150,12 +311,59 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { showToast(this, { message: "Statistic sum adjusted", }); - lastMoment = this._data!.moment; this.closeDialog(); } static get styles(): CSSResultGroup { - return [haStyle, haStyleDialog]; + return [ + haStyle, + haStyleDialog, + css` + @media all and (max-width: 450px), all and (max-height: 500px) { + /* overrule the ha-style-dialog max-height on small screens */ + ha-dialog { + --mdc-dialog-max-height: 100%; + height: 100%; + } + } + + @media all and (min-width: 850px) { + ha-dialog { + --mdc-dialog-max-height: 80%; + --mdc-dialog-max-height: 80%; + } + } + + @media all and (min-width: 451px) and (min-height: 501px) { + ha-dialog { + --mdc-dialog-max-width: 480px; + } + } + + .text-content, + ha-selector-datetime, + ha-selector-number { + margin-bottom: 20px; + } + mwc-list-item { + margin: 0 -24px; + --mdc-list-side-padding: 24px; + } + .table-row { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + } + .stat-list { + min-height: 360px; + display: flex; + flex-direction: column; + } + .stat-list ha-circular-progress { + margin: 0 auto; + } + `, + ]; } }