mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Multiple entities on history panel bugfix and additional improvements (#13045)
Co-authored-by: Zack Barett <zackbarett@hey.com>
This commit is contained in:
parent
8fd5f53f96
commit
6cd38472cd
@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
|
||||
line-height: 60px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: var(--history-max-height);
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "./ha-area-picker";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@ -119,15 +119,26 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class=${this.horizontal ? "horizontal-container" : ""}>
|
||||
${this.horizontal ? this._renderChips() : this._renderItems()}
|
||||
${this._renderPicker()}
|
||||
${this.horizontal ? this._renderItems() : this._renderChips()}
|
||||
</div>`;
|
||||
return html`
|
||||
${this.horizontal
|
||||
? html`
|
||||
<div class="horizontal-container">
|
||||
${this._renderChips()} ${this._renderPicker()}
|
||||
</div>
|
||||
${this._renderItems()}
|
||||
`
|
||||
: html`
|
||||
<div>
|
||||
${this._renderItems()} ${this._renderPicker()}
|
||||
${this._renderChips()}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItems() {
|
||||
return html`<div class="mdc-chip-set items">
|
||||
return html`
|
||||
<div class="mdc-chip-set items">
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
@ -163,11 +174,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>`;
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderChips() {
|
||||
return html`<div class="mdc-chip-set">
|
||||
return html`
|
||||
<div class="mdc-chip-set">
|
||||
<div
|
||||
class="mdc-chip area_id add"
|
||||
.type=${"area_id"}
|
||||
@ -231,7 +244,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""} `;
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showPicker(ev) {
|
||||
@ -320,7 +334,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
private _renderPicker() {
|
||||
switch (this._addMode) {
|
||||
case "area_id":
|
||||
return html`<ha-area-picker
|
||||
return html`
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"area_id"}
|
||||
@ -332,11 +347,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-area-picker>`;
|
||||
></ha-area-picker>
|
||||
`;
|
||||
case "device_id":
|
||||
return html`<ha-device-picker
|
||||
return html`
|
||||
<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"device_id"}
|
||||
@ -347,11 +363,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-device-picker>`;
|
||||
></ha-device-picker>
|
||||
`;
|
||||
case "entity_id":
|
||||
return html`<ha-entity-picker
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"entity_id"}
|
||||
@ -361,10 +378,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
></ha-entity-picker>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
@ -553,12 +570,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.hidden-picker {
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
.horizontal-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import { mdiCollapseAll, mdiRefresh } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
@ -34,6 +34,10 @@ import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
@ -57,9 +61,23 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _ranges?: DateRangePickerRanges;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
@state() private _devices?: { [deviceId: string]: DeviceRegistryEntry };
|
||||
|
||||
@state() private _stateEntities?: EntityRegistryEntry[];
|
||||
@state() private _entities?: { [entityId: string]: EntityRegistryEntry };
|
||||
|
||||
@state() private _stateEntities?: { [entityId: string]: EntityRegistryEntry };
|
||||
|
||||
@state() private _deviceIdToEntities?: {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
};
|
||||
|
||||
@state() private _areaIdToEntities?: {
|
||||
[areaId: string]: EntityRegistryEntry[];
|
||||
};
|
||||
|
||||
@state() private _areaIdToDevices?: {
|
||||
[areaId: string]: DeviceRegistryEntry[];
|
||||
};
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@ -76,7 +94,52 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
this._entities = entities.reduce((accumulator, current) => {
|
||||
accumulator[current.entity_id] = current;
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._deviceIdToEntities = entities.reduce((accumulator, current) => {
|
||||
if (!current.device_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.device_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.device_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._areaIdToEntities = entities.reduce((accumulator, current) => {
|
||||
if (!current.area_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.area_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.area_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices.reduce((accumulator, current) => {
|
||||
accumulator[current.id] = current;
|
||||
return accumulator;
|
||||
}, {});
|
||||
this._areaIdToDevices = devices.reduce((accumulator, current) => {
|
||||
if (!current.area_id) {
|
||||
return accumulator;
|
||||
}
|
||||
let found = accumulator[current.area_id];
|
||||
if (found === undefined) {
|
||||
found = [];
|
||||
accumulator[current.area_id] = found;
|
||||
}
|
||||
found.push(current);
|
||||
return accumulator;
|
||||
}, {});
|
||||
}),
|
||||
];
|
||||
}
|
||||
@ -91,6 +154,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div main-title>${this.hass.localize("panel.history")}</div>
|
||||
<ha-icon-button
|
||||
@click=${this._removeAll}
|
||||
.disabled=${this._isLoading}
|
||||
.path=${mdiCollapseAll}
|
||||
.label=${this.hass.localize("ui.panel.history.remove_all")}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
@click=${this._refreshHistory}
|
||||
.disabled=${this._isLoading}
|
||||
@ -125,6 +194,10 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: !this._targetPickerValue
|
||||
? html`<div class="start-search">
|
||||
${this.hass.localize("ui.panel.history.start_search")}
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
@ -135,24 +208,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
</state-history-charts>
|
||||
`}
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<div class="progress-wrapper">
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
virtualize
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.endTime=${this._endDate}
|
||||
.narrow=${this.narrow}
|
||||
no-single
|
||||
>
|
||||
</state-history-charts>
|
||||
`}
|
||||
</ha-app-layout>
|
||||
`;
|
||||
}
|
||||
@ -199,8 +254,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
if (
|
||||
changedProps.has("_startDate") ||
|
||||
changedProps.has("_endDate") ||
|
||||
changedProps.has("_targetPickerValue") ||
|
||||
changedProps.has("_entities")
|
||||
changedProps.has("_targetPickerValue")
|
||||
) {
|
||||
this._getHistory();
|
||||
}
|
||||
@ -211,15 +265,13 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
if (this._entities) {
|
||||
const stateEntities: EntityRegistryEntry[] = [];
|
||||
const regEntityIds = new Set(
|
||||
this._entities.map((entity) => entity.entity_id)
|
||||
);
|
||||
const stateEntities: { [entityId: string]: EntityRegistryEntry } = {};
|
||||
const regEntityIds = new Set(Object.keys(this._entities));
|
||||
for (const entityId of Object.keys(this.hass.states)) {
|
||||
if (regEntityIds.has(entityId)) {
|
||||
continue;
|
||||
}
|
||||
stateEntities.push({
|
||||
stateEntities[entityId] = {
|
||||
name: computeStateName(this.hass.states[entityId]),
|
||||
entity_id: entityId,
|
||||
platform: computeDomain(entityId),
|
||||
@ -230,17 +282,27 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
device_id: null,
|
||||
icon: null,
|
||||
entity_category: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
this._stateEntities = stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _removeAll() {
|
||||
this._targetPickerValue = undefined;
|
||||
}
|
||||
|
||||
private _refreshHistory() {
|
||||
this._getHistory();
|
||||
}
|
||||
|
||||
private _shouldShowEntityByLargerSelection(
|
||||
entity: EntityRegistryEntry
|
||||
): boolean {
|
||||
return entity.entity_category === null;
|
||||
}
|
||||
|
||||
private async _getHistory() {
|
||||
this._isLoading = true;
|
||||
const entityIds = this._getEntityIds();
|
||||
@ -261,50 +323,79 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
this._isLoading = false;
|
||||
}
|
||||
|
||||
private _filterEntity(entity: EntityRegistryEntry): boolean {
|
||||
const { area_id, device_id, entity_id } = this._targetPickerValue;
|
||||
if (area_id !== undefined) {
|
||||
if (typeof area_id === "string" && area_id === entity.area_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(area_id) && area_id.includes(entity.area_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (device_id !== undefined) {
|
||||
if (typeof device_id === "string" && device_id === entity.device_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(device_id) && device_id.includes(entity.device_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (entity_id !== undefined) {
|
||||
if (typeof entity_id === "string" && entity_id === entity.entity_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(entity_id) && entity_id.includes(entity.entity_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getEntityIds(): string[] {
|
||||
if (
|
||||
this._targetPickerValue === undefined ||
|
||||
this._entities === undefined ||
|
||||
this._stateEntities === undefined
|
||||
this._stateEntities === undefined ||
|
||||
this._devices === undefined ||
|
||||
this._deviceIdToEntities === undefined ||
|
||||
this._areaIdToEntities === undefined ||
|
||||
this._areaIdToDevices === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const entityIds = this._entities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
const stateEntityIds = this._stateEntities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
return [...entityIds, ...stateEntityIds];
|
||||
const entityIds = new Set<string>();
|
||||
let {
|
||||
area_id: searchingAreaId,
|
||||
device_id: searchingDeviceId,
|
||||
entity_id: searchingEntityId,
|
||||
} = this._targetPickerValue;
|
||||
if (searchingAreaId !== undefined) {
|
||||
if (typeof searchingAreaId === "string") {
|
||||
searchingAreaId = [searchingAreaId];
|
||||
}
|
||||
for (const singleSearchingAreaId of searchingAreaId) {
|
||||
const foundEntities = this._areaIdToEntities[singleSearchingAreaId];
|
||||
if (foundEntities !== undefined) {
|
||||
for (const foundEntity of foundEntities) {
|
||||
if (this._shouldShowEntityByLargerSelection(foundEntity)) {
|
||||
entityIds.add(foundEntity.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
const foundDevices = this._areaIdToDevices[singleSearchingAreaId];
|
||||
if (foundDevices !== undefined) {
|
||||
for (const foundDevice of foundDevices) {
|
||||
const foundDeviceEntities =
|
||||
this._deviceIdToEntities[foundDevice.id];
|
||||
for (const foundDeviceEntity of foundDeviceEntities) {
|
||||
if (
|
||||
(!foundDeviceEntity.area_id ||
|
||||
foundDeviceEntity.area_id === singleSearchingAreaId) &&
|
||||
this._shouldShowEntityByLargerSelection(foundDeviceEntity)
|
||||
) {
|
||||
entityIds.add(foundDeviceEntity.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (searchingDeviceId !== undefined) {
|
||||
if (typeof searchingDeviceId === "string") {
|
||||
searchingDeviceId = [searchingDeviceId];
|
||||
}
|
||||
for (const singleSearchingDeviceId of searchingDeviceId) {
|
||||
const foundEntities = this._deviceIdToEntities[singleSearchingDeviceId];
|
||||
if (foundEntities !== undefined) {
|
||||
for (const foundEntity of foundEntities) {
|
||||
if (this._shouldShowEntityByLargerSelection(foundEntity)) {
|
||||
entityIds.add(foundEntity.entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (searchingEntityId !== undefined) {
|
||||
if (typeof searchingEntityId === "string") {
|
||||
searchingEntityId = [searchingEntityId];
|
||||
}
|
||||
for (const singleSearchingEntityId of searchingEntityId) {
|
||||
entityIds.add(singleSearchingEntityId);
|
||||
}
|
||||
}
|
||||
return [...entityIds];
|
||||
}
|
||||
|
||||
private _dateRangeChanged(ev) {
|
||||
@ -389,7 +480,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: flex-start;
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
@ -429,6 +520,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.start-search {
|
||||
padding-top: 16px;
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -4559,6 +4559,11 @@
|
||||
"energy_sources_table_title": "Sources",
|
||||
"energy_devices_graph_title": "Monitor individual devices"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"start_search": "Start by selecting an area, device or entity above",
|
||||
"add_all": "Add all entities",
|
||||
"remove_all": "Remove all selections"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user