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

View File

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