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",