mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-08 08:57:47 +00:00
Split legacy state filter and condition logic
This commit is contained in:
parent
e20bf741a4
commit
f1ef33c1a7
@ -1,15 +1,16 @@
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { evaluateStateFilter } from "../common/evaluate-filter";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import {
|
||||
addEntityToCondition,
|
||||
checkConditionsMet,
|
||||
} from "../common/validate-condition";
|
||||
import { createBadgeElement } from "../create-element/create-badge-element";
|
||||
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
import { LovelaceBadge } from "../types";
|
||||
import { EntityFilterBadgeConfig } from "./types";
|
||||
import {
|
||||
buildConditionForFilter,
|
||||
checkConditionsMet,
|
||||
} from "../common/validate-condition";
|
||||
|
||||
@customElement("hui-entity-filter-badge")
|
||||
export class HuiEntityFilterBadge
|
||||
@ -86,20 +87,23 @@ export class HuiEntityFilterBadge
|
||||
}
|
||||
|
||||
const entitiesList = this._configEntities.filter((entityConf) => {
|
||||
const conditions =
|
||||
entityConf.conditions ??
|
||||
this._config!.conditions ??
|
||||
entityConf.state_filter ??
|
||||
this._config!.state_filter;
|
||||
const stateObj = this.hass.states[entityConf.entity];
|
||||
if (!stateObj) return false;
|
||||
|
||||
return (
|
||||
conditions
|
||||
.map((condition) =>
|
||||
buildConditionForFilter(condition, entityConf.entity)
|
||||
)
|
||||
.filter((condition) => checkConditionsMet([condition], this.hass!))
|
||||
.length > 0
|
||||
);
|
||||
const conditions = entityConf.conditions ?? this._config!.conditions;
|
||||
if (conditions) {
|
||||
const conditionWithEntity = conditions.map((condition) =>
|
||||
addEntityToCondition(condition, entityConf.entity)
|
||||
);
|
||||
return checkConditionsMet(conditionWithEntity, this.hass!);
|
||||
}
|
||||
|
||||
const filters = entityConf.state_filter ?? this._config!.state_filter;
|
||||
if (filters) {
|
||||
return filters.some((filter) => evaluateStateFilter(stateObj, filter));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (entitiesList.length === 0) {
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import { Condition, LegacyFilterCondition } from "../common/validate-condition";
|
||||
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
import type { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { LegacyStateFilter } from "../common/evaluate-filter";
|
||||
import type { Condition } from "../common/validate-condition";
|
||||
import type { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
|
||||
export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig {
|
||||
type: "entity-filter";
|
||||
entities: Array<EntityFilterEntityConfig | string>;
|
||||
state_filter?: Array<Condition | LegacyFilterCondition | string | number>;
|
||||
conditions: Array<Condition | LegacyFilterCondition | string | number>;
|
||||
state_filter?: Array<LegacyStateFilter>;
|
||||
conditions?: Array<Condition>;
|
||||
}
|
||||
|
||||
export interface ErrorBadgeConfig extends LovelaceBadgeConfig {
|
||||
|
@ -3,16 +3,17 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { evaluateStateFilter } from "../common/evaluate-filter";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import {
|
||||
addEntityToCondition,
|
||||
checkConditionsMet,
|
||||
} from "../common/validate-condition";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EntityFilterCardConfig } from "./types";
|
||||
import {
|
||||
buildConditionForFilter,
|
||||
checkConditionsMet,
|
||||
} from "../common/validate-condition";
|
||||
|
||||
@customElement("hui-entity-filter-card")
|
||||
export class HuiEntityFilterCard
|
||||
@ -141,20 +142,23 @@ export class HuiEntityFilterCard
|
||||
}
|
||||
|
||||
const entitiesList = this._configEntities.filter((entityConf) => {
|
||||
const conditions =
|
||||
entityConf.conditions ??
|
||||
this._config!.conditions ??
|
||||
entityConf.state_filter ??
|
||||
this._config!.state_filter;
|
||||
const stateObj = this.hass!.states[entityConf.entity];
|
||||
if (!stateObj) return false;
|
||||
|
||||
return (
|
||||
conditions
|
||||
.map((condition) =>
|
||||
buildConditionForFilter(condition, entityConf.entity)
|
||||
)
|
||||
.filter((condition) => checkConditionsMet([condition], this.hass!))
|
||||
.length > 0
|
||||
);
|
||||
const conditions = entityConf.conditions ?? this._config!.conditions;
|
||||
if (conditions) {
|
||||
const conditionWithEntity = conditions.map((condition) =>
|
||||
addEntityToCondition(condition, entityConf.entity)
|
||||
);
|
||||
return checkConditionsMet(conditionWithEntity, this.hass!);
|
||||
}
|
||||
|
||||
const filters = entityConf.state_filter ?? this._config!.state_filter;
|
||||
if (filters) {
|
||||
return filters.some((filter) => evaluateStateFilter(stateObj, filter));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (entitiesList.length === 0 && this._config.show_empty === false) {
|
||||
|
92
src/panels/lovelace/common/evaluate-filter.ts
Normal file
92
src/panels/lovelace/common/evaluate-filter.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
type FilterOperator =
|
||||
| "=="
|
||||
| "<="
|
||||
| "<"
|
||||
| ">="
|
||||
| ">"
|
||||
| "!="
|
||||
| "in"
|
||||
| "not in"
|
||||
| "regex";
|
||||
|
||||
// Legacy entity-filter badge & card condition
|
||||
export type LegacyStateFilter =
|
||||
| {
|
||||
operator: FilterOperator;
|
||||
attribute?: string;
|
||||
value: string | number | (string | number)[];
|
||||
}
|
||||
| number
|
||||
| string;
|
||||
|
||||
export const evaluateStateFilter = (
|
||||
stateObj: HassEntity,
|
||||
filter: LegacyStateFilter
|
||||
): boolean => {
|
||||
let operator: FilterOperator;
|
||||
let value: string | number | (string | number)[];
|
||||
let state: any;
|
||||
|
||||
if (typeof filter === "object") {
|
||||
operator = filter.operator;
|
||||
value = filter.value;
|
||||
state = filter.attribute
|
||||
? stateObj.attributes[filter.attribute]
|
||||
: stateObj.state;
|
||||
} else {
|
||||
operator = "==";
|
||||
value = filter;
|
||||
state = stateObj.state;
|
||||
}
|
||||
|
||||
if (operator === "==" || operator === "!=") {
|
||||
const valueIsNumeric =
|
||||
typeof value === "number" ||
|
||||
(typeof value === "string" && value.trim() && !isNaN(Number(value)));
|
||||
const stateIsNumeric =
|
||||
typeof state === "number" ||
|
||||
(typeof state === "string" && state.trim() && !isNaN(Number(state)));
|
||||
if (valueIsNumeric && stateIsNumeric) {
|
||||
value = Number(value);
|
||||
state = Number(state);
|
||||
}
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case "==":
|
||||
return state === value;
|
||||
case "<=":
|
||||
return state <= value;
|
||||
case "<":
|
||||
return state < value;
|
||||
case ">=":
|
||||
return state >= value;
|
||||
case ">":
|
||||
return state > value;
|
||||
case "!=":
|
||||
return state !== value;
|
||||
case "in":
|
||||
if (Array.isArray(value) || typeof value === "string") {
|
||||
return value.includes(state);
|
||||
}
|
||||
return false;
|
||||
case "not in":
|
||||
if (Array.isArray(value) || typeof value === "string") {
|
||||
return !value.includes(state);
|
||||
}
|
||||
return false;
|
||||
case "regex": {
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
if (state !== null && typeof state === "object") {
|
||||
return RegExp(value).test(JSON.stringify(state));
|
||||
}
|
||||
return RegExp(value).test(state);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
@ -1,8 +1,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
|
||||
export type Condition =
|
||||
| NumericStateCondition
|
||||
@ -19,25 +18,6 @@ export interface LegacyCondition {
|
||||
state_not?: string | string[];
|
||||
}
|
||||
|
||||
type FilterOperator =
|
||||
| "=="
|
||||
| "<="
|
||||
| "<"
|
||||
| ">="
|
||||
| ">"
|
||||
| "!="
|
||||
| "in"
|
||||
| "not in"
|
||||
| "regex";
|
||||
|
||||
// Legacy entity-filter badge & card condition
|
||||
export interface LegacyFilterCondition {
|
||||
operator: FilterOperator;
|
||||
entity?: string | number;
|
||||
attribute?: string;
|
||||
value: string | number | string[];
|
||||
}
|
||||
|
||||
interface BaseCondition {
|
||||
condition: string;
|
||||
}
|
||||
@ -92,72 +72,6 @@ function getValueFromEntityId(
|
||||
return value;
|
||||
}
|
||||
|
||||
function checkLegacyFilterCondition(
|
||||
condition: LegacyFilterCondition,
|
||||
hass: HomeAssistant
|
||||
) {
|
||||
const entity: HassEntity = hass.states[condition.entity!];
|
||||
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let value = condition.value;
|
||||
let state: string | number = condition.attribute
|
||||
? entity.attributes[condition.attribute]
|
||||
: entity.state;
|
||||
|
||||
if (Array.isArray(value) || typeof value === "string") {
|
||||
value = getValueFromEntityId(hass, value);
|
||||
}
|
||||
|
||||
if (condition.operator === "==" || condition.operator === "!=") {
|
||||
const valueIsNumeric =
|
||||
typeof value === "number" ||
|
||||
(typeof value === "string" && !isNaN(Number(value.trim())));
|
||||
const stateIsNumeric =
|
||||
typeof state === "number" ||
|
||||
(typeof state === "string" && !isNaN(Number(state.trim())));
|
||||
if (valueIsNumeric && stateIsNumeric) {
|
||||
value = Number(value);
|
||||
state = Number(state);
|
||||
}
|
||||
}
|
||||
|
||||
switch (condition.operator) {
|
||||
case "==":
|
||||
return state === value;
|
||||
case "<=":
|
||||
return state <= value;
|
||||
case "<":
|
||||
return state < value;
|
||||
case ">=":
|
||||
return state >= value;
|
||||
case ">":
|
||||
return state > value;
|
||||
case "!=":
|
||||
return state !== value;
|
||||
case "in":
|
||||
if (Array.isArray(value) || typeof value === "string") {
|
||||
return value.includes(`${state}`);
|
||||
}
|
||||
return false;
|
||||
case "not in":
|
||||
if (Array.isArray(value) || typeof value === "string") {
|
||||
return !value.includes(`${state}`);
|
||||
}
|
||||
return false;
|
||||
case "regex": {
|
||||
if (state !== null && typeof state === "object") {
|
||||
return RegExp(`${value}`).test(JSON.stringify(state));
|
||||
}
|
||||
return RegExp(`${value}`).test(`${state}`);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function checkStateCondition(
|
||||
condition: StateCondition | LegacyCondition,
|
||||
hass: HomeAssistant
|
||||
@ -235,40 +149,6 @@ function checkOrCondition(condition: OrCondition, hass: HomeAssistant) {
|
||||
return condition.conditions.some((c) => checkConditionsMet([c], hass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a condition for filters
|
||||
* @param condition condition to apply
|
||||
* @param entityId base the condition on that entity (current entity to filter)
|
||||
* @returns a new condition that handled legacy filter conditions
|
||||
*/
|
||||
export function buildConditionForFilter(
|
||||
condition: Condition | LegacyFilterCondition | string | number,
|
||||
entityId: string
|
||||
): Condition | LegacyFilterCondition {
|
||||
let newCondition: Condition | LegacyFilterCondition;
|
||||
|
||||
if (typeof condition === "string" || typeof condition === "number") {
|
||||
newCondition = {
|
||||
condition: "state",
|
||||
state: `${condition}`,
|
||||
};
|
||||
} else {
|
||||
newCondition = condition;
|
||||
}
|
||||
|
||||
// Set the entity to filter on
|
||||
if (
|
||||
("condition" in newCondition &&
|
||||
(newCondition.condition === "numeric_state" ||
|
||||
newCondition.condition === "state")) ||
|
||||
"operator" in newCondition
|
||||
) {
|
||||
newCondition.entity = entityId;
|
||||
}
|
||||
|
||||
return newCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of applying conditions
|
||||
* @param conditions conditions to apply
|
||||
@ -276,7 +156,7 @@ export function buildConditionForFilter(
|
||||
* @returns true if conditions are respected
|
||||
*/
|
||||
export function checkConditionsMet(
|
||||
conditions: (Condition | LegacyCondition | LegacyFilterCondition)[],
|
||||
conditions: (Condition | LegacyCondition)[],
|
||||
hass: HomeAssistant
|
||||
): boolean {
|
||||
return conditions.every((c) => {
|
||||
@ -295,8 +175,6 @@ export function checkConditionsMet(
|
||||
default:
|
||||
return checkStateCondition(c, hass);
|
||||
}
|
||||
} else if ("operator" in c) {
|
||||
return checkLegacyFilterCondition(c, hass);
|
||||
}
|
||||
return checkStateCondition(c, hass);
|
||||
});
|
||||
@ -359,3 +237,34 @@ export function validateConditionalConfig(
|
||||
return validateStateCondition(c);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a condition for filters
|
||||
* @param condition condition to apply
|
||||
* @param entityId base the condition on that entity
|
||||
* @returns a new condition with entity id
|
||||
*/
|
||||
export function addEntityToCondition(
|
||||
condition: Condition,
|
||||
entityId: string
|
||||
): Condition {
|
||||
if ("conditions" in condition && condition.conditions) {
|
||||
return {
|
||||
...condition,
|
||||
conditions: condition.conditions.map((c) =>
|
||||
addEntityToCondition(c, entityId)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
condition.condition === "state" ||
|
||||
condition.condition === "numeric_state"
|
||||
) {
|
||||
return {
|
||||
...condition,
|
||||
entity: entityId,
|
||||
};
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { Condition, LegacyFilterCondition } from "../common/validate-condition";
|
||||
import { TimestampRenderingFormat } from "../components/types";
|
||||
import type { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LegacyStateFilter } from "../common/evaluate-filter";
|
||||
import type { Condition } from "../common/validate-condition";
|
||||
import type { TimestampRenderingFormat } from "../components/types";
|
||||
|
||||
export interface EntityConfig {
|
||||
entity: string;
|
||||
@ -14,8 +15,8 @@ export interface ActionRowConfig extends EntityConfig {
|
||||
action_name?: string;
|
||||
}
|
||||
export interface EntityFilterEntityConfig extends EntityConfig {
|
||||
state_filter?: Array<Condition | LegacyFilterCondition | string | number>;
|
||||
conditions?: Array<Condition | LegacyFilterCondition | string | number>;
|
||||
state_filter?: Array<LegacyStateFilter>;
|
||||
conditions?: Array<Condition>;
|
||||
}
|
||||
export interface DividerConfig {
|
||||
type: "divider";
|
||||
|
Loading…
x
Reference in New Issue
Block a user