From 992a4cd98a423e47c19ad2545519e19ef6b73f95 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:59:30 -0700 Subject: [PATCH] Improve automation save timeout (#27584) * Improve automation save timeout * junk * fix error handling * fix error handling * translate fix * Fix typo --- .../dialog-automation-save-timeout.ts | 130 ++++++++++++++++++ .../show-dialog-automation-save-timeout.ts | 31 +++++ .../config/automation/ha-automation-editor.ts | 37 +++-- src/panels/config/script/ha-script-editor.ts | 37 +++-- src/translations/en.json | 4 +- 5 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 src/panels/config/automation/automation-save-timeout-dialog/dialog-automation-save-timeout.ts create mode 100644 src/panels/config/automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout.ts diff --git a/src/panels/config/automation/automation-save-timeout-dialog/dialog-automation-save-timeout.ts b/src/panels/config/automation/automation-save-timeout-dialog/dialog-automation-save-timeout.ts new file mode 100644 index 0000000000..0ac163b9a8 --- /dev/null +++ b/src/panels/config/automation/automation-save-timeout-dialog/dialog-automation-save-timeout.ts @@ -0,0 +1,130 @@ +import type { CSSResultGroup } from "lit"; +import { LitElement, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-wa-dialog"; +import "../../../../components/ha-spinner"; +import "../../../../components/ha-alert"; +import "../../../../components/ha-button"; +import "../../../../components/ha-dialog-footer"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import type { AutomationSaveTimeoutDialogParams } from "./show-dialog-automation-save-timeout"; + +@customElement("ha-dialog-automation-save-timeout") +class DialogAutomationSaveTimeout extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _opened = false; + + @state() private _saveComplete = false; + + private _params!: AutomationSaveTimeoutDialogParams; + + public showDialog(params: AutomationSaveTimeoutDialogParams): void { + this._opened = true; + this._params = params; + this._saveComplete = false; + + this._params.savedPromise.then(() => { + this._saveComplete = true; + }); + } + + public closeDialog(): void { + this._opened = false; + } + + private _dialogClosed() { + this._params.onClose?.(); + + if (this._opened) { + fireEvent(this, "dialog-closed"); + } + this._opened = false; + return true; + } + + protected render() { + const title = this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_title", + { + type: this.hass.localize( + `ui.panel.config.automation.editor.type_${this._params.type}` + ), + } + ); + + return html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_text", + { + type: this.hass.localize( + `ui.panel.config.automation.editor.type_${this._params.type}` + ), + types: this.hass.localize( + `ui.panel.config.automation.editor.type_${this._params.type}_plural` + ), + } + )} +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_keep_waiting", + { + type: this.hass.localize( + `ui.panel.config.automation.editor.type_${this._params.type}` + ), + } + )} + ${this._saveComplete + ? html`

+ ${this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_timedout_success" + )}` + : html`
+ +
`} +
+ + + ${this.hass.localize( + `ui.common.${this._saveComplete ? "ok" : "cancel"}` + )} + + +
+ `; + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + .loading { + display: flex; + justify-content: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-automation-save-timeout": DialogAutomationSaveTimeout; + } +} diff --git a/src/panels/config/automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout.ts b/src/panels/config/automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout.ts new file mode 100644 index 0000000000..317864c377 --- /dev/null +++ b/src/panels/config/automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout.ts @@ -0,0 +1,31 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; + +export const loadAutomationSaveTimeoutDialog = () => + import("./dialog-automation-save-timeout"); + +export interface AutomationSaveTimeoutDialogParams { + onClose?: () => void; + savedPromise: Promise; + type: "automation" | "script"; +} + +export const showAutomationSaveTimeoutDialog = ( + element: HTMLElement, + dialogParams: AutomationSaveTimeoutDialogParams +) => + new Promise((resolve) => { + const origClose = dialogParams.onClose; + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-automation-save-timeout", + dialogImport: loadAutomationSaveTimeoutDialog, + dialogParams: { + ...dialogParams, + onClose: () => { + resolve(); + if (origClose) { + origClose(); + } + }, + }, + }); + }); diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 2a412caedc..b1cf19f336 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -89,6 +89,7 @@ import { import "./blueprint-automation-editor"; import "./manual-automation-editor"; import type { HaManualAutomationEditor } from "./manual-automation-editor"; +import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout"; declare global { interface HTMLElementTagNameMap { @@ -1136,28 +1137,22 @@ export class HaAutomationEditor extends PreventUnsavedMixin( entityId = automation.entity_id; } catch (e) { if (e instanceof Error && e.name === "TimeoutError") { - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.automation.editor.new_automation_setup_failed_title", - { - type: this.hass.localize( - "ui.panel.config.automation.editor.type_automation" - ), - } - ), - text: this.hass.localize( - "ui.panel.config.automation.editor.new_automation_setup_failed_text", - { - type: this.hass.localize( - "ui.panel.config.automation.editor.type_automation" - ), - types: this.hass.localize( - "ui.panel.config.automation.editor.type_automation_plural" - ), - } - ), - warning: true, + // Show the dialog and give user a chance to wait for the registry + // to respond. + await showAutomationSaveTimeoutDialog(this, { + savedPromise: entityRegPromise, + type: "automation", }); + try { + // We already gave the user a chance to wait once, so if they skipped + // the dialog and it's still not there just immediately timeout. + const automation = await promiseTimeout(0, entityRegPromise); + entityId = automation.entity_id; + } catch (e2) { + if (!(e2 instanceof Error && e2.name === "TimeoutError")) { + throw e2; + } + } } else { throw e; } diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 136f4dc2af..bd86392d22 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -77,6 +77,7 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor import "./blueprint-script-editor"; import "./manual-script-editor"; import type { HaManualScriptEditor } from "./manual-script-editor"; +import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout"; export class HaScriptEditor extends SubscribeMixin( PreventUnsavedMixin(KeyboardShortcutMixin(LitElement)) @@ -1051,28 +1052,22 @@ export class HaScriptEditor extends SubscribeMixin( } catch (e) { entityId = undefined; if (e instanceof Error && e.name === "TimeoutError") { - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.automation.editor.new_automation_setup_failed_title", - { - type: this.hass.localize( - "ui.panel.config.automation.editor.type_script" - ), - } - ), - text: this.hass.localize( - "ui.panel.config.automation.editor.new_automation_setup_failed_text", - { - type: this.hass.localize( - "ui.panel.config.automation.editor.type_script" - ), - types: this.hass.localize( - "ui.panel.config.automation.editor.type_script_plural" - ), - } - ), - warning: true, + // Show the dialog and give user a chance to wait for the registry + // to respond. + await showAutomationSaveTimeoutDialog(this, { + savedPromise: entityRegPromise, + type: "script", }); + try { + // We already gave the user a chance to wait once, so if they skipped + // the dialog and it's still not there just immediately timeout. + const automation = await promiseTimeout(0, entityRegPromise); + entityId = automation.entity_id; + } catch (e2) { + if (!(e2 instanceof Error && e2.name === "TimeoutError")) { + throw e2; + } + } } else { throw e; } diff --git a/src/translations/en.json b/src/translations/en.json index da378cd15f..707c28a65a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3923,8 +3923,10 @@ "type_script": "script", "type_automation_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::automation%]", "type_script_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::script%]", - "new_automation_setup_failed_title": "New {type} setup failed", + "new_automation_setup_failed_title": "New {type} setup timed out", "new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", + "new_automation_setup_keep_waiting": "You may continue to wait for a response from the server, in case it is just taking an unusually long time to process this {type}.", + "new_automation_setup_timedout_success": "The server has responded and this has now setup successfully. You may now close this dialog.", "item_pasted": "{item} pasted", "ctrl": "Ctrl", "del": "Del",