Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only show Google Tasks that are parents and fix ordering #103820

Merged
merged 3 commits into from Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 15 additions & 2 deletions homeassistant/components/google_tasks/todo.py
Expand Up @@ -2,7 +2,7 @@
from __future__ import annotations

from datetime import timedelta
from typing import cast
from typing import Any, cast

from homeassistant.components.todo import (
TodoItem,
Expand Down Expand Up @@ -96,7 +96,7 @@ def todo_items(self) -> list[TodoItem] | None:
item.get("status"), TodoItemStatus.NEEDS_ACTION # type: ignore[arg-type]
),
)
for item in self.coordinator.data
for item in _order_tasks(self.coordinator.data)
]

async def async_create_todo_item(self, item: TodoItem) -> None:
Expand All @@ -121,3 +121,16 @@ async def async_delete_todo_items(self, uids: list[str]) -> None:
"""Delete To-do items."""
await self.coordinator.api.delete(self._task_list_id, uids)
await self.coordinator.async_refresh()


def _order_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Order the task items response.

All tasks have an order amongst their sibblings based on position.

Home Assistant To-do items do not support the Google Task parent/sibbling
relationships and the desired behavior is for them to be filtered.
"""
parents = [task for task in tasks if task.get("parent") is None]
parents.sort(key=lambda task: task["position"])
return parents
24 changes: 24 additions & 0 deletions tests/components/google_tasks/snapshots/test_todo.ambr
Expand Up @@ -14,6 +14,30 @@
'POST',
)
# ---
# name: test_parent_child_ordering[api_responses0]
list([
dict({
'status': 'needs_action',
'summary': 'Task 1',
'uid': 'task-1',
}),
dict({
'status': 'needs_action',
'summary': 'Task 2',
'uid': 'task-2',
}),
dict({
'status': 'needs_action',
'summary': 'Task 3 (Parent)',
'uid': 'task-3',
}),
dict({
'status': 'needs_action',
'summary': 'Task 4',
'uid': 'task-4',
}),
])
# ---
# name: test_partial_update_status[api_responses0]
tuple(
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
Expand Down
113 changes: 106 additions & 7 deletions tests/components/google_tasks/test_todo.py
Expand Up @@ -45,14 +45,34 @@

LIST_TASKS_RESPONSE_WATER = {
"items": [
{"id": "some-task-id", "title": "Water", "status": "needsAction"},
{
"id": "some-task-id",
"title": "Water",
"status": "needsAction",
"position": "00000000000000000001",
},
],
}
LIST_TASKS_RESPONSE_MULTIPLE = {
"items": [
{"id": "some-task-id-1", "title": "Water", "status": "needsAction"},
{"id": "some-task-id-2", "title": "Milk", "status": "needsAction"},
{"id": "some-task-id-3", "title": "Cheese", "status": "needsAction"},
{
"id": "some-task-id-2",
"title": "Milk",
"status": "needsAction",
"position": "00000000000000000002",
},
{
"id": "some-task-id-1",
"title": "Water",
"status": "needsAction",
"position": "00000000000000000001",
},
{
"id": "some-task-id-3",
"title": "Cheese",
"status": "needsAction",
"position": "00000000000000000003",
},
],
}

Expand Down Expand Up @@ -199,8 +219,18 @@ def mock_http_response(response_handler: list | Callable) -> Mock:
LIST_TASK_LIST_RESPONSE,
{
"items": [
{"id": "task-1", "title": "Task 1", "status": "needsAction"},
{"id": "task-2", "title": "Task 2", "status": "completed"},
{
"id": "task-1",
"title": "Task 1",
"status": "needsAction",
"position": "0000000000000001",
},
{
"id": "task-2",
"title": "Task 2",
"status": "completed",
"position": "0000000000000002",
},
],
},
]
Expand Down Expand Up @@ -558,7 +588,7 @@ async def test_partial_update_status(
LIST_TASK_LIST_RESPONSE,
LIST_TASKS_RESPONSE_MULTIPLE,
[EMPTY_RESPONSE, EMPTY_RESPONSE, EMPTY_RESPONSE], # Delete batch
LIST_TASKS_RESPONSE, # refresh after create
LIST_TASKS_RESPONSE, # refresh after delete
]
)
)
Expand Down Expand Up @@ -714,3 +744,72 @@ async def test_delete_server_error(
target={"entity_id": "todo.my_tasks"},
blocking=True,
)


@pytest.mark.parametrize(
"api_responses",
[
[
LIST_TASK_LIST_RESPONSE,
{
"items": [
{
"id": "task-3-2",
"title": "Child 2",
"status": "needsAction",
"parent": "task-3",
"position": "0000000000000002",
},
{
"id": "task-3",
"title": "Task 3 (Parent)",
"status": "needsAction",
"position": "0000000000000003",
},
{
"id": "task-2",
"title": "Task 2",
"status": "needsAction",
"position": "0000000000000002",
},
{
"id": "task-1",
"title": "Task 1",
"status": "needsAction",
"position": "0000000000000001",
},
{
"id": "task-3-1",
"title": "Child 1",
"status": "needsAction",
"parent": "task-3",
"position": "0000000000000001",
},
{
"id": "task-4",
"title": "Task 4",
"status": "needsAction",
"position": "0000000000000004",
},
],
},
]
],
)
async def test_parent_child_ordering(
hass: HomeAssistant,
setup_credentials: None,
integration_setup: Callable[[], Awaitable[bool]],
ws_get_items: Callable[[], Awaitable[dict[str, str]]],
snapshot: SnapshotAssertion,
) -> None:
"""Test getting todo list items."""

assert await integration_setup()

state = hass.states.get("todo.my_tasks")
assert state
assert state.state == "4"

items = await ws_get_items()
assert items == snapshot