Add support for reordering Shopping List Items via Drag and Drop (#41585)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/44840/head
Shane Qi 2021-01-05 04:24:30 -06:00 committed by GitHub
parent addafd517f
commit c654476e24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 177 additions and 1 deletions

View File

@ -128,6 +128,8 @@ async def async_setup_entry(hass, config_entry):
SCHEMA_WEBSOCKET_CLEAR_ITEMS,
)
websocket_api.async_register_command(hass, websocket_handle_reorder)
return True
@ -163,6 +165,31 @@ class ShoppingData:
self.items = [itm for itm in self.items if not itm["complete"]]
await self.hass.async_add_executor_job(self.save)
@callback
def async_reorder(self, item_ids):
"""Reorder items."""
# The array for sorted items.
new_items = []
all_items_mapping = {item["id"]: item for item in self.items}
# Append items by the order of passed in array.
for item_id in item_ids:
if item_id not in all_items_mapping:
raise KeyError
new_items.append(all_items_mapping[item_id])
# Remove the item from mapping after it's appended in the result array.
del all_items_mapping[item_id]
# Append the rest of the items
for key in all_items_mapping:
# All the unchecked items must be passed in the item_ids array,
# so all items left in the mapping should be checked items.
if all_items_mapping[key]["complete"] is False:
raise vol.Invalid(
"The item ids array doesn't contain all the unchecked shopping list items."
)
new_items.append(all_items_mapping[key])
self.items = new_items
self.hass.async_add_executor_job(self.save)
async def async_load(self):
"""Load items."""
@ -277,3 +304,26 @@ async def websocket_handle_clear(hass, connection, msg):
await hass.data[DOMAIN].async_clear_completed()
hass.bus.async_fire(EVENT, {"action": "clear"})
connection.send_message(websocket_api.result_message(msg["id"]))
@websocket_api.websocket_command(
{
vol.Required("type"): "shopping_list/items/reorder",
vol.Required("item_ids"): [str],
}
)
def websocket_handle_reorder(hass, connection, msg):
"""Handle reordering shopping_list items."""
msg_id = msg.pop("id")
try:
hass.data[DOMAIN].async_reorder(msg.pop("item_ids"))
hass.bus.async_fire(EVENT, {"action": "reorder"})
connection.send_result(msg_id)
except KeyError:
connection.send_error(
msg_id,
websocket_api.const.ERR_NOT_FOUND,
"One or more item id(s) not found.",
)
except vol.Invalid as err:
connection.send_error(msg_id, websocket_api.const.ERR_INVALID_FORMAT, f"{err}")

View File

@ -1,6 +1,10 @@
"""Test shopping list component."""
from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.components.websocket_api.const import (
ERR_INVALID_FORMAT,
ERR_NOT_FOUND,
TYPE_RESULT,
)
from homeassistant.const import HTTP_NOT_FOUND
from homeassistant.helpers import intent
@ -311,3 +315,125 @@ async def test_ws_add_item_fail(hass, hass_ws_client, sl_setup):
msg = await client.receive_json()
assert msg["success"] is False
assert len(hass.data["shopping_list"].items) == 0
async def test_ws_reorder_items(hass, hass_ws_client, sl_setup):
"""Test reordering shopping_list items websocket command."""
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}}
)
beer_id = hass.data["shopping_list"].items[0]["id"]
wine_id = hass.data["shopping_list"].items[1]["id"]
apple_id = hass.data["shopping_list"].items[2]["id"]
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 6,
"type": "shopping_list/items/reorder",
"item_ids": [wine_id, apple_id, beer_id],
}
)
msg = await client.receive_json()
assert msg["success"] is True
assert hass.data["shopping_list"].items[0] == {
"id": wine_id,
"name": "wine",
"complete": False,
}
assert hass.data["shopping_list"].items[1] == {
"id": apple_id,
"name": "apple",
"complete": False,
}
assert hass.data["shopping_list"].items[2] == {
"id": beer_id,
"name": "beer",
"complete": False,
}
# Mark wine as completed.
await client.send_json(
{
"id": 7,
"type": "shopping_list/items/update",
"item_id": wine_id,
"complete": True,
}
)
_ = await client.receive_json()
await client.send_json(
{
"id": 8,
"type": "shopping_list/items/reorder",
"item_ids": [apple_id, beer_id],
}
)
msg = await client.receive_json()
assert msg["success"] is True
assert hass.data["shopping_list"].items[0] == {
"id": apple_id,
"name": "apple",
"complete": False,
}
assert hass.data["shopping_list"].items[1] == {
"id": beer_id,
"name": "beer",
"complete": False,
}
assert hass.data["shopping_list"].items[2] == {
"id": wine_id,
"name": "wine",
"complete": True,
}
async def test_ws_reorder_items_failure(hass, hass_ws_client, sl_setup):
"""Test reordering shopping_list items websocket command."""
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}}
)
beer_id = hass.data["shopping_list"].items[0]["id"]
wine_id = hass.data["shopping_list"].items[1]["id"]
apple_id = hass.data["shopping_list"].items[2]["id"]
client = await hass_ws_client(hass)
# Testing sending bad item id.
await client.send_json(
{
"id": 8,
"type": "shopping_list/items/reorder",
"item_ids": [wine_id, apple_id, beer_id, "BAD_ID"],
}
)
msg = await client.receive_json()
assert msg["success"] is False
assert msg["error"]["code"] == ERR_NOT_FOUND
# Testing not sending all unchecked item ids.
await client.send_json(
{
"id": 9,
"type": "shopping_list/items/reorder",
"item_ids": [wine_id, apple_id],
}
)
msg = await client.receive_json()
assert msg["success"] is False
assert msg["error"]["code"] == ERR_INVALID_FORMAT