Add a Google Calendar birthdays calendar (#141300)

pull/141335/head
Allen Porter 2025-03-24 18:06:45 -07:00 committed by GitHub
parent b2377d6da3
commit 204b1e1f24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 5 deletions

View File

@ -89,6 +89,7 @@ OPAQUE = "opaque"
RRULE_PREFIX = "RRULE:"
SERVICE_CREATE_EVENT = "create_event"
FILTERED_EVENT_TYPES = [EventTypeEnum.BIRTHDAY, EventTypeEnum.WORKING_LOCATION]
@dataclasses.dataclass(frozen=True, kw_only=True)
@ -103,7 +104,7 @@ class GoogleCalendarEntityDescription(CalendarEntityDescription):
search: str | None
local_sync: bool
device_id: str
working_location: bool = False
event_type: EventTypeEnum | None = None
def _get_entity_descriptions(
@ -173,14 +174,24 @@ def _get_entity_descriptions(
local_sync,
)
if calendar_item.primary and local_sync:
_LOGGER.debug("work location entity")
# Create a separate calendar for birthdays
entity_descriptions.append(
dataclasses.replace(
entity_description,
key=f"{key}-birthdays",
translation_key="birthdays",
event_type=EventTypeEnum.BIRTHDAY,
name=None,
entity_id=None,
)
)
# Create an optional disabled by default entity for Work Location
entity_descriptions.append(
dataclasses.replace(
entity_description,
key=f"{key}-work-location",
translation_key="working_location",
working_location=True,
event_type=EventTypeEnum.WORKING_LOCATION,
name=None,
entity_id=None,
entity_registry_enabled_default=False,
@ -383,8 +394,17 @@ class GoogleCalendarEntity(
for attendee in event.attendees
):
return False
is_working_location_event = event.event_type == EventTypeEnum.WORKING_LOCATION
if self.entity_description.working_location != is_working_location_event:
# Calendar enttiy may be limited to a specific event type
if (
self.entity_description.event_type is not None
and self.entity_description.event_type != event.event_type
):
return False
# Default calendar entity omits the special types but includes all the others
if (
self.entity_description.event_type is None
and event.event_type in FILTERED_EVENT_TYPES
):
return False
if self._ignore_availability:
return True

View File

@ -131,6 +131,9 @@
"calendar": {
"working_location": {
"name": "Working location"
},
"birthdays": {
"name": "Birthdays"
}
}
}

View File

@ -1455,6 +1455,7 @@ async def test_working_location_ignored(
("event_type", "expected_event_message"),
[
("workingLocation", "Test All Day Event"),
("birthday", None),
("default", None),
],
)
@ -1515,3 +1516,49 @@ async def test_no_working_location_entity(
entity_entry = entity_registry.async_get("calendar.working_location")
assert not entity_entry
@pytest.mark.parametrize(
("event_type", "expected_event_message"),
[
("workingLocation", None),
("birthday", "Test All Day Event"),
("default", None),
],
)
@pytest.mark.parametrize("calendar_is_primary", [True])
async def test_birthday_entity(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
entity_registry: er.EntityRegistry,
mock_events_list_items: Callable[[list[dict[str, Any]]], None],
component_setup: ComponentSetup,
event_type: str,
expected_event_message: str | None,
) -> None:
"""Test that birthday events appear only on the birthdays calendar."""
event = {
**TEST_EVENT,
**upcoming(),
"eventType": event_type,
}
mock_events_list_items([event])
assert await component_setup()
entity_entry = entity_registry.async_get("calendar.birthdays")
assert entity_entry
assert entity_entry.disabled_by is None # Enabled by default
entity_registry.async_update_entity(
entity_id="calendar.birthdays", disabled_by=None
)
async_fire_time_changed(
hass,
dt_util.utcnow() + datetime.timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1),
)
await hass.async_block_till_done()
state = hass.states.get("calendar.birthdays")
assert state
assert state.name == "Birthdays"
assert state.attributes.get("message") == expected_event_message