From 3b84e4913859dd19c18f353a9b8a77cfba87e2e8 Mon Sep 17 00:00:00 2001 From: karwosts Date: Mon, 30 Dec 2024 20:33:26 -0800 Subject: [PATCH 1/3] Display an error if saving new automation times out --- .../config/automation/ha-automation-editor.ts | 33 +++++++++++++++++-- src/translations/en.json | 5 +++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 26fe6cc0f67c..965895709971 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -307,12 +307,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin( ${this.hass.localize( - this._readOnly + this._readOnly && !this._saving ? "ui.panel.config.automation.editor.migrate" : "ui.panel.config.automation.editor.duplicate" )} @@ -423,7 +424,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( > ` - : this._readOnly + : this._readOnly && !this._saving ? html`${this.hass.localize( "ui.panel.config.automation.editor.read_only" @@ -496,7 +497,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin( class=${classMap({ dirty: !this._readOnly && this._dirty, })} - .label=${this.hass.localize("ui.panel.config.automation.editor.save")} + .label=${this._saving + ? this.hass.localize("ui.panel.config.automation.editor.saving") + : this.hass.localize("ui.panel.config.automation.editor.save")} .disabled=${this._saving} extended @click=${this._saveAutomation} @@ -944,7 +947,31 @@ export class HaAutomationEditor extends PreventUnsavedMixin( // wait for automation to appear in entity registry when creating a new automation if (entityRegPromise) { + const timeout = setTimeout(() => { + this._readOnly = true; + this._dirty = false; + 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" + ), + } + ), + warning: true, + }); + }, 5000); const automation = await entityRegPromise; + clearTimeout(timeout); entityId = automation.entity_id; } diff --git a/src/translations/en.json b/src/translations/en.json index 59327c196e21..2585c7e74a75 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3021,6 +3021,7 @@ "load_error_not_deletable": "Only automations in automations.yaml can be deleted.", "load_error_unknown": "Error loading automation ({err_no}).", "save": "Save", + "saving": "Saving", "unsaved_confirm_title": "Leave editor?", "unsaved_confirm_text": "Unsaved changes will be lost.", "alias": "Name", @@ -3064,6 +3065,10 @@ "unknown_entity": "unknown entity", "edit_unknown_device": "Editor not available for unknown device", "switch_ui_yaml_error": "There are currently YAML errors in the automation, and it cannot be parsed. Switching to UI mode may cause pending changes to be lost. Press cancel to correct any errors before proceeding to prevent loss of pending changes, or continue if you are sure.", + "type_automation": "automation", + "type_script": "script", + "new_automation_setup_failed_title": "New {type} setup failed", + "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 or editable until this is corrected, and automations are reloaded.", "triggers": { "name": "Triggers", "header": "When", From 81bd1274886aaeb0421fbe54fcf90542d0a1082f Mon Sep 17 00:00:00 2001 From: karwosts Date: Tue, 31 Dec 2024 07:47:35 -0800 Subject: [PATCH 2/3] changes --- src/common/util/promise-timeout.ts | 20 +++++- .../config/automation/ha-automation-editor.ts | 66 +++++++++++-------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/common/util/promise-timeout.ts b/src/common/util/promise-timeout.ts index 43b3359026f9..c38acce11a40 100644 --- a/src/common/util/promise-timeout.ts +++ b/src/common/util/promise-timeout.ts @@ -1,7 +1,25 @@ +class TimeoutError extends Error { + public timeout: number; + + constructor(timeout: number, ...params) { + super(...params); + + // Maintains proper stack trace for where our error was thrown (only available on V8) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, TimeoutError); + } + + this.name = "TimeoutError"; + // Custom debugging information + this.timeout = timeout; + this.message = `Timed out in ${timeout} ms.`; + } +} + export const promiseTimeout = (ms: number, promise: Promise | any) => { const timeout = new Promise((_resolve, reject) => { setTimeout(() => { - reject(`Timed out in ${ms} ms.`); + reject(new TimeoutError(ms)); }, ms); }); diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 965895709971..0b66b58fc963 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -27,6 +27,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; import { afterNextRender } from "../../../common/util/render-status"; +import { promiseTimeout } from "../../../common/util/promise-timeout"; import "../../../components/ha-button-menu"; import "../../../components/ha-fab"; import "../../../components/ha-icon"; @@ -142,6 +143,8 @@ export class HaAutomationEditor extends PreventUnsavedMixin( @state() private _saving = false; + @state() private _saveFailed = false; + @state() @consume({ context: fullEntitiesContext, subscribe: true }) _entityRegistry!: EntityRegistryEntry[]; @@ -307,13 +310,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin( ${this.hass.localize( - this._readOnly && !this._saving + this._readOnly && !this._saveFailed ? "ui.panel.config.automation.editor.migrate" : "ui.panel.config.automation.editor.duplicate" )} @@ -424,7 +427,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( > ` - : this._readOnly && !this._saving + : this._readOnly && !this._saveFailed ? html`${this.hass.localize( "ui.panel.config.automation.editor.read_only" @@ -947,32 +950,37 @@ export class HaAutomationEditor extends PreventUnsavedMixin( // wait for automation to appear in entity registry when creating a new automation if (entityRegPromise) { - const timeout = setTimeout(() => { - this._readOnly = true; - this._dirty = false; - 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" - ), - } - ), - warning: true, - }); - }, 5000); - const automation = await entityRegPromise; - clearTimeout(timeout); - entityId = automation.entity_id; + try { + const automation = await promiseTimeout(2000, entityRegPromise); + entityId = automation.entity_id; + } catch (e) { + if (e instanceof Error && e.name === "TimeoutError") { + this._readOnly = true; + this._saveFailed = true; + this._dirty = false; + 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" + ), + } + ), + warning: true, + }); + return; + } + throw e; + } } if (entityId) { From c3a4d91adc81f4226fb03872776b2e42ce420631 Mon Sep 17 00:00:00 2001 From: karwosts Date: Tue, 31 Dec 2024 10:26:47 -0800 Subject: [PATCH 3/3] update --- .../config/automation/ha-automation-editor.ts | 18 +++++------------- src/translations/en.json | 3 +-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 0b66b58fc963..e6ca39758471 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -143,8 +143,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin( @state() private _saving = false; - @state() private _saveFailed = false; - @state() @consume({ context: fullEntitiesContext, subscribe: true }) _entityRegistry!: EntityRegistryEntry[]; @@ -310,13 +308,12 @@ export class HaAutomationEditor extends PreventUnsavedMixin( ${this.hass.localize( - this._readOnly && !this._saveFailed + this._readOnly ? "ui.panel.config.automation.editor.migrate" : "ui.panel.config.automation.editor.duplicate" )} @@ -427,7 +424,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( > ` - : this._readOnly && !this._saveFailed + : this._readOnly ? html`${this.hass.localize( "ui.panel.config.automation.editor.read_only" @@ -500,9 +497,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( class=${classMap({ dirty: !this._readOnly && this._dirty, })} - .label=${this._saving - ? this.hass.localize("ui.panel.config.automation.editor.saving") - : this.hass.localize("ui.panel.config.automation.editor.save")} + .label=${this.hass.localize("ui.panel.config.automation.editor.save")} .disabled=${this._saving} extended @click=${this._saveAutomation} @@ -955,9 +950,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin( entityId = automation.entity_id; } catch (e) { if (e instanceof Error && e.name === "TimeoutError") { - this._readOnly = true; - this._saveFailed = true; - this._dirty = false; showAlertDialog(this, { title: this.hass.localize( "ui.panel.config.automation.editor.new_automation_setup_failed_title", @@ -977,9 +969,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin( ), warning: true, }); - return; + } else { + throw e; } - throw e; } } diff --git a/src/translations/en.json b/src/translations/en.json index 2585c7e74a75..930d02fb4143 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3021,7 +3021,6 @@ "load_error_not_deletable": "Only automations in automations.yaml can be deleted.", "load_error_unknown": "Error loading automation ({err_no}).", "save": "Save", - "saving": "Saving", "unsaved_confirm_title": "Leave editor?", "unsaved_confirm_text": "Unsaved changes will be lost.", "alias": "Name", @@ -3068,7 +3067,7 @@ "type_automation": "automation", "type_script": "script", "new_automation_setup_failed_title": "New {type} setup failed", - "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 or editable until this is corrected, and automations are reloaded.", + "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 automations are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", "triggers": { "name": "Triggers", "header": "When",