Exclude triggers without description from WS command response

This commit is contained in:
Erik
2025-06-19 15:54:37 +02:00
parent bd9ffd9ad0
commit 2dca165869
4 changed files with 57 additions and 22 deletions

View File

@@ -536,7 +536,13 @@ async def _async_get_all_trigger_descriptions_json(hass: HomeAssistant) -> bytes
# If the descriptions are the same, return the cached JSON payload # If the descriptions are the same, return the cached JSON payload
if cached_descriptions is descriptions: if cached_descriptions is descriptions:
return cast(bytes, cached_json_payload) return cast(bytes, cached_json_payload)
json_payload = json_bytes(descriptions) json_payload = json_bytes(
{
trigger: description
for trigger, description in descriptions.items()
if description is not None
}
)
hass.data[ALL_TRIGGER_DESCRIPTIONS_JSON_CACHE] = (descriptions, json_payload) hass.data[ALL_TRIGGER_DESCRIPTIONS_JSON_CACHE] = (descriptions, json_payload)
return json_payload return json_payload
@@ -553,8 +559,10 @@ async def handle_subscribe_trigger_platforms(
descriptions = await async_get_all_trigger_descriptions(hass) descriptions = await async_get_all_trigger_descriptions(hass)
new_trigger_descriptions = {} new_trigger_descriptions = {}
for trigger in new_triggers: for trigger in new_triggers:
if trigger in descriptions: if (description := descriptions[trigger]) is not None:
new_trigger_descriptions[trigger] = descriptions[trigger] new_trigger_descriptions[trigger] = description
if not new_trigger_descriptions:
return
connection.send_event(msg["id"], new_trigger_descriptions) connection.send_event(msg["id"], new_trigger_descriptions)
connection.subscriptions[msg["id"]] = async_subscribe_trigger_platform_events( connection.subscriptions[msg["id"]] = async_subscribe_trigger_platform_events(

View File

@@ -60,7 +60,7 @@ DATA_PLUGGABLE_ACTIONS: HassKey[defaultdict[tuple, PluggableActionsEntry]] = Has
"pluggable_actions" "pluggable_actions"
) )
TRIGGER_DESCRIPTION_CACHE: HassKey[dict[str, dict[str, Any]]] = HassKey( TRIGGER_DESCRIPTION_CACHE: HassKey[dict[str, dict[str, Any] | None]] = HassKey(
"trigger_description_cache" "trigger_description_cache"
) )
TRIGGER_PLATFORM_SUBSCRIPTIONS: HassKey[ TRIGGER_PLATFORM_SUBSCRIPTIONS: HassKey[
@@ -550,7 +550,7 @@ def _load_triggers_files(
async def async_get_all_descriptions( async def async_get_all_descriptions(
hass: HomeAssistant, hass: HomeAssistant,
) -> dict[str, dict[str, Any]]: ) -> dict[str, dict[str, Any] | None]:
"""Return descriptions (i.e. user documentation) for all triggers.""" """Return descriptions (i.e. user documentation) for all triggers."""
descriptions_cache = hass.data[TRIGGER_DESCRIPTION_CACHE] descriptions_cache = hass.data[TRIGGER_DESCRIPTION_CACHE]
@@ -599,12 +599,17 @@ async def async_get_all_descriptions(
for missing_trigger in missing_triggers: for missing_trigger in missing_triggers:
domain = triggers[missing_trigger] domain = triggers[missing_trigger]
# Cache missing descriptions if (
domain_yaml = new_triggers_descriptions.get(domain) or {} yaml_description := new_triggers_descriptions.get(domain, {}).get( # type: ignore[union-attr]
missing_trigger
yaml_description = ( )
domain_yaml.get(missing_trigger) or {} # type: ignore[union-attr] ) is None:
) _LOGGER.debug(
"No trigger descriptions found for trigger %s, skipping",
missing_trigger,
)
new_descriptions_cache[missing_trigger] = None
continue
description = {"fields": yaml_description.get("fields", {})} description = {"fields": yaml_description.get("fields", {})}

View File

@@ -2,6 +2,7 @@
import asyncio import asyncio
from copy import deepcopy from copy import deepcopy
import io
import logging import logging
from typing import Any from typing import Any
from unittest.mock import ANY, AsyncMock, Mock, patch from unittest.mock import ANY, AsyncMock, Mock, patch
@@ -28,9 +29,10 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.loader import async_get_integration from homeassistant.loader import Integration, async_get_integration
from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component
from homeassistant.util.json import json_loads from homeassistant.util.json import json_loads
from homeassistant.util.yaml.loader import parse_yaml
from tests.common import ( from tests.common import (
MockConfigEntry, MockConfigEntry,
@@ -680,10 +682,34 @@ async def test_get_services(
assert msg["result"].keys() == hass.services.async_services().keys() assert msg["result"].keys() == hass.services.async_services().keys()
@patch("annotatedyaml.loader.load_yaml")
@patch.object(Integration, "has_triggers", return_value=True)
async def test_subscribe_triggers( async def test_subscribe_triggers(
hass: HomeAssistant, websocket_client: MockHAClientWebSocket mock_has_triggers: Mock,
mock_load_yaml: Mock,
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
) -> None: ) -> None:
"""Test get_triggers command.""" """Test get_triggers command."""
sun_service_descriptions = """
sun: {}
"""
tag_service_descriptions = """
tag: {}
"""
def _load_yaml(fname, secrets=None):
if fname.endswith("sun/triggers.yaml"):
service_descriptions = sun_service_descriptions
elif fname.endswith("tag/triggers.yaml"):
service_descriptions = tag_service_descriptions
else:
raise FileNotFoundError
with io.StringIO(service_descriptions) as file:
return parse_yaml(file)
mock_load_yaml.side_effect = _load_yaml
assert await async_setup_component(hass, "sun", {}) assert await async_setup_component(hass, "sun", {})
assert await async_setup_component(hass, "system_health", {}) assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done() await hass.async_block_till_done()
@@ -700,8 +726,10 @@ async def test_subscribe_triggers(
old_cache = hass.data[ALL_TRIGGER_DESCRIPTIONS_JSON_CACHE] old_cache = hass.data[ALL_TRIGGER_DESCRIPTIONS_JSON_CACHE]
# Test we receive an event when a new platform is loaded # Test we receive an event when a new platform is loaded, if it has descriptions
assert await async_setup_component(hass, "calendar", {})
assert await async_setup_component(hass, "tag", {}) assert await async_setup_component(hass, "tag", {})
await hass.async_block_till_done()
msg = await websocket_client.receive_json() msg = await websocket_client.receive_json()
assert msg == { assert msg == {
"event": {"tag": {"fields": {}}}, "event": {"tag": {"fields": {}}},

View File

@@ -478,8 +478,6 @@ async def test_async_get_all_descriptions(
hass: HomeAssistant, sun_service_descriptions: str hass: HomeAssistant, sun_service_descriptions: str
) -> None: ) -> None:
"""Test async_get_all_descriptions.""" """Test async_get_all_descriptions."""
await trigger.async_setup(hass) # Move to hass fixture
assert await async_setup_component(hass, DOMAIN_SUN, {}) assert await async_setup_component(hass, DOMAIN_SUN, {})
assert await async_setup_component(hass, DOMAIN_SYSTEM_HEALTH, {}) assert await async_setup_component(hass, DOMAIN_SYSTEM_HEALTH, {})
await hass.async_block_till_done() await hass.async_block_till_done()
@@ -546,8 +544,6 @@ async def test_async_get_all_descriptions_with_yaml_error(
expected_message: str, expected_message: str,
) -> None: ) -> None:
"""Test async_get_all_descriptions.""" """Test async_get_all_descriptions."""
await trigger.async_setup(hass) # Move to hass fixture
assert await async_setup_component(hass, DOMAIN_SUN, {}) assert await async_setup_component(hass, DOMAIN_SUN, {})
await hass.async_block_till_done() await hass.async_block_till_done()
@@ -563,7 +559,7 @@ async def test_async_get_all_descriptions_with_yaml_error(
): ):
descriptions = await trigger.async_get_all_descriptions(hass) descriptions = await trigger.async_get_all_descriptions(hass)
assert descriptions == {DOMAIN_SUN: {"fields": {}}} assert descriptions == {DOMAIN_SUN: None}
assert expected_message in caplog.text assert expected_message in caplog.text
@@ -578,8 +574,6 @@ async def test_async_get_all_descriptions_with_bad_description(
fields: not_a_dict fields: not_a_dict
""" """
await trigger.async_setup(hass) # Move to hass fixture
assert await async_setup_component(hass, DOMAIN_SUN, {}) assert await async_setup_component(hass, DOMAIN_SUN, {})
await hass.async_block_till_done() await hass.async_block_till_done()
@@ -596,7 +590,7 @@ async def test_async_get_all_descriptions_with_bad_description(
): ):
descriptions = await trigger.async_get_all_descriptions(hass) descriptions = await trigger.async_get_all_descriptions(hass)
assert descriptions == {DOMAIN_SUN: {"fields": {}}} assert descriptions == {DOMAIN_SUN: None}
assert ( assert (
"Unable to parse triggers.yaml for the sun integration: " "Unable to parse triggers.yaml for the sun integration: "