Skip to content

Commit

Permalink
Allow entities table to delete helpers (#22248)
Browse files Browse the repository at this point in the history
* Allow deleting helpers in entities table

* Fix calling the right delete on restored legacy helpers
  • Loading branch information
karwosts authored Nov 12, 2024
1 parent db03e27 commit 28703b3
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 8 deletions.
91 changes: 91 additions & 0 deletions src/common/entity/delete_entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { HomeAssistant } from "../../types";
import type { IntegrationManifest } from "../../data/integration";
import { computeDomain } from "./compute_domain";
import { HELPERS_CRUD } from "../../data/helpers_crud";
import type { Helper } from "../../panels/config/helpers/const";
import { isHelperDomain } from "../../panels/config/helpers/const";
import { isComponentLoaded } from "../config/is_component_loaded";
import type { EntityRegistryEntry } from "../../data/entity_registry";
import { removeEntityRegistryEntry } from "../../data/entity_registry";
import type { ConfigEntry } from "../../data/config_entries";
import { deleteConfigEntry } from "../../data/config_entries";

export const isDeletableEntity = (
hass: HomeAssistant,
entity_id: string,
manifests: IntegrationManifest[],
entityRegistry: EntityRegistryEntry[],
configEntries: ConfigEntry[],
fetchedHelpers: Helper[]
): boolean => {
const restored = !!hass.states[entity_id]?.attributes.restored;
if (restored) {
return true;
}

const domain = computeDomain(entity_id);
const entityRegEntry = entityRegistry.find((e) => e.entity_id === entity_id);
if (isHelperDomain(domain)) {
return !!(
isComponentLoaded(hass, domain) &&
entityRegEntry &&
fetchedHelpers.some((e) => e.id === entityRegEntry.unique_id)
);
}

const configEntryId = entityRegEntry?.config_entry_id;
if (!configEntryId) {
return false;
}
const configEntry = configEntries.find((e) => e.entry_id === configEntryId);
return (
manifests.find((m) => m.domain === configEntry?.domain)
?.integration_type === "helper"
);
};

export const deleteEntity = (
hass: HomeAssistant,
entity_id: string,
manifests: IntegrationManifest[],
entityRegistry: EntityRegistryEntry[],
configEntries: ConfigEntry[],
fetchedHelpers: Helper[]
) => {
// This function assumes the entity_id already was validated by isDeletableEntity and does not repeat all those checks.
const domain = computeDomain(entity_id);
const entityRegEntry = entityRegistry.find((e) => e.entity_id === entity_id);
if (isHelperDomain(domain)) {
if (isComponentLoaded(hass, domain)) {
if (
entityRegEntry &&
fetchedHelpers.some((e) => e.id === entityRegEntry.unique_id)
) {
HELPERS_CRUD[domain].delete(hass, entityRegEntry.unique_id);
return;
}
}
const stateObj = hass.states[entity_id];
if (!stateObj?.attributes.restored) {
return;
}
removeEntityRegistryEntry(hass, entity_id);
return;
}

const configEntryId = entityRegEntry?.config_entry_id;
const configEntry = configEntryId
? configEntries.find((e) => e.entry_id === configEntryId)
: undefined;
const isHelperEntryType = configEntry
? manifests.find((m) => m.domain === configEntry.domain)
?.integration_type === "helper"
: false;

if (isHelperEntryType) {
deleteConfigEntry(hass, configEntryId!);
return;
}

removeEntityRegistryEntry(hass, entity_id);
};
70 changes: 62 additions & 8 deletions src/panels/config/entities/ha-config-entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import {
isDeletableEntity,
deleteEntity,
} from "../../../common/entity/delete_entity";
import type { Helper } from "../helpers/const";
import { isHelperDomain } from "../helpers/const";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import { computeStateName } from "../../../common/entity/compute_state_name";
import {
PROTOCOL_INTEGRATIONS,
Expand Down Expand Up @@ -73,12 +80,15 @@ import type {
} from "../../../data/entity_registry";
import {
computeEntityRegistryName,
removeEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import type { IntegrationManifest } from "../../../data/integration";
import {
fetchIntegrationManifests,
domainToName,
} from "../../../data/integration";
import type { EntitySources } from "../../../data/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
import { domainToName } from "../../../data/integration";
import type { LabelRegistryEntry } from "../../../data/label_registry";
import {
createLabelRegistryEntry,
Expand Down Expand Up @@ -136,6 +146,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {

@state() private _entries?: ConfigEntry[];

@state() private _manifests?: IntegrationManifest[];

@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entities!: EntityRegistryEntry[];
Expand Down Expand Up @@ -1280,11 +1292,46 @@ ${rejected
});
}

private _removeSelected() {
const removeableEntities = this._selected.filter((entity) => {
const stateObj = this.hass.states[entity];
return stateObj?.attributes.restored;
private async _removeSelected() {
if (!this._entities || !this.hass) {
return;
}

const manifestsProm = this._manifests
? undefined
: fetchIntegrationManifests(this.hass);
const helperDomains = [
...new Set(this._selected.map((s) => computeDomain(s))),
].filter((d) => isHelperDomain(d));

const configEntriesProm = this._entries
? undefined
: this._loadConfigEntries();
const domainProms = helperDomains.map((d) =>
HELPERS_CRUD[d].fetch(this.hass)
);
const helpersResult = await Promise.all(domainProms);
let fetchedHelpers: Helper[] = [];
helpersResult.forEach((r) => {
fetchedHelpers = fetchedHelpers.concat(r);
});
if (manifestsProm) {
this._manifests = await manifestsProm;
}
if (configEntriesProm) {
await configEntriesProm;
}

const removeableEntities = this._selected.filter((entity_id) =>
isDeletableEntity(
this.hass,
entity_id,
this._manifests!,
this._entities,
this._entries!,
fetchedHelpers
)
);
showConfirmationDialog(this, {
title: this.hass.localize(
`ui.panel.config.entities.picker.delete_selected.confirm_title`
Expand All @@ -1305,8 +1352,15 @@ ${rejected
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
confirm: () => {
removeableEntities.forEach((entity) =>
removeEntityRegistryEntry(this.hass, entity)
removeableEntities.forEach((entity_id) =>
deleteEntity(
this.hass,
entity_id,
this._manifests!,
this._entities,
this._entries!,
fetchedHelpers
)
);
this._clearSelection();
},
Expand Down

0 comments on commit 28703b3

Please sign in to comment.