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 cached_descriptions is descriptions:
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)
return json_payload
@@ -553,8 +559,10 @@ async def handle_subscribe_trigger_platforms(
descriptions = await async_get_all_trigger_descriptions(hass)
new_trigger_descriptions = {}
for trigger in new_triggers:
if trigger in descriptions:
new_trigger_descriptions[trigger] = descriptions[trigger]
if (description := descriptions[trigger]) is not None:
new_trigger_descriptions[trigger] = description
if not new_trigger_descriptions:
return
connection.send_event(msg["id"], new_trigger_descriptions)
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"
)
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_PLATFORM_SUBSCRIPTIONS: HassKey[
@@ -550,7 +550,7 @@ def _load_triggers_files(
async def async_get_all_descriptions(
hass: HomeAssistant,
) -> dict[str, dict[str, Any]]:
) -> dict[str, dict[str, Any] | None]:
"""Return descriptions (i.e. user documentation) for all triggers."""
descriptions_cache = hass.data[TRIGGER_DESCRIPTION_CACHE]
@@ -599,12 +599,17 @@ async def async_get_all_descriptions(
for missing_trigger in missing_triggers:
domain = triggers[missing_trigger]
# Cache missing descriptions
domain_yaml = new_triggers_descriptions.get(domain) or {}
yaml_description = (
domain_yaml.get(missing_trigger) or {} # type: ignore[union-attr]
)
if (
yaml_description := new_triggers_descriptions.get(domain, {}).get( # type: ignore[union-attr]
missing_trigger
)
) 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", {})}

View File

@@ -2,6 +2,7 @@
import asyncio
from copy import deepcopy
import io
import logging
from typing import Any
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.dispatcher import async_dispatcher_send
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.util.json import json_loads
from homeassistant.util.yaml.loader import parse_yaml
from tests.common import (
MockConfigEntry,
@@ -680,10 +682,34 @@ async def test_get_services(
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(
hass: HomeAssistant, websocket_client: MockHAClientWebSocket
mock_has_triggers: Mock,
mock_load_yaml: Mock,
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
) -> None:
"""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, "system_health", {})
await hass.async_block_till_done()
@@ -700,8 +726,10 @@ async def test_subscribe_triggers(
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", {})
await hass.async_block_till_done()
msg = await websocket_client.receive_json()
assert msg == {
"event": {"tag": {"fields": {}}},

View File

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