mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-14 09:31:38 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a353395d18 | ||
|
|
e648340937 |
No files matched your search
@@ -261,13 +261,19 @@ export const updateStatisticsMetadata = (
|
||||
unit_class,
|
||||
});
|
||||
|
||||
// Without a range, the statistics are removed completely.
|
||||
export const clearStatistics = (
|
||||
hass: Pick<HomeAssistant, "callWS">,
|
||||
statistic_ids: string[]
|
||||
statistic_ids: string[],
|
||||
range?: { start: Date; end: Date }
|
||||
) =>
|
||||
hass.callWS<undefined>({
|
||||
type: "recorder/clear_statistics",
|
||||
statistic_ids,
|
||||
...(range && {
|
||||
start_time: range.start.toISOString(),
|
||||
end_time: range.end.toISOString(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const calculateStatisticSumGrowth = (
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { consume } from "../../../../common/decorators/consume";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/chart/statistics-chart";
|
||||
import "../../../../components/date-picker/ha-date-range-picker";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-spinner";
|
||||
import {
|
||||
apiContext,
|
||||
internationalizationContext,
|
||||
} from "../../../../data/context";
|
||||
import type { Statistics, StatisticType } from "../../../../data/recorder";
|
||||
import {
|
||||
clearStatistics,
|
||||
fetchStatistics,
|
||||
StatisticMeanType,
|
||||
} from "../../../../data/recorder";
|
||||
import { DialogMixin } from "../../../../dialogs/dialog-mixin";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import type { DialogStatisticsClearRangeParams } from "./show-dialog-statistics-clear-range";
|
||||
|
||||
// Longer ranges use hourly statistics to keep the preview fast.
|
||||
const MAX_5MINUTE_PREVIEW_MS = 3 * 24 * 60 * 60 * 1000;
|
||||
|
||||
@customElement("dialog-statistics-clear-range")
|
||||
class DialogStatisticsClearRange extends DialogMixin<DialogStatisticsClearRangeParams>(
|
||||
LitElement
|
||||
) {
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state() private _start = new Date();
|
||||
|
||||
@state() private _end = new Date();
|
||||
|
||||
@state() private _statistics?: Statistics;
|
||||
|
||||
@state() private _busy = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
// Incremented for every fetch, so results of outdated fetches are ignored
|
||||
private _fetchId = 0;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._end.setMinutes(
|
||||
this._end.getMinutes() - (this._end.getMinutes() % 5),
|
||||
0,
|
||||
0
|
||||
);
|
||||
this._start = new Date(this._end.getTime() - 24 * 60 * 60 * 1000);
|
||||
this._fetchStatistics();
|
||||
}
|
||||
|
||||
private get _validRange() {
|
||||
return this._start.getTime() < this._end.getTime();
|
||||
}
|
||||
|
||||
private get _statTypes(): StatisticType[] {
|
||||
return this.params!.statistic.mean_type !== StatisticMeanType.NONE
|
||||
? ["mean", "min", "max"]
|
||||
: ["change"];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
const localize = this._i18n.localize;
|
||||
const statisticId = this.params.statistic.statistic_id;
|
||||
const hasData = Boolean(this._statistics?.[statisticId]?.length);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
width="large"
|
||||
.headerTitle=${localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.title"
|
||||
)}
|
||||
.preventScrimClose=${this._busy}
|
||||
>
|
||||
<p>
|
||||
${localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.info_text",
|
||||
{ name: html`<b>${this.params.name}</b>` }
|
||||
)}
|
||||
</p>
|
||||
<ha-date-range-picker
|
||||
time-picker
|
||||
.startDate=${this._start}
|
||||
.endDate=${this._end}
|
||||
.disabled=${this._busy}
|
||||
@value-changed=${this._rangeChanged}
|
||||
></ha-date-range-picker>
|
||||
${
|
||||
this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
!this._validRange
|
||||
? html`<ha-alert alert-type="error">
|
||||
${localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.invalid_range"
|
||||
)}
|
||||
</ha-alert>`
|
||||
: !this._statistics
|
||||
? html`<div class="preview center">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
: !hasData
|
||||
? html`<div class="preview center">
|
||||
${localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.no_statistics_found"
|
||||
)}
|
||||
</div>`
|
||||
: html`<div class="preview">
|
||||
<statistics-chart
|
||||
.statisticsData=${this._statistics}
|
||||
.metadata=${{ [statisticId]: this.params.statistic }}
|
||||
.names=${{ [statisticId]: this.params.name }}
|
||||
.statTypes=${this._statTypes}
|
||||
.chartType=${
|
||||
this._statTypes[0] === "change" ? "bar" : "line"
|
||||
}
|
||||
.startTime=${this._start}
|
||||
.endTime=${this._end}
|
||||
hide-legend
|
||||
.clickForMoreInfo=${false}
|
||||
height="240px"
|
||||
></statistics-chart>
|
||||
</div>`
|
||||
}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
.disabled=${this._busy}
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
variant="danger"
|
||||
.loading=${this._busy}
|
||||
.disabled=${this._busy || !this._validRange || !hasData}
|
||||
@click=${this._delete}
|
||||
>
|
||||
${localize("ui.common.delete")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _rangeChanged(
|
||||
ev: HASSDomEvent<{ value: { startDate: Date; endDate: Date } }>
|
||||
) {
|
||||
this._start = ev.detail.value.startDate;
|
||||
this._end = ev.detail.value.endDate;
|
||||
this._fetchStatistics();
|
||||
}
|
||||
|
||||
private async _fetchStatistics() {
|
||||
const fetchId = ++this._fetchId;
|
||||
this._statistics = undefined;
|
||||
this._error = undefined;
|
||||
if (!this._validRange) {
|
||||
return;
|
||||
}
|
||||
const start = this._start;
|
||||
const end = this._end;
|
||||
try {
|
||||
const statistics = await fetchStatistics(
|
||||
this._api.callWS,
|
||||
start,
|
||||
end,
|
||||
[this.params!.statistic.statistic_id],
|
||||
end.getTime() - start.getTime() > MAX_5MINUTE_PREVIEW_MS
|
||||
? "hour"
|
||||
: "5minute",
|
||||
undefined,
|
||||
this._statTypes.includes("change") ? ["change"] : ["mean", "min", "max"]
|
||||
);
|
||||
if (fetchId === this._fetchId) {
|
||||
this._statistics = statistics;
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (fetchId === this._fetchId) {
|
||||
this._statistics = {};
|
||||
this._error = err.message || err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _delete() {
|
||||
this._busy = true;
|
||||
this._error = undefined;
|
||||
try {
|
||||
await clearStatistics(this._api, [this.params!.statistic.statistic_id], {
|
||||
start: this._start,
|
||||
end: this._end,
|
||||
});
|
||||
} catch (err: any) {
|
||||
this._busy = false;
|
||||
this._error = err.message || err;
|
||||
return;
|
||||
}
|
||||
showToast(this, {
|
||||
message: this._i18n.localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.deleted"
|
||||
),
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-date-range-picker {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
.preview {
|
||||
min-height: 240px;
|
||||
}
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-statistics-clear-range": DialogStatisticsClearRange;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { StatisticsMetaData } from "../../../../data/recorder";
|
||||
|
||||
export const loadClearRangeDialog = () =>
|
||||
import("./dialog-statistics-clear-range");
|
||||
|
||||
export interface DialogStatisticsClearRangeParams {
|
||||
statistic: StatisticsMetaData;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const showStatisticsClearRangeDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: DialogStatisticsClearRangeParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-statistics-clear-range",
|
||||
dialogImport: loadClearRangeDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
mdiArrowDown,
|
||||
mdiArrowUp,
|
||||
mdiClose,
|
||||
mdiDeleteClock,
|
||||
mdiFormatListChecks,
|
||||
mdiMenuDown,
|
||||
mdiSlopeUphill,
|
||||
@@ -78,6 +79,7 @@ import type { HomeAssistantRegistries } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { fixStatisticsIssue } from "./fix-statistics";
|
||||
import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum";
|
||||
import { showStatisticsClearRangeDialog } from "./show-dialog-statistics-clear-range";
|
||||
|
||||
const FIX_ISSUES_ORDER: Record<StatisticsValidationResult["type"], number> = {
|
||||
no_state: 0,
|
||||
@@ -305,6 +307,24 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
maxWidth: "113px",
|
||||
showNarrow: true,
|
||||
},
|
||||
clear_range: {
|
||||
title: "",
|
||||
label: localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.title"
|
||||
),
|
||||
type: "icon-button",
|
||||
showNarrow: true,
|
||||
template: (statistic) => html`
|
||||
<ha-icon-button
|
||||
.label=${localize(
|
||||
"ui.panel.config.tools.tabs.statistics.clear_range.title"
|
||||
)}
|
||||
.path=${mdiDeleteClock}
|
||||
.statistic=${statistic}
|
||||
@click=${this._showStatisticsClearRangeDialog}
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: localize("ui.panel.config.tools.tabs.statistics.adjust_sum"),
|
||||
@@ -727,6 +747,19 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _showStatisticsClearRangeDialog(
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HTMLElement & { statistic: DisplayedStatisticData }
|
||||
>
|
||||
) {
|
||||
ev.stopPropagation();
|
||||
const statistic = ev.currentTarget.statistic;
|
||||
showStatisticsClearRangeDialog(this, {
|
||||
statistic,
|
||||
name: statistic.displayName,
|
||||
});
|
||||
}
|
||||
|
||||
private _rowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const id = ev.detail.id;
|
||||
if (id in this._states) {
|
||||
|
||||
@@ -4312,6 +4312,13 @@
|
||||
}
|
||||
},
|
||||
"adjust_sum": "Adjust sum",
|
||||
"clear_range": {
|
||||
"title": "Delete statistics in a time range",
|
||||
"info_text": "Delete the statistics of {name} between the start and end time. Use this to remove bad measurements from your graphs. Deleted statistics cannot be restored.",
|
||||
"invalid_range": "The end time must be after the start time.",
|
||||
"no_statistics_found": "No statistics found for this period.",
|
||||
"deleted": "Statistics deleted"
|
||||
},
|
||||
"data_table": {
|
||||
"select_all_issues": "Select all with issues",
|
||||
"name": "Name",
|
||||
|
||||
Reference in new issue
Block a user