Add label filter for helper page (#20281)

* Label filter for helper page

* Clean up debugging label
This commit is contained in:
Simon Lamon 2024-03-30 13:30:24 +01:00 committed by GitHub
parent 85f80ff863
commit 88c59c5c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,8 +1,17 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiAlertCircle, mdiPencilOff, mdiPlus } from "@mdi/js"; import { mdiAlertCircle, mdiPencilOff, mdiPlus } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { LitElement, PropertyValues, TemplateResult, html } from "lit"; import {
CSSResultGroup,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { consume } from "@lit-labs/context";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
@ -15,6 +24,7 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-state-icon"; import "../../../components/ha-state-icon";
@ -44,6 +54,13 @@ import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { isHelperDomain } from "./const"; import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import { fullEntitiesContext } from "../../../data/context";
import "../../../components/ha-filter-labels";
import { haStyle } from "../../../resources/styles";
type HelperItem = { type HelperItem = {
id: string; id: string;
@ -54,6 +71,7 @@ type HelperItem = {
type: string; type: string;
configEntry?: ConfigEntry; configEntry?: ConfigEntry;
entity?: HassEntity; entity?: HassEntity;
label_entries: LabelRegistryEntry[];
}; };
// This groups items by a key but only returns last entry per key. // This groups items by a key but only returns last entry per key.
@ -93,6 +111,24 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
@state() private _configEntries?: Record<string, ConfigEntry>; @state() private _configEntries?: Record<string, ConfigEntry>;
@state() private _activeFilters?: string[];
@state() private _filters: Record<
string,
{ value: string[] | undefined; items: Set<string> | undefined }
> = {};
@state() private _expandedFilter?: string;
@state()
_labels!: LabelRegistryEntry[];
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state() private _filteredStateItems?: string[] | null;
public hassSubscribe() { public hassSubscribe() {
return [ return [
subscribeConfigEntries( subscribeConfigEntries(
@ -117,6 +153,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
subscribeEntityRegistry(this.hass.connection!, (entries) => { subscribeEntityRegistry(this.hass.connection!, (entries) => {
this._entityEntries = groupByOne(entries, (entry) => entry.entity_id); this._entityEntries = groupByOne(entries, (entry) => entry.entity_id);
}), }),
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
]; ];
} }
@ -146,10 +185,17 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
grows: true, grows: true,
direction: "asc", direction: "asc",
template: (helper) => html` template: (helper) => html`
${helper.name} <div style="font-size: 14px;">${helper.name}</div>
${narrow ${narrow
? html`<div class="secondary">${helper.entity_id}</div> ` ? html`<div class="secondary">${helper.entity_id}</div> `
: ""} : nothing}
${helper.label_entries.length
? html`
<ha-data-table-labels
.labels=${helper.label_entries}
></ha-data-table-labels>
`
: nothing}
`, `,
}, },
}; };
@ -201,8 +247,15 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
localize: LocalizeFunc, localize: LocalizeFunc,
stateItems: HassEntity[], stateItems: HassEntity[],
entityEntries: Record<string, EntityRegistryEntry>, entityEntries: Record<string, EntityRegistryEntry>,
configEntries: Record<string, ConfigEntry> configEntries: Record<string, ConfigEntry>,
entityReg: EntityRegistryEntry[],
labelReg?: LabelRegistryEntry[],
filteredStateItems?: string[] | null
): HelperItem[] => { ): HelperItem[] => {
if (filteredStateItems === null) {
return [];
}
const configEntriesCopy = { ...configEntries }; const configEntriesCopy = { ...configEntries };
const states = stateItems.map((entityState) => { const states = stateItems.map((entityState) => {
@ -241,14 +294,29 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
entity: undefined, entity: undefined,
})); }));
return [...states, ...entries].map((item) => ({ return [...states, ...entries]
...item, .filter((item) =>
localized_type: item.configEntry filteredStateItems
? domainToName(localize, item.type) ? filteredStateItems?.includes(item.entity_id)
: localize( : true
`ui.panel.config.helpers.types.${item.type}` as LocalizeKeys )
) || item.type, .map((item) => {
})); const entityRegEntry = entityReg.find(
(reg) => reg.entity_id === item.entity_id
);
const labels = labelReg && entityRegEntry?.labels;
return {
...item,
localized_type: item.configEntry
? domainToName(localize, item.type)
: localize(
`ui.panel.config.helpers.types.${item.type}` as LocalizeKeys
) || item.type,
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
};
});
} }
); );
@ -269,20 +337,40 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.devices} .tabs=${configSections.devices}
hasFilters
.filters=${Object.values(this._filters).filter(
(filter) => filter.value?.length
).length}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${this._getItems( .data=${this._getItems(
this.hass.localize, this.hass.localize,
this._stateItems, this._stateItems,
this._entityEntries, this._entityEntries,
this._configEntries this._configEntries,
this._entityReg,
this._labels,
this._filteredStateItems
)} )}
.activeFilters=${this._activeFilters}
@clear-filter=${this._clearFilter}
@row-click=${this._openEditDialog} @row-click=${this._openEditDialog}
hasFab hasFab
clickable clickable
.noDataText=${this.hass.localize( .noDataText=${this.hass.localize(
"ui.panel.config.helpers.picker.no_helpers" "ui.panel.config.helpers.picker.no_helpers"
)} )}
class=${this.narrow ? "narrow" : ""}
> >
<ha-filter-labels
.hass=${this.hass}
.value=${this._filters["ha-filter-labels"]?.value}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
.expanded=${this._expandedFilter === "ha-filter-labels"}
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-labels>
<ha-integration-overflow-menu <ha-integration-overflow-menu
.hass=${this.hass} .hass=${this.hass}
slot="toolbar-icon" slot="toolbar-icon"
@ -293,7 +381,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
"ui.panel.config.helpers.picker.create_helper" "ui.panel.config.helpers.picker.create_helper"
)} )}
extended extended
@click=${this._createHelpler} @click=${this._createHelper}
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab> </ha-fab>
@ -301,6 +389,63 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
`; `;
} }
private _filterExpanded(ev) {
if (ev.detail.expanded) {
this._expandedFilter = ev.target.localName;
} else if (this._expandedFilter === ev.target.localName) {
this._expandedFilter = undefined;
}
}
private _filterChanged(ev) {
const type = ev.target.localName;
this._filters[type] = ev.detail;
this._applyFilters();
}
private _applyFilters() {
const filters = Object.entries(this._filters);
let items: Set<string> | undefined;
for (const [key, filter] of filters) {
if (filter.items) {
if (!items) {
items = filter.items;
continue;
}
items =
"intersection" in items
? // @ts-ignore
items.intersection(filter.items)
: new Set([...items].filter((x) => filter.items!.has(x)));
}
if (key === "ha-filter-labels" && filter.value?.length) {
const labelItems: Set<string> = new Set();
this._stateItems
.filter((stateItem) =>
this._entityReg
.find((reg) => reg.entity_id === stateItem.entity_id)
?.labels.some((lbl) => filter.value!.includes(lbl))
)
.forEach((stateItem) => labelItems.add(stateItem.entity_id));
if (!items) {
items = labelItems;
continue;
}
items =
"intersection" in items
? // @ts-ignore
items.intersection(labelItems)
: new Set([...items].filter((x) => labelItems!.has(x)));
}
}
this._filteredStateItems = items ? [...items] : undefined;
}
private _clearFilter() {
this._filters = {};
this._applyFilters();
}
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
if (this.route.path === "/add") { if (this.route.path === "/add") {
@ -418,9 +563,23 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
} }
} }
private _createHelpler() { private _createHelper() {
showHelperDetailDialog(this, {}); showHelperDetailDialog(this, {});
} }
static get styles(): CSSResultGroup {
return [
haStyle,
css`
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}
hass-tabs-subpage-data-table.narrow {
--data-table-row-height: 72px;
}
`,
];
}
} }
declare global { declare global {