From e1830470b69ce6c3931455fed32c9ff5ea827b7b Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:40:49 -0800 Subject: [PATCH] Better disabled/error handling on `config/helpers` page (#22237) * Add a way to fix/remove broken helpers * more disabled/sources fixes * Update src/translations/en.json Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update ha-config-helpers.ts --------- Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> --- .../config/helpers/ha-config-helpers.ts | 111 ++++++++++++++++-- .../ha-config-integration-page.ts | 64 +++++----- src/translations/en.json | 3 +- 3 files changed, 134 insertions(+), 44 deletions(-) diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index e73060c6f386..32b5854342ee 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -3,19 +3,23 @@ import { ResizeController } from "@lit-labs/observers/resize-controller"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import { mdiAlertCircle, + mdiCancel, mdiChevronRight, mdiCog, mdiDotsVertical, mdiMenuDown, mdiPencilOff, + mdiProgressHelper, mdiPlus, mdiTag, + mdiTrashCan, } from "@mdi/js"; import type { HassEntity } from "home-assistant-js-websocket"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { debounce } from "../../../common/util/debounce"; import { computeCssColor } from "../../../common/color/compute-color"; import { storage } from "../../../common/decorators/storage"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; @@ -54,7 +58,11 @@ import { subscribeCategoryRegistry, } from "../../../data/category_registry"; import type { ConfigEntry } from "../../../data/config_entries"; -import { subscribeConfigEntries } from "../../../data/config_entries"; +import { + ERROR_STATES, + deleteConfigEntry, + subscribeConfigEntries, +} from "../../../data/config_entries"; import { getConfigFlowHandlers } from "../../../data/config_flow"; import { fullEntitiesContext } from "../../../data/context"; import type { @@ -97,6 +105,7 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; +import { renderConfigEntryError } from "../integrations/ha-config-integration-page"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { isHelperDomain } from "./const"; import { showHelperDetailDialog } from "./show-dialog-helper-detail"; @@ -220,6 +229,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { callback: (entries) => entries[0]?.contentRect.width, }); + private _debouncedFetchEntitySources = debounce( + () => this._fetchEntitySources(), + 500, + false + ); + public hassSubscribe() { return [ subscribeConfigEntries( @@ -236,6 +251,14 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { } else if (message.type === "updated") { newEntries[message.entry.entry_id] = message.entry; } + if ( + this._entitySource && + this._configEntries && + message.entry.state === "loaded" && + this._configEntries[message.entry.entry_id]?.state !== "loaded" + ) { + this._debouncedFetchEntitySources(); + } }); this._configEntries = newEntries; }, @@ -352,6 +375,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { .hass=${this.hass} narrow .items=${[ + ...(helper.configEntry && + ERROR_STATES.includes(helper.configEntry.state) + ? [ + { + path: mdiAlertCircle, + label: this.hass.localize( + "ui.panel.config.helpers.picker.error_information" + ), + warning: true, + action: () => this._showError(helper), + }, + ] + : []), { path: mdiCog, label: this.hass.localize( @@ -366,6 +402,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { ), action: () => this._editCategory(helper), }, + ...(helper.configEntry && + helper.editable && + ERROR_STATES.includes(helper.configEntry.state) && + helper.entity === undefined + ? [ + { + path: mdiTrashCan, + label: this.hass.localize("ui.common.delete"), + warning: true, + action: () => this._deleteEntry(helper), + }, + ] + : []), ]} > @@ -417,17 +466,27 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { }; }); - const entries = Object.values(configEntriesCopy).map((configEntry) => ({ - id: configEntry.entry_id, - entity_id: "", - icon: mdiAlertCircle, - name: configEntry.title || "", - editable: true, - type: configEntry.domain, - configEntry, - entity: undefined, - selectable: false, - })); + const entries = Object.values(configEntriesCopy).map((configEntry) => { + const entityEntry = Object.values(entityEntries).find( + (entry) => entry.config_entry_id === configEntry.entry_id + ); + const entityIsDisabled = !!entityEntry?.disabled_by; + return { + id: entityIsDisabled ? entityEntry.entity_id : configEntry.entry_id, + entity_id: entityIsDisabled ? entityEntry.entity_id : "", + icon: entityIsDisabled + ? mdiCancel + : configEntry.state === "setup_in_progress" + ? mdiProgressHelper + : mdiAlertCircle, + name: configEntry.title || "", + editable: true, + type: configEntry.domain, + configEntry, + entity: undefined, + selectable: entityIsDisabled, + }; + }); return [...states, ...entries] .filter((item) => @@ -1081,6 +1140,34 @@ ${rejected } } + private _showError(helper: HelperItem) { + showAlertDialog(this, { + title: this.hass.localize("ui.errors.config.configuration_error"), + text: renderConfigEntryError(this.hass, helper.configEntry!), + warning: true, + }); + } + + private async _deleteEntry(helper: HelperItem) { + const confirmed = await showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.integrations.config_entry.delete_confirm_title", + { title: helper.configEntry!.title } + ), + text: this.hass.localize( + "ui.panel.config.integrations.config_entry.delete_confirm_text" + ), + confirmText: this.hass!.localize("ui.common.delete"), + dismissText: this.hass!.localize("ui.common.cancel"), + destructive: true, + }); + + if (!confirmed) { + return; + } + deleteConfigEntry(this.hass, helper.id); + } + private _openSettings(helper: HelperItem) { if (helper.entity) { showMoreInfoDialog(this, { diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 876c3e59d89d..9beef8810663 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -106,6 +106,38 @@ import { fileDownload } from "../../../util/file_download"; import type { DataEntryFlowProgressExtended } from "./ha-config-integrations"; import { showAddIntegrationDialog } from "./show-add-integration-dialog"; +export const renderConfigEntryError = ( + hass: HomeAssistant, + entry: ConfigEntry +): TemplateResult => { + if (entry.reason) { + if (entry.error_reason_translation_key) { + const lokalisePromExc = hass + .loadBackendTranslation("exceptions", entry.domain) + .then( + (localize) => + localize( + `component.${entry.domain}.exceptions.${entry.error_reason_translation_key}.message`, + entry.error_reason_translation_placeholders ?? undefined + ) || entry.reason + ); + return html`${until(lokalisePromExc)}`; + } + const lokalisePromError = hass + .loadBackendTranslation("config", entry.domain) + .then( + (localize) => + localize(`component.${entry.domain}.config.error.${entry.reason}`) || + entry.reason + ); + return html`${until(lokalisePromError, entry.reason)}`; + } + return html` +
+ ${hass.localize("ui.panel.config.integrations.config_entry.check_the_logs")} + `; +}; + @customElement("ha-config-integration-page") class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @@ -618,37 +650,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { stateText = [ `ui.panel.config.integrations.config_entry.state.${item.state}`, ]; - if (item.reason) { - if (item.error_reason_translation_key) { - const lokalisePromExc = this.hass - .loadBackendTranslation("exceptions", item.domain) - .then( - (localize) => - localize( - `component.${item.domain}.exceptions.${item.error_reason_translation_key}.message`, - item.error_reason_translation_placeholders ?? undefined - ) || item.reason - ); - stateTextExtra = html`${until(lokalisePromExc)}`; - } else { - const lokalisePromError = this.hass - .loadBackendTranslation("config", item.domain) - .then( - (localize) => - localize( - `component.${item.domain}.config.error.${item.reason}` - ) || item.reason - ); - stateTextExtra = html`${until(lokalisePromError, item.reason)}`; - } - } else { - stateTextExtra = html` -
- ${this.hass.localize( - "ui.panel.config.integrations.config_entry.check_the_logs" - )} - `; - } + stateTextExtra = renderConfigEntryError(this.hass, item); } const devices = this._getConfigEntryDevices(item); diff --git a/src/translations/en.json b/src/translations/en.json index b72a7a54d118..70e23cff0312 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2343,7 +2343,8 @@ }, "create_helper": "Create helper", "no_helpers": "Looks like you don't have any helpers yet!", - "search": "Search {number} helpers" + "search": "Search {number} helpers", + "error_information": "Error information" }, "dialog": { "create": "Create",