Update entity naming in entities config page (#24966)

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
Paul Bottein 2025-04-10 14:43:08 +02:00 committed by GitHub
parent e3122e8e4d
commit a6c9702ab2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 96 additions and 35 deletions

View File

@ -1,3 +1,4 @@
import memoizeOne from "memoize-one";
import type { DeviceRegistryEntry } from "../../data/device_registry"; import type { DeviceRegistryEntry } from "../../data/device_registry";
import type { import type {
EntityRegistryDisplayEntry, EntityRegistryDisplayEntry,
@ -5,6 +6,7 @@ import type {
} from "../../data/entity_registry"; } from "../../data/entity_registry";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { computeStateName } from "./compute_state_name"; import { computeStateName } from "./compute_state_name";
import { getDuplicates } from "../string/get_duplicates";
export const computeDeviceName = ( export const computeDeviceName = (
device: DeviceRegistryEntry device: DeviceRegistryEntry
@ -36,3 +38,13 @@ export const fallbackDeviceName = (
} }
return undefined; return undefined;
}; };
export const getDuplicatedDeviceNames = memoizeOne(
(devices: HomeAssistant["devices"]): Set<string> => {
const names = Object.values(devices)
.map((device) => computeDeviceName(device))
.filter((name): name is string => name !== undefined);
return getDuplicates(names);
}
);

View File

@ -0,0 +1,14 @@
export function getDuplicates(array: string[]): Set<string> {
const duplicates = new Set<string>();
const seen = new Set<string>();
for (const item of array) {
if (seen.has(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}
return duplicates;
}

View File

@ -497,7 +497,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
: "", : "",
}, },
name: { name: {
title: localize("ui.panel.config.devices.data_table.name"), title: localize("ui.panel.config.devices.data_table.device"),
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,

View File

@ -25,15 +25,18 @@ import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time"; import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage"; import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeAreaName } from "../../../common/entity/compute_area_name";
import { import {
isDeletableEntity, computeDeviceName,
deleteEntity, getDuplicatedDeviceNames,
} from "../../../common/entity/delete_entity"; } from "../../../common/entity/compute_device_name";
import type { Helper } from "../helpers/const"; import { computeDomain } from "../../../common/entity/compute_domain";
import { isHelperDomain } from "../helpers/const"; import { computeEntityEntryName } from "../../../common/entity/compute_entity_name";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import {
deleteEntity,
isDeletableEntity,
} from "../../../common/entity/delete_entity";
import { import {
PROTOCOL_INTEGRATIONS, PROTOCOL_INTEGRATIONS,
protocolIntegrationPicked, protocolIntegrationPicked,
@ -53,7 +56,6 @@ import "../../../components/data-table/ha-data-table-labels";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item"; import "../../../components/ha-check-list-item";
import "../../../components/ha-md-divider";
import "../../../components/ha-filter-devices"; import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-domains"; import "../../../components/ha-filter-domains";
import "../../../components/ha-filter-floor-areas"; import "../../../components/ha-filter-floor-areas";
@ -62,6 +64,7 @@ import "../../../components/ha-filter-labels";
import "../../../components/ha-filter-states"; import "../../../components/ha-filter-states";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-md-divider";
import "../../../components/ha-md-menu-item"; import "../../../components/ha-md-menu-item";
import "../../../components/ha-sub-menu"; import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -78,17 +81,15 @@ import type {
EntityRegistryEntry, EntityRegistryEntry,
UpdateEntityRegistryEntryResult, UpdateEntityRegistryEntryResult,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { import { updateEntityRegistryEntry } from "../../../data/entity_registry";
computeEntityRegistryName,
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 type { EntitySources } from "../../../data/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources"; import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import type { IntegrationManifest } from "../../../data/integration";
import {
domainToName,
fetchIntegrationManifests,
} from "../../../data/integration";
import type { LabelRegistryEntry } from "../../../data/label_registry"; import type { LabelRegistryEntry } from "../../../data/label_registry";
import { import {
createLabelRegistryEntry, createLabelRegistryEntry,
@ -106,6 +107,8 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types"; import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import type { Helper } from "../helpers/const";
import { isHelperDomain } from "../helpers/const";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
@ -124,6 +127,8 @@ export interface EntityRow extends StateEntity {
restored: boolean; restored: boolean;
status: string | undefined; status: string | undefined;
area?: string; area?: string;
device?: string;
device_full?: string;
localized_platform: string; localized_platform: string;
domain: string; domain: string;
label_entries: LabelRegistryEntry[]; label_entries: LabelRegistryEntry[];
@ -304,11 +309,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
}, },
name: { name: {
main: true, main: true,
title: localize("ui.panel.config.entities.picker.headers.name"), title: localize("ui.panel.config.entities.picker.headers.entity"),
sortable: true, sortable: true,
filterable: true, filterable: true,
direction: "asc", direction: "asc",
flex: 2,
extraTemplate: (entry) => extraTemplate: (entry) =>
entry.label_entries.length entry.label_entries.length
? html` ? html`
@ -318,10 +322,29 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
` `
: nothing, : nothing,
}, },
device: {
title: localize("ui.panel.config.entities.picker.headers.device"),
sortable: true,
template: (entry) => entry.device || "—",
},
device_full: {
title: localize("ui.panel.config.entities.picker.headers.device"),
filterable: true,
groupable: true,
hidden: true,
},
area: {
title: localize("ui.panel.config.entities.picker.headers.area"),
sortable: true,
filterable: true,
groupable: true,
template: (entry) => entry.area || "—",
},
entity_id: { entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"), title: localize("ui.panel.config.entities.picker.headers.entity_id"),
sortable: true, sortable: true,
filterable: true, filterable: true,
defaultHidden: true,
}, },
localized_platform: { localized_platform: {
title: localize("ui.panel.config.entities.picker.headers.integration"), title: localize("ui.panel.config.entities.picker.headers.integration"),
@ -336,12 +359,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
groupable: true, groupable: true,
}, },
area: {
title: localize("ui.panel.config.entities.picker.headers.area"),
sortable: true,
filterable: true,
groupable: true,
},
disabled_by: { disabled_by: {
title: localize("ui.panel.config.entities.picker.headers.disabled_by"), title: localize("ui.panel.config.entities.picker.headers.disabled_by"),
hidden: true, hidden: true,
@ -620,12 +637,16 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
}); });
const duplicatedDevicesNames = getDuplicatedDeviceNames(devices);
for (const entry of filteredEntities) { for (const entry of filteredEntities) {
const entity = this.hass.states[entry.entity_id]; const entity = this.hass.states[entry.entity_id];
const unavailable = entity?.state === UNAVAILABLE; const unavailable = entity?.state === UNAVAILABLE;
const restored = entity?.attributes.restored === true; const restored = entity?.attributes.restored === true;
const areaId = entry.area_id ?? devices[entry.device_id!]?.area_id; const deviceId = entry.device_id;
const areaId = entry.area_id || devices[deviceId!]?.area_id;
const area = areaId ? areas[areaId] : undefined; const area = areaId ? areas[areaId] : undefined;
const device = deviceId ? devices[deviceId] : undefined;
const hidden = !!entry.hidden_by; const hidden = !!entry.hidden_by;
const disabled = !!entry.disabled_by; const disabled = !!entry.disabled_by;
const readonly = entry.readonly; const readonly = entry.readonly;
@ -651,17 +672,30 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
(lbl) => labelReg!.find((label) => label.label_id === lbl)! (lbl) => labelReg!.find((label) => label.label_id === lbl)!
); );
const entityName = computeEntityEntryName(
entry as EntityRegistryEntry,
this.hass
);
const deviceName = device ? computeDeviceName(device) : undefined;
const areaName = area ? computeAreaName(area) : undefined;
const deviceFullName = deviceName
? duplicatedDevicesNames.has(deviceName) && areaName
? `${deviceName} (${areaName})`
: deviceName
: undefined;
result.push({ result.push({
...entry, ...entry,
entity, entity,
name: computeEntityRegistryName( name: entityName || deviceName || entry.entity_id,
this.hass!, device: deviceName,
entry as EntityRegistryEntry area: areaName,
), device_full: deviceFullName,
unavailable, unavailable,
restored, restored,
localized_platform: domainToName(localize, entry.platform), localized_platform: domainToName(localize, entry.platform),
area: area ? area.name : "—",
domain: domainToName(localize, computeDomain(entry.entity_id)), domain: domainToName(localize, computeDomain(entry.entity_id)),
status: restored status: restored
? localize("ui.panel.config.entities.picker.status.not_provided") ? localize("ui.panel.config.entities.picker.status.not_provided")
@ -792,7 +826,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
selectable selectable
.selected=${this._selected.length} .selected=${this._selected.length}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping ?? "device_full"}
.initialCollapsedGroups=${this._activeCollapsed} .initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting} .initialSorting=${this._activeSorting}
.columnOrder=${this._activeColumnOrder} .columnOrder=${this._activeColumnOrder}

View File

@ -4970,7 +4970,7 @@
"disabled": "Disabled", "disabled": "Disabled",
"data_table": { "data_table": {
"icon": "Icon", "icon": "Icon",
"name": "Name", "device": "Device",
"manufacturer": "Manufacturer", "manufacturer": "Manufacturer",
"model": "Model", "model": "Model",
"area": "Area", "area": "Area",
@ -5017,8 +5017,9 @@
}, },
"headers": { "headers": {
"state_icon": "State icon", "state_icon": "State icon",
"name": "Name", "entity": "Entity",
"entity_id": "Entity ID", "entity_id": "Entity ID",
"device": "Device",
"integration": "Integration", "integration": "Integration",
"area": "Area", "area": "Area",
"disabled_by": "Disabled by", "disabled_by": "Disabled by",