Skip to content

Commit

Permalink
Improve error handling in backup status banner (#23604)
Browse files Browse the repository at this point in the history
* Improve error handling in backup status banner

* Fix completion

* Fix loading

* Check attempt and completion date first
  • Loading branch information
piitaya authored Jan 6, 2025
1 parent 2a2b6d3 commit ffff797
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,24 @@ class HaBackupOverviewBackups extends LitElement {

@property({ type: Boolean }) public fetching = false;

private _lastSuccessfulBackup = memoizeOne((backups: BackupContent[]) => {
const sortedBackups = backups
private _sortedBackups = memoizeOne((backups: BackupContent[]) =>
backups
.filter((backup) => backup.with_automatic_settings)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
);

private _lastBackup = memoizeOne((backups: BackupContent[]) => {
const sortedBackups = this._sortedBackups(backups);
return sortedBackups[0] as BackupContent | undefined;
});

private _lastUploadedBackup = memoizeOne((backups: BackupContent[]) => {
const sortedBackups = this._sortedBackups(backups);
return sortedBackups.find(
(backup) => backup.failed_agent_ids?.length === 0
);
});

private _nextBackupDescription(schedule: BackupScheduleState) {
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);

Expand All @@ -65,6 +75,8 @@ class HaBackupOverviewBackups extends LitElement {
}

protected render() {
const now = new Date();

if (this.fetching) {
return html`
<ha-backup-summary-card heading="Loading backups" status="loading">
Expand All @@ -82,24 +94,28 @@ class HaBackupOverviewBackups extends LitElement {
`;
}

const lastSuccessfulBackup = this._lastSuccessfulBackup(this.backups);
const lastBackup = this._lastBackup(this.backups);

const lastAttempt = this.config.last_attempted_automatic_backup
const nextBackupDescription = this._nextBackupDescription(
this.config.schedule.state
);

const lastAttemptDate = this.config.last_attempted_automatic_backup
? new Date(this.config.last_attempted_automatic_backup)
: undefined;
: new Date(0);

const lastCompletedBackupDate = this.config.last_completed_automatic_backup
const lastCompletedDate = this.config.last_completed_automatic_backup
? new Date(this.config.last_completed_automatic_backup)
: undefined;

const now = new Date();
: new Date(0);

const lastBackupDescription = lastSuccessfulBackup
? `Last successful backup ${relativeTime(new Date(lastSuccessfulBackup.date), this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.`
: "You have no successful backups.";
// If last attempt is after last completed backup, show error
if (lastAttemptDate > lastCompletedDate) {
const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`;
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
const secondaryDescription = lastUploadedBackup
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
: nextBackupDescription;

if (lastAttempt && lastAttempt > (lastCompletedBackupDate || 0)) {
const lastAttemptDescription = `The last automatic backup triggered ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
return html`
<ha-backup-summary-card
heading="Last automatic backup failed"
Expand All @@ -108,29 +124,30 @@ class HaBackupOverviewBackups extends LitElement {
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline">${lastAttemptDescription}</span>
<span slot="headline">${description}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span slot="headline">${lastBackupDescription}</span>
<span slot="headline">${secondaryDescription}</span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}

const nextBackupDescription = this._nextBackupDescription(
this.config.schedule.state
);

if (!lastSuccessfulBackup) {
// If no backups yet, show warning
if (!lastBackup) {
const description = "You have no automatic backups yet.";
return html`
<ha-backup-summary-card
heading="No automatic backup available"
description="You have no automatic backups yet."
status="warning"
>
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline">${description}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span slot="headline">${nextBackupDescription}</span>
Expand All @@ -140,10 +157,41 @@ class HaBackupOverviewBackups extends LitElement {
`;
}

const lastBackupDate = new Date(lastBackup.date);

// If last backup
if (lastBackup.failed_agent_ids?.length) {
const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`;
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
const secondaryDescription = lastUploadedBackup
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
: nextBackupDescription;

return html`
<ha-backup-summary-card
heading="Last automatic backup failed"
status="error"
>
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline">${description}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span slot="headline">${secondaryDescription}</span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}

const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`;

const numberOfDays = differenceInDays(
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
addHours(now, -OVERDUE_MARGIN_HOURS),
new Date(lastSuccessfulBackup.date)
lastBackupDate
);

const isOverdue =
Expand All @@ -160,7 +208,7 @@ class HaBackupOverviewBackups extends LitElement {
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline">${lastBackupDescription}</span>
<span slot="headline">${description}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
Expand All @@ -170,12 +218,13 @@ class HaBackupOverviewBackups extends LitElement {
</ha-backup-summary-card>
`;
}

return html`
<ha-backup-summary-card heading=${`Backed up`} status="success">
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline">${lastBackupDescription}</span>
<span slot="headline">${description}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
Expand Down
40 changes: 24 additions & 16 deletions src/panels/config/backup/ha-config-backup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { BackupConfig, BackupContent } from "../../../data/backup";
import {
compareAgents,
fetchBackupConfig,
fetchBackupInfo,
} from "../../../data/backup";
import type { ManagerStateEvent } from "../../../data/backup_manager";
import {
DEFAULT_MANAGER_STATE,
Expand All @@ -15,12 +21,6 @@ import type { HomeAssistant } from "../../../types";
import { showToast } from "../../../util/toast";
import "./ha-config-backup-backups";
import "./ha-config-backup-overview";
import type { BackupConfig, BackupContent } from "../../../data/backup";
import {
compareAgents,
fetchBackupConfig,
fetchBackupInfo,
} from "../../../data/backup";

declare global {
interface HASSDomEvents {
Expand All @@ -47,13 +47,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {

protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetching = true;
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
() => {
this._fetching = false;
}
);

this._fetchAll();
this.addEventListener("ha-refresh-backup-info", () => {
this._fetchBackupInfo();
});
Expand All @@ -62,6 +56,15 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
});
}

private _fetchAll() {
this._fetching = true;
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
() => {
this._fetching = false;
}
);
}

public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated) {
Expand Down Expand Up @@ -128,11 +131,16 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
return [
subscribeBackupEvents(this.hass!, (event) => {
const curState = this._manager.manager_state;

this._manager = event;
if (
event.manager_state === "idle" &&
event.manager_state !== curState
) {
this._fetchAll();
}
if ("state" in event) {
if (event.state === "completed" || event.state === "failed") {
this._fetchBackupInfo();
}
if (event.state === "failed") {
let message = "";
switch (this._manager.manager_state) {
Expand Down

0 comments on commit ffff797

Please sign in to comment.