Pass device ID to logbook if available (#12728)

This commit is contained in:
Paulus Schoutsen 2022-05-19 21:09:33 -07:00 committed by GitHub
parent 1f105b6c15
commit bfeb90780f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 291 additions and 225 deletions

View File

@ -56,17 +56,28 @@ export const getLogbookData = async (
hass: HomeAssistant, hass: HomeAssistant,
startDate: string, startDate: string,
endDate: string, endDate: string,
entityId?: string entityIds?: string[],
deviceIds?: string[]
): Promise<LogbookEntry[]> => { ): Promise<LogbookEntry[]> => {
const localize = await hass.loadBackendTranslation("device_class"); const localize = await hass.loadBackendTranslation("device_class");
return addLogbookMessage( return addLogbookMessage(
hass, hass,
localize, localize,
await getLogbookDataCache(hass, startDate, endDate, entityId) // bypass cache if we have a device ID
deviceIds?.length
? await getLogbookDataFromServer(
hass,
startDate,
endDate,
entityIds,
undefined,
deviceIds
)
: await getLogbookDataCache(hass, startDate, endDate, entityIds)
); );
}; };
export const addLogbookMessage = ( const addLogbookMessage = (
hass: HomeAssistant, hass: HomeAssistant,
localize: LocalizeFunc, localize: LocalizeFunc,
logbookData: LogbookEntry[] logbookData: LogbookEntry[]
@ -86,60 +97,73 @@ export const addLogbookMessage = (
return logbookData; return logbookData;
}; };
export const getLogbookDataCache = async ( const getLogbookDataCache = async (
hass: HomeAssistant, hass: HomeAssistant,
startDate: string, startDate: string,
endDate: string, endDate: string,
entityId?: string entityId?: string[]
) => { ) => {
const ALL_ENTITIES = "*"; const ALL_ENTITIES = "*";
if (!entityId) { const entityIdKey = entityId ? entityId.toString() : ALL_ENTITIES;
entityId = ALL_ENTITIES;
}
const cacheKey = `${startDate}${endDate}`; const cacheKey = `${startDate}${endDate}`;
if (!DATA_CACHE[cacheKey]) { if (!DATA_CACHE[cacheKey]) {
DATA_CACHE[cacheKey] = {}; DATA_CACHE[cacheKey] = {};
} }
if (entityId in DATA_CACHE[cacheKey]) { if (entityIdKey in DATA_CACHE[cacheKey]) {
return DATA_CACHE[cacheKey][entityId]; return DATA_CACHE[cacheKey][entityIdKey];
} }
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) { if (entityId && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
const entities = await DATA_CACHE[cacheKey][ALL_ENTITIES]; const entities = await DATA_CACHE[cacheKey][ALL_ENTITIES];
return entities.filter((entity) => entity.entity_id === entityId); return entities.filter(
(entity) => entity.entity_id && entityId.includes(entity.entity_id)
);
} }
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer( DATA_CACHE[cacheKey][entityIdKey] = getLogbookDataFromServer(
hass, hass,
startDate, startDate,
endDate, endDate,
entityId !== ALL_ENTITIES ? entityId : undefined entityId
).then((entries) => entries.reverse()); );
return DATA_CACHE[cacheKey][entityId]; return DATA_CACHE[cacheKey][entityIdKey];
}; };
export const getLogbookDataFromServer = ( const getLogbookDataFromServer = (
hass: HomeAssistant, hass: HomeAssistant,
startDate: string, startDate: string,
endDate?: string, endDate?: string,
entityId?: string, entityIds?: string[],
contextId?: string contextId?: string,
) => { deviceIds?: string[]
let params: any = { ): Promise<LogbookEntry[]> => {
// If all specified filters are empty lists, we can return an empty list.
if (
(entityIds || deviceIds) &&
(!entityIds || entityIds.length === 0) &&
(!deviceIds || deviceIds.length === 0)
) {
return Promise.resolve([]);
}
const params: any = {
type: "logbook/get_events", type: "logbook/get_events",
start_time: startDate, start_time: startDate,
}; };
if (endDate) { if (endDate) {
params = { ...params, end_time: endDate }; params.end_time = endDate;
} }
if (entityId) { if (entityIds?.length) {
params = { ...params, entity_ids: entityId.split(",") }; params.entity_ids = entityIds;
} else if (contextId) { }
params = { ...params, context_id: contextId }; if (deviceIds?.length) {
params.device_ids = deviceIds;
}
if (contextId) {
params.context_id = contextId;
} }
return hass.callWS<LogbookEntry[]>(params); return hass.callWS<LogbookEntry[]>(params);
}; };
@ -148,7 +172,7 @@ export const clearLogbookCache = (startDate: string, endDate: string) => {
DATA_CACHE[`${startDate}${endDate}`] = {}; DATA_CACHE[`${startDate}${endDate}`] = {};
}; };
export const getLogbookMessage = ( const getLogbookMessage = (
hass: HomeAssistant, hass: HomeAssistant,
localize: LocalizeFunc, localize: LocalizeFunc,
state: string, state: string,

View File

@ -1,6 +1,7 @@
import { startOfYesterday } from "date-fns/esm"; import { startOfYesterday } from "date-fns/esm";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import "../../panels/logbook/ha-logbook"; import "../../panels/logbook/ha-logbook";
@ -16,6 +17,8 @@ export class MoreInfoLogbook extends LitElement {
private _time = { recent: 86400 }; private _time = { recent: 86400 };
private _entityIdAsList = memoizeOne((entityId: string) => [entityId]);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!isComponentLoaded(this.hass, "logbook") || !this.entityId) { if (!isComponentLoaded(this.hass, "logbook") || !this.entityId) {
return html``; return html``;
@ -38,7 +41,7 @@ export class MoreInfoLogbook extends LitElement {
<ha-logbook <ha-logbook
.hass=${this.hass} .hass=${this.hass}
.time=${this._time} .time=${this._time}
.entityId=${this.entityId} .entityIds=${this._entityIdAsList(this.entityId)}
narrow narrow
no-icon no-icon
no-name no-name

View File

@ -2,7 +2,10 @@ import "@material/mwc-button";
import { mdiImagePlus, mdiPencil } from "@mdi/js"; import { mdiImagePlus, mdiPencil } from "@mdi/js";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { HassEntity } from "home-assistant-js-websocket/dist/types"; import {
HassEntity,
UnsubscribeFunc,
} from "home-assistant-js-websocket/dist/types";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
@ -20,6 +23,7 @@ import "../../logbook/ha-logbook";
import { import {
AreaRegistryEntry, AreaRegistryEntry,
deleteAreaRegistryEntry, deleteAreaRegistryEntry,
subscribeAreaRegistry,
updateAreaRegistryEntry, updateAreaRegistryEntry,
} from "../../../data/area_registry"; } from "../../../data/area_registry";
import { AutomationEntity } from "../../../data/automation"; import { AutomationEntity } from "../../../data/automation";
@ -27,11 +31,13 @@ import {
computeDeviceName, computeDeviceName,
DeviceRegistryEntry, DeviceRegistryEntry,
sortDeviceRegistryByName, sortDeviceRegistryByName,
subscribeDeviceRegistry,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { import {
computeEntityRegistryName, computeEntityRegistryName,
EntityRegistryEntry, EntityRegistryEntry,
sortEntityRegistryByName, sortEntityRegistryByName,
subscribeEntityRegistry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { SceneEntity } from "../../../data/scene"; import { SceneEntity } from "../../../data/scene";
import { ScriptEntity } from "../../../data/script"; import { ScriptEntity } from "../../../data/script";
@ -45,6 +51,7 @@ import {
loadAreaRegistryDetailDialog, loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog, showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail"; } from "./show-dialog-area-registry-detail";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
declare type NameAndEntity<EntityType extends HassEntity> = { declare type NameAndEntity<EntityType extends HassEntity> = {
name: string; name: string;
@ -52,17 +59,11 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
}; };
@customElement("ha-config-area-page") @customElement("ha-config-area-page")
class HaConfigAreaPage extends LitElement { class HaConfigAreaPage extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public areaId!: string; @property() public areaId!: string;
@property() public areas!: AreaRegistryEntry[];
@property() public devices!: DeviceRegistryEntry[];
@property() public entities!: EntityRegistryEntry[];
@property({ type: Boolean, reflect: true }) public narrow!: boolean; @property({ type: Boolean, reflect: true }) public narrow!: boolean;
@property() public isWide!: boolean; @property() public isWide!: boolean;
@ -71,6 +72,12 @@ class HaConfigAreaPage extends LitElement {
@property() public route!: Route; @property() public route!: Route;
@state() public _areas!: AreaRegistryEntry[];
@state() public _devices!: DeviceRegistryEntry[];
@state() public _entities!: EntityRegistryEntry[];
@state() private _related?: RelatedResult; @state() private _related?: RelatedResult;
private _logbookTime = { recent: 86400 }; private _logbookTime = { recent: 86400 };
@ -89,7 +96,7 @@ class HaConfigAreaPage extends LitElement {
registryDevices: DeviceRegistryEntry[], registryDevices: DeviceRegistryEntry[],
registryEntities: EntityRegistryEntry[] registryEntities: EntityRegistryEntry[]
) => { ) => {
const devices = new Map(); const devices = new Map<string, DeviceRegistryEntry>();
for (const device of registryDevices) { for (const device of registryDevices) {
if (device.area_id === areaId) { if (device.area_id === areaId) {
@ -105,7 +112,7 @@ class HaConfigAreaPage extends LitElement {
if (entity.area_id === areaId) { if (entity.area_id === areaId) {
entities.push(entity); entities.push(entity);
} }
} else if (devices.has(entity.device_id)) { } else if (entity.device_id && devices.has(entity.device_id)) {
indirectEntities.push(entity); indirectEntities.push(entity);
} }
} }
@ -118,6 +125,10 @@ class HaConfigAreaPage extends LitElement {
} }
); );
private _allDeviceIds = memoizeOne((devices: DeviceRegistryEntry[]) =>
devices.map((device) => device.id)
);
private _allEntities = memoizeOne( private _allEntities = memoizeOne(
(memberships: { (memberships: {
entities: EntityRegistryEntry[]; entities: EntityRegistryEntry[];
@ -140,8 +151,26 @@ class HaConfigAreaPage extends LitElement {
} }
} }
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._devices = entries;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entities = entries;
}),
];
}
protected render(): TemplateResult { protected render(): TemplateResult {
const area = this._area(this.areaId, this.areas); if (!this._areas || !this._devices || !this._entities) {
return html``;
}
const area = this._area(this.areaId, this._areas);
if (!area) { if (!area) {
return html` return html`
@ -154,8 +183,8 @@ class HaConfigAreaPage extends LitElement {
const memberships = this._memberships( const memberships = this._memberships(
this.areaId, this.areaId,
this.devices, this._devices,
this.entities this._entities
); );
const { devices, entities } = memberships; const { devices, entities } = memberships;
@ -465,6 +494,7 @@ class HaConfigAreaPage extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.time=${this._logbookTime} .time=${this._logbookTime}
.entityIds=${this._allEntities(memberships)} .entityIds=${this._allEntities(memberships)}
.deviceIds=${this._allDeviceIds(memberships.devices)}
virtualize virtualize
narrow narrow
no-icon no-icon

View File

@ -1,6 +1,7 @@
import { mdiHelpCircle, mdiPlus } from "@mdi/js"; import { mdiHelpCircle, mdiPlus } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
@ -9,12 +10,20 @@ import "../../../components/ha-svg-icon";
import { import {
AreaRegistryEntry, AreaRegistryEntry,
createAreaRegistryEntry, createAreaRegistryEntry,
subscribeAreaRegistry,
} from "../../../data/area_registry"; } from "../../../data/area_registry";
import type { DeviceRegistryEntry } from "../../../data/device_registry"; import {
import type { EntityRegistryEntry } from "../../../data/entity_registry"; DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
@ -24,7 +33,7 @@ import {
} from "./show-dialog-area-registry-detail"; } from "./show-dialog-area-registry-detail";
@customElement("ha-config-areas-dashboard") @customElement("ha-config-areas-dashboard")
export class HaConfigAreasDashboard extends LitElement { export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide?: boolean; @property() public isWide?: boolean;
@ -33,13 +42,13 @@ export class HaConfigAreasDashboard extends LitElement {
@property() public route!: Route; @property() public route!: Route;
@property() public areas!: AreaRegistryEntry[]; @state() private _areas!: AreaRegistryEntry[];
@property() public devices!: DeviceRegistryEntry[]; @state() private _devices!: DeviceRegistryEntry[];
@property() public entities!: EntityRegistryEntry[]; @state() private _entities!: EntityRegistryEntry[];
private _areas = memoizeOne( private _processAreas = memoizeOne(
( (
areas: AreaRegistryEntry[], areas: AreaRegistryEntry[],
devices: DeviceRegistryEntry[], devices: DeviceRegistryEntry[],
@ -75,6 +84,20 @@ export class HaConfigAreasDashboard extends LitElement {
}) })
); );
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._devices = entries;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entities = entries;
}),
];
}
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
@ -92,7 +115,13 @@ export class HaConfigAreasDashboard extends LitElement {
@click=${this._showHelp} @click=${this._showHelp}
></ha-icon-button> ></ha-icon-button>
<div class="container"> <div class="container">
${this._areas(this.areas, this.devices, this.entities).map( ${!this._areas || !this._devices || !this._entities
? ""
: this._processAreas(
this._areas,
this._devices,
this._entities
).map(
(area) => (area) =>
html`<a href=${`/config/areas/area/${area.area_id}`} html`<a href=${`/config/areas/area/${area.area_id}`}
><ha-card outlined> ><ha-card outlined>

View File

@ -1,20 +1,4 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { customElement, property } from "lit/decorators";
import { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stringCompare } from "../../../common/string/compare";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../../data/area_registry";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
@ -46,44 +30,6 @@ class HaConfigAreas extends HassRouterPage {
}, },
}; };
@state() private _configEntries: ConfigEntry[] = [];
@state()
private _deviceRegistryEntries: DeviceRegistryEntry[] = [];
@state()
private _entityRegistryEntries: EntityRegistryEntry[] = [];
@state() private _areas: AreaRegistryEntry[] = [];
private _unsubs?: UnsubscribeFunc[];
public connectedCallback() {
super.connectedCallback();
if (!this.hass) {
return;
}
this._loadData();
}
public disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubs) {
while (this._unsubs.length) {
this._unsubs.pop()!();
}
this._unsubs = undefined;
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this._unsubs && changedProps.has("hass")) {
this._loadData();
}
}
protected updatePageEl(pageEl) { protected updatePageEl(pageEl) {
pageEl.hass = this.hass; pageEl.hass = this.hass;
@ -91,37 +37,11 @@ class HaConfigAreas extends HassRouterPage {
pageEl.areaId = this.routeTail.path.substr(1); pageEl.areaId = this.routeTail.path.substr(1);
} }
pageEl.entries = this._configEntries;
pageEl.devices = this._deviceRegistryEntries;
pageEl.entities = this._entityRegistryEntries;
pageEl.areas = this._areas;
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide; pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced; pageEl.showAdvanced = this.showAdvanced;
pageEl.route = this.routeTail; pageEl.route = this.routeTail;
} }
private _loadData() {
getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries.sort((conf1, conf2) =>
stringCompare(conf1.title, conf2.title)
);
});
if (this._unsubs) {
return;
}
this._unsubs = [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._deviceRegistryEntries = entries;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entityRegistryEntries = entries;
}),
];
}
} }
declare global { declare global {

View File

@ -134,6 +134,8 @@ export class HaConfigDevicePage extends LitElement {
) )
); );
private _deviceIdInList = memoizeOne((deviceId: string) => [deviceId]);
private _entityIds = memoizeOne( private _entityIds = memoizeOne(
(entries: EntityRegistryStateEntry[]): string[] => (entries: EntityRegistryStateEntry[]): string[] =>
entries.map((entry) => entry.entity_id) entries.map((entry) => entry.entity_id)
@ -592,7 +594,8 @@ export class HaConfigDevicePage extends LitElement {
<ha-logbook <ha-logbook
.hass=${this.hass} .hass=${this.hass}
.time=${this._logbookTime} .time=${this._logbookTime}
.entityId=${this._entityIds(entities)} .entityIds=${this._entityIds(entities)}
.deviceIds=${this._deviceIdInList(this.deviceId)}
virtualize virtualize
narrow narrow
no-icon no-icon

View File

@ -21,7 +21,9 @@ export class HaLogbook extends LitElement {
@property() public time!: { range: [Date, Date] } | { recent: number }; @property() public time!: { range: [Date, Date] } | { recent: number };
@property() public entityId?: string | string[]; @property() public entityIds?: string[];
@property() public deviceIds?: string[];
@property({ type: Boolean, attribute: "narrow" }) @property({ type: Boolean, attribute: "narrow" })
public narrow = false; public narrow = false;
@ -120,23 +122,47 @@ export class HaLogbook extends LitElement {
this._lastLogbookDate = undefined; this._lastLogbookDate = undefined;
this._logbookEntries = undefined; this._logbookEntries = undefined;
this._error = undefined;
this._throttleGetLogbookEntries(); this._throttleGetLogbookEntries();
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
super.updated(changedProps); super.updated(changedProps);
if (changedProps.has("time") || changedProps.has("entityId")) { let changed = changedProps.has("time");
for (const key of ["entityIds", "deviceIds"]) {
if (!changedProps.has(key)) {
continue;
}
const oldValue = changedProps.get(key) as string[] | undefined;
const curValue = this[key] as string[] | undefined;
if (
!oldValue ||
!curValue ||
oldValue.length !== curValue.length ||
!oldValue.every((val) => curValue.includes(val))
) {
changed = true;
break;
}
}
if (changed) {
this.refresh(true); this.refresh(true);
return; return;
} }
if (this._filterAlwaysEmptyResults) {
return;
}
// We only need to fetch again if we track recent entries for an entity // We only need to fetch again if we track recent entries for an entity
if ( if (
!("recent" in this.time) || !("recent" in this.time) ||
!changedProps.has("hass") || !changedProps.has("hass") ||
!this.entityId !this.entityIds
) { ) {
return; return;
} }
@ -146,7 +172,7 @@ export class HaLogbook extends LitElement {
// Refresh data if we know the entity has changed. // Refresh data if we know the entity has changed.
if ( if (
!oldHass || !oldHass ||
ensureArray(this.entityId).some( ensureArray(this.entityIds).some(
(entityId) => this.hass.states[entityId] !== oldHass?.states[entityId] (entityId) => this.hass.states[entityId] !== oldHass?.states[entityId]
) )
) { ) {
@ -155,9 +181,34 @@ export class HaLogbook extends LitElement {
} }
} }
private get _filterAlwaysEmptyResults(): boolean {
const entityIds = ensureArray(this.entityIds);
const deviceIds = ensureArray(this.deviceIds);
// If all specified filters are empty lists, we can return an empty list.
return (
(entityIds || deviceIds) &&
(!entityIds || entityIds.length === 0) &&
(!deviceIds || deviceIds.length === 0)
);
}
private async _getLogBookData() { private async _getLogBookData() {
this._renderId += 1; this._renderId += 1;
const renderId = this._renderId; const renderId = this._renderId;
this._error = undefined;
if (this._filterAlwaysEmptyResults) {
this._logbookEntries = [];
this._lastLogbookDate = undefined;
return;
}
this._updateUsers();
if (this.hass.user?.is_admin) {
this._updateTraceContexts();
}
let startTime: Date; let startTime: Date;
let endTime: Date; let endTime: Date;
let appendData = false; let appendData = false;
@ -173,27 +224,15 @@ export class HaLogbook extends LitElement {
endTime = new Date(); endTime = new Date();
} }
const entityIdFilter = this.entityId
? ensureArray(this.entityId)
: undefined;
let newEntries: LogbookEntry[]; let newEntries: LogbookEntry[];
if (entityIdFilter?.length === 0) {
// filtering by 0 entities, means we never can have any results
newEntries = [];
} else {
this._updateUsers();
if (this.hass.user?.is_admin) {
this._updateTraceContexts();
}
try { try {
newEntries = await getLogbookData( newEntries = await getLogbookData(
this.hass, this.hass,
startTime.toISOString(), startTime.toISOString(),
endTime.toISOString(), endTime.toISOString(),
entityIdFilter ? entityIdFilter.toString() : undefined ensureArray(this.entityIds),
ensureArray(this.deviceIds)
); );
} catch (err: any) { } catch (err: any) {
if (renderId === this._renderId) { if (renderId === this._renderId) {
@ -201,13 +240,16 @@ export class HaLogbook extends LitElement {
} }
return; return;
} }
}
// New render happening. // New render happening.
if (renderId !== this._renderId) { if (renderId !== this._renderId) {
return; return;
} }
// Put newest ones on top. Reverse works in-place so
// make a copy first.
newEntries = [...newEntries].reverse();
this._logbookEntries = this._logbookEntries =
appendData && this._logbookEntries appendData && this._logbookEntries
? newEntries.concat(...this._logbookEntries) ? newEntries.concat(...this._logbookEntries)

View File

@ -38,7 +38,7 @@ export class HaPanelLogbook extends LitElement {
@state() _time: { range: [Date, Date] }; @state() _time: { range: [Date, Date] };
@state() _entityId = ""; @state() _entityIds?: string[];
@property({ reflect: true, type: Boolean }) rtl = false; @property({ reflect: true, type: Boolean }) rtl = false;
@ -85,7 +85,7 @@ export class HaPanelLogbook extends LitElement {
<ha-entity-picker <ha-entity-picker
.hass=${this.hass} .hass=${this.hass}
.value=${this._entityId} .value=${this._entityIds ? this._entityIds[0] : undefined}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.components.entity.entity-picker.entity" "ui.components.entity.entity-picker.entity"
)} )}
@ -97,7 +97,7 @@ export class HaPanelLogbook extends LitElement {
<ha-logbook <ha-logbook
.hass=${this.hass} .hass=${this.hass}
.time=${this._time} .time=${this._time}
.entityId=${this._entityId} .entityIds=${this._entityIds}
virtualize virtualize
></ha-logbook> ></ha-logbook>
</ha-app-layout> </ha-app-layout>
@ -157,15 +157,30 @@ export class HaPanelLogbook extends LitElement {
this.rtl = computeRTL(this.hass); this.rtl = computeRTL(this.hass);
} }
} }
this._applyURLParams();
} }
private _applyURLParams() { private _applyURLParams() {
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
if (searchParams.has("entity_id")) { if (searchParams.has("entity_id")) {
this._entityId = searchParams.get("entity_id") ?? ""; const entityIdsRaw = searchParams.get("entity_id");
if (!entityIdsRaw) {
this._entityIds = undefined;
} else {
const entityIds = entityIdsRaw.split(",").sort();
// Check if different
if (
!this._entityIds ||
entityIds.length !== this._entityIds.length ||
this._entityIds.every((val, idx) => val === entityIds[idx])
) {
this._entityIds = entityIds;
}
}
} else {
this._entityIds = undefined;
} }
const startDateStr = searchParams.get("start_date"); const startDateStr = searchParams.get("start_date");
@ -199,19 +214,19 @@ export class HaPanelLogbook extends LitElement {
endDate.setDate(endDate.getDate() + 1); endDate.setDate(endDate.getDate() + 1);
endDate.setMilliseconds(endDate.getMilliseconds() - 1); endDate.setMilliseconds(endDate.getMilliseconds() - 1);
} }
this._time = { range: [startDate, endDate] };
this._updatePath({ this._updatePath({
start_date: this._time.range[0].toISOString(), start_date: startDate.toISOString(),
end_date: this._time.range[1].toISOString(), end_date: endDate.toISOString(),
}); });
} }
private _entityPicked(ev) { private _entityPicked(ev) {
this._entityId = ev.target.value; this._updatePath({
this._updatePath({ entity_id: this._entityId }); entity_id: ev.target.value || undefined,
});
} }
private _updatePath(update: Record<string, string>) { private _updatePath(update: Record<string, string | undefined>) {
const params = extractSearchParamsObject(); const params = extractSearchParamsObject();
for (const [key, value] of Object.entries(update)) { for (const [key, value] of Object.entries(update)) {
if (value === undefined) { if (value === undefined) {

View File

@ -120,7 +120,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
<ha-logbook <ha-logbook
.hass=${this.hass} .hass=${this.hass}
.time=${this._time} .time=${this._time}
.entityId=${this._entityId} .entityIds=${this._entityId}
narrow narrow
relative-time relative-time
virtualize virtualize