Multiple entities on history panel (#9946)

Co-authored-by: Zack Barett <zackbarett@hey.com>
This commit is contained in:
D3v01dZA 2022-06-29 11:39:38 -04:00 committed by GitHub
parent 0f580a91c9
commit cff3f51d34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 234 additions and 86 deletions

View File

@ -79,6 +79,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean, reflect: true }) public disabled = false;
@property({ type: Boolean }) public horizontal = false;
@state() private _areas?: { [areaId: string]: AreaRegistryEntry };
@state() private _devices?: {
@ -117,45 +119,55 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) {
return html``;
}
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];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})
: ""}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})
: ""}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
: ""}
</div>
return html`<div class=${this.horizontal ? "horizontal-container" : ""}>
${this.horizontal ? this._renderChips() : this._renderItems()}
${this._renderPicker()}
<div class="mdc-chip-set">
${this.horizontal ? this._renderItems() : this._renderChips()}
</div>`;
}
private _renderItems() {
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];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})
: ""}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})
: ""}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
: ""}
</div>`;
}
private _renderChips() {
return html`<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
.type=${"area_id"}
@ -217,7 +229,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
</span>
</div>
</div>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""} `;
@ -321,6 +332,7 @@ 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>`;
case "device_id":
@ -335,6 +347,7 @@ 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>`;
case "entity_id":
@ -348,6 +361,7 @@ 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>`;
@ -539,6 +553,16 @@ 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;
}
.mdc-chip {
color: var(--primary-text-color);
}

View File

@ -223,16 +223,12 @@ export const fetchDate = (
hass: HomeAssistant,
startTime: Date,
endTime: Date,
entityId?: string
entityIds: string[]
): Promise<HassEntity[][]> =>
hass.callApi(
"GET",
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response${
entityId ? `&filter_entity_id=${entityId}` : ``
}${
entityId && !entityIdHistoryNeedsAttributes(hass, entityId)
? `&no_attributes`
: ``
entityIds ? `&filter_entity_id=${entityIds.join(",")}` : ``
}`
);
@ -240,19 +236,19 @@ export const fetchDateWS = (
hass: HomeAssistant,
startTime: Date,
endTime: Date,
entityId?: string
entityIds: string[]
) => {
const params = {
type: "history/history_during_period",
start_time: startTime.toISOString(),
end_time: endTime.toISOString(),
minimal_response: true,
no_attributes: !!(
entityId && !entityIdHistoryNeedsAttributes(hass, entityId)
),
no_attributes: !entityIds
.map((entityId) => entityIdHistoryNeedsAttributes(hass, entityId))
.reduce((cur, next) => cur || next, false),
};
if (entityId) {
return hass.callWS<HistoryStates>({ ...params, entity_ids: [entityId] });
if (entityIds.length !== 0) {
return hass.callWS<HistoryStates>({ ...params, entity_ids: entityIds });
}
return hass.callWS<HistoryStates>(params);
};

View File

@ -12,6 +12,7 @@ import {
} from "date-fns/esm";
import { css, html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
import { navigate } from "../../common/navigate";
import {
createSearchParam,
@ -19,7 +20,7 @@ import {
} from "../../common/url/search-params";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/chart/state-history-charts";
import "../../components/entity/ha-entity-picker";
import "../../components/ha-target-picker";
import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
@ -29,8 +30,15 @@ import { computeHistory, fetchDateWS } from "../../data/history";
import "../../layouts/ha-app-layout";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeDomain } from "../../common/entity/compute_domain";
class HaPanelHistory extends LitElement {
class HaPanelHistory extends SubscribeMixin(LitElement) {
@property() hass!: HomeAssistant;
@property({ reflect: true, type: Boolean }) narrow!: boolean;
@ -39,7 +47,7 @@ class HaPanelHistory extends LitElement {
@property() _endDate: Date;
@property() _entityId = "";
@property() _targetPickerValue?;
@property() _isLoading = false;
@ -49,6 +57,10 @@ class HaPanelHistory extends LitElement {
@state() private _ranges?: DateRangePickerRanges;
@state() private _entities?: EntityRegistryEntry[];
@state() private _stateEntities?: EntityRegistryEntry[];
public constructor() {
super();
@ -61,6 +73,14 @@ class HaPanelHistory extends LitElement {
this._endDate = end;
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected render() {
return html`
<ha-app-layout>
@ -80,25 +100,40 @@ class HaPanelHistory extends LitElement {
</app-toolbar>
</app-header>
<div class="filters">
<ha-date-range-picker
.hass=${this.hass}
?disabled=${this._isLoading}
.startDate=${this._startDate}
.endDate=${this._endDate}
.ranges=${this._ranges}
@change=${this._dateRangeChanged}
></ha-date-range-picker>
<ha-entity-picker
.hass=${this.hass}
.value=${this._entityId}
.label=${this.hass.localize(
"ui.components.entity.entity-picker.entity"
)}
.disabled=${this._isLoading}
@change=${this._entityPicked}
></ha-entity-picker>
<div class="flex content">
<div class="filters flex layout horizontal narrow-wrap">
<ha-date-range-picker
.hass=${this.hass}
?disabled=${this._isLoading}
.startDate=${this._startDate}
.endDate=${this._endDate}
.ranges=${this._ranges}
@change=${this._dateRangeChanged}
></ha-date-range-picker>
<ha-target-picker
.hass=${this.hass}
.value=${this._targetPickerValue}
.disabled=${this._isLoading}
horizontal
@value-changed=${this._entitiesChanged}
></ha-target-picker>
</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
.hass=${this.hass}
.historyData=${this._stateHistory}
.endTime=${this._endDate}
no-single
>
</state-history-charts>
`}
</div>
${this._isLoading
? html`<div class="progress-wrapper">
@ -142,7 +177,13 @@ class HaPanelHistory extends LitElement {
[addDays(weekStart, -7), addDays(weekEnd, -7)],
};
this._entityId = extractSearchParam("entity_id") ?? "";
const entityIds = extractSearchParam("entity_id");
if (entityIds) {
const splitEntityIds = entityIds.split(",");
this._targetPickerValue = {
entity_id: splitEntityIds,
};
}
const startDate = extractSearchParam("start_date");
if (startDate) {
@ -158,16 +199,41 @@ class HaPanelHistory extends LitElement {
if (
changedProps.has("_startDate") ||
changedProps.has("_endDate") ||
changedProps.has("_entityId")
changedProps.has("_targetPickerValue") ||
changedProps.has("_entities")
) {
this._getHistory();
}
if (changedProps.has("hass")) {
if (changedProps.has("hass") || changedProps.has("_entities")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
this.rtl = computeRTL(this.hass);
}
if (this._entities) {
const stateEntities: EntityRegistryEntry[] = [];
const regEntityIds = new Set(
this._entities.map((entity) => entity.entity_id)
);
for (const entityId of Object.keys(this.hass.states)) {
if (regEntityIds.has(entityId)) {
continue;
}
stateEntities.push({
name: computeStateName(this.hass.states[entityId]),
entity_id: entityId,
platform: computeDomain(entityId),
disabled_by: null,
hidden_by: null,
area_id: null,
config_entry_id: null,
device_id: null,
icon: null,
entity_category: null,
});
}
this._stateEntities = stateEntities;
}
}
}
@ -177,12 +243,16 @@ class HaPanelHistory extends LitElement {
private async _getHistory() {
this._isLoading = true;
const dateHistory = await fetchDateWS(
this.hass,
this._startDate,
this._endDate,
this._entityId
);
const entityIds = this._getEntityIds();
const dateHistory =
entityIds.length === 0
? {}
: await fetchDateWS(
this.hass,
this._startDate,
this._endDate,
entityIds
);
this._stateHistory = computeHistory(
this.hass,
dateHistory,
@ -191,6 +261,52 @@ class HaPanelHistory extends 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
) {
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];
}
private _dateRangeChanged(ev) {
this._startDate = ev.detail.startDate;
const endDate = ev.detail.endDate;
@ -203,8 +319,8 @@ class HaPanelHistory extends LitElement {
this._updatePath();
}
private _entityPicked(ev) {
this._entityId = ev.target.value;
private _entitiesChanged(ev) {
this._targetPickerValue = ev.detail.value;
this._updatePath();
}
@ -212,8 +328,8 @@ class HaPanelHistory extends LitElement {
private _updatePath() {
const params: Record<string, string> = {};
if (this._entityId) {
params.entity_id = this._entityId;
if (this._targetPickerValue) {
params.entity_id = this._getEntityIds().join(",");
}
if (this._startDate) {
@ -255,6 +371,18 @@ class HaPanelHistory extends LitElement {
height: 100%;
}
:host([narrow]) .narrow-wrap {
flex-wrap: wrap;
}
.horizontal {
align-items: center;
}
:host(:not([narrow])) .selector-padding {
padding-left: 32px;
}
.progress-wrapper {
position: relative;
}