mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add a working location google calendar entity (#127016)
This commit is contained in:
parent
963b9d9a83
commit
c5ebd53079
@ -3,14 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
import dataclasses
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from gcal_sync.api import Range, SyncEventsRequest
|
||||
from gcal_sync.exceptions import ApiException
|
||||
from gcal_sync.model import AccessRole, Calendar, DateOrDatetime, Event
|
||||
from gcal_sync.model import AccessRole, Calendar, DateOrDatetime, Event, EventTypeEnum
|
||||
from gcal_sync.store import ScopedCalendarStore
|
||||
from gcal_sync.sync import CalendarEventSyncManager
|
||||
|
||||
@ -84,18 +84,19 @@ RRULE_PREFIX = "RRULE:"
|
||||
SERVICE_CREATE_EVENT = "create_event"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class GoogleCalendarEntityDescription(CalendarEntityDescription):
|
||||
"""Google calendar entity description."""
|
||||
|
||||
name: str
|
||||
entity_id: str
|
||||
name: str | None
|
||||
entity_id: str | None
|
||||
read_only: bool
|
||||
ignore_availability: bool
|
||||
offset: str | None
|
||||
search: str | None
|
||||
local_sync: bool
|
||||
device_id: str
|
||||
working_location: bool = False
|
||||
|
||||
|
||||
def _get_entity_descriptions(
|
||||
@ -142,22 +143,42 @@ def _get_entity_descriptions(
|
||||
) or calendar_item.access_role == AccessRole.FREE_BUSY_READER:
|
||||
read_only = True
|
||||
local_sync = False
|
||||
entity_descriptions.append(
|
||||
GoogleCalendarEntityDescription(
|
||||
key=key,
|
||||
name=data[CONF_NAME].capitalize(),
|
||||
entity_id=generate_entity_id(
|
||||
ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass
|
||||
),
|
||||
read_only=read_only,
|
||||
ignore_availability=data.get(CONF_IGNORE_AVAILABILITY, False),
|
||||
offset=data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET),
|
||||
search=search,
|
||||
local_sync=local_sync,
|
||||
entity_registry_enabled_default=entity_enabled,
|
||||
device_id=data[CONF_DEVICE_ID],
|
||||
)
|
||||
entity_description = GoogleCalendarEntityDescription(
|
||||
key=key,
|
||||
name=data[CONF_NAME].capitalize(),
|
||||
entity_id=generate_entity_id(
|
||||
ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass
|
||||
),
|
||||
read_only=read_only,
|
||||
ignore_availability=data.get(CONF_IGNORE_AVAILABILITY, False),
|
||||
offset=data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET),
|
||||
search=search,
|
||||
local_sync=local_sync,
|
||||
entity_registry_enabled_default=entity_enabled,
|
||||
device_id=data[CONF_DEVICE_ID],
|
||||
)
|
||||
entity_descriptions.append(entity_description)
|
||||
_LOGGER.debug(
|
||||
"calendar_item.primary=%s, search=%s, calendar_item.access_role=%s - %s",
|
||||
calendar_item.primary,
|
||||
search,
|
||||
calendar_item.access_role,
|
||||
local_sync,
|
||||
)
|
||||
if calendar_item.primary and local_sync:
|
||||
_LOGGER.debug("work location entity")
|
||||
# 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,
|
||||
name=None,
|
||||
entity_id=None,
|
||||
entity_registry_enabled_default=False,
|
||||
)
|
||||
)
|
||||
return entity_descriptions
|
||||
|
||||
|
||||
@ -233,12 +254,13 @@ async def async_setup_entry(
|
||||
entity_registry.async_remove(
|
||||
entity_entry.entity_id,
|
||||
)
|
||||
_LOGGER.debug("Creating entity with unique_id=%s", unique_id)
|
||||
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator
|
||||
if not entity_description.local_sync:
|
||||
coordinator = CalendarQueryUpdateCoordinator(
|
||||
hass,
|
||||
calendar_service,
|
||||
entity_description.name,
|
||||
entity_description.name or entity_description.key,
|
||||
calendar_id,
|
||||
entity_description.search,
|
||||
)
|
||||
@ -257,7 +279,7 @@ async def async_setup_entry(
|
||||
coordinator = CalendarSyncUpdateCoordinator(
|
||||
hass,
|
||||
sync,
|
||||
entity_description.name,
|
||||
entity_description.name or entity_description.key,
|
||||
)
|
||||
entities.append(
|
||||
GoogleCalendarEntity(
|
||||
@ -310,12 +332,15 @@ class GoogleCalendarEntity(
|
||||
) -> None:
|
||||
"""Create the Calendar event device."""
|
||||
super().__init__(coordinator)
|
||||
_LOGGER.debug("entity_description.entity_id=%s", entity_description.entity_id)
|
||||
_LOGGER.debug("entity_description=%s", entity_description)
|
||||
self.calendar_id = calendar_id
|
||||
self.entity_description = entity_description
|
||||
self._ignore_availability = entity_description.ignore_availability
|
||||
self._offset = entity_description.offset
|
||||
self._event: CalendarEvent | None = None
|
||||
self.entity_id = entity_description.entity_id
|
||||
if entity_description.entity_id:
|
||||
self.entity_id = entity_description.entity_id
|
||||
self._attr_unique_id = unique_id
|
||||
if not entity_description.read_only:
|
||||
self._attr_supported_features = (
|
||||
@ -343,6 +368,8 @@ class GoogleCalendarEntity(
|
||||
|
||||
def _event_filter(self, event: Event) -> bool:
|
||||
"""Return True if the event is visible."""
|
||||
if event.event_type == EventTypeEnum.WORKING_LOCATION:
|
||||
return self.entity_description.working_location
|
||||
if self._ignore_availability:
|
||||
return True
|
||||
return event.transparency == OPAQUE
|
||||
|
@ -123,5 +123,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"calendar": {
|
||||
"working_location": {
|
||||
"name": "Working location"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,12 +98,21 @@ def calendar_access_role() -> str:
|
||||
return "owner"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def calendar_is_primary() -> bool:
|
||||
"""Set if the calendar is the primary or not."""
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(name="test_api_calendar")
|
||||
def api_calendar(calendar_access_role: str) -> dict[str, Any]:
|
||||
def api_calendar(
|
||||
calendar_access_role: str, calendar_is_primary: bool
|
||||
) -> dict[str, Any]:
|
||||
"""Return a test calendar object used in API responses."""
|
||||
return {
|
||||
**TEST_API_CALENDAR,
|
||||
"accessRole": calendar_access_role,
|
||||
"primary": calendar_is_primary,
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,9 +15,11 @@ from gcal_sync.auth import API_BASE_URL
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS, DOMAIN
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_registry import RegistryEntryDisabler
|
||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
@ -1359,3 +1361,90 @@ async def test_invalid_rrule_fix(
|
||||
assert event["uid"] == "cydrevtfuybguinhomj@google.com"
|
||||
assert event["recurrence_id"] == "_c8rinwq863h45qnucyoi43ny8_20230915"
|
||||
assert event["rrule"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("event_type", "expected_event_message"),
|
||||
[
|
||||
("default", "Test All Day Event"),
|
||||
("workingLocation", None),
|
||||
],
|
||||
)
|
||||
async def test_working_location_ignored(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_events_list_items: Callable[[list[dict[str, Any]]], None],
|
||||
component_setup: ComponentSetup,
|
||||
event_type: str,
|
||||
expected_event_message: str | None,
|
||||
) -> None:
|
||||
"""Test working location events are skipped."""
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
**upcoming(),
|
||||
"eventType": event_type,
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
assert state.attributes.get("message") == expected_event_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize("calendar_is_primary", [True])
|
||||
async def test_working_location_entity(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_events_list_items: Callable[[list[dict[str, Any]]], None],
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test that working location events are registered under a disabled by default entity."""
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
**upcoming(),
|
||||
"eventType": "workingLocation",
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
assert await component_setup()
|
||||
|
||||
entity_entry = entity_registry.async_get("calendar.working_location")
|
||||
assert entity_entry
|
||||
assert entity_entry.disabled_by == RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
entity_registry.async_update_entity(
|
||||
entity_id="calendar.working_location", 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.working_location")
|
||||
assert state
|
||||
assert state.name == "Working location"
|
||||
assert state.attributes.get("message") == "Test All Day Event"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("calendar_is_primary", [False])
|
||||
async def test_no_working_location_entity(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_events_list_items: Callable[[list[dict[str, Any]]], None],
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test that working location events are not registered for a secondary calendar."""
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
**upcoming(),
|
||||
"eventType": "workingLocation",
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
assert await component_setup()
|
||||
|
||||
entity_entry = entity_registry.async_get("calendar.working_location")
|
||||
assert not entity_entry
|
||||
|
Loading…
x
Reference in New Issue
Block a user