mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add update_habit
action to Habitica integration (#139311)
* Add update_habit action * icons
This commit is contained in:
parent
e6c946b3f4
commit
b0b5567316
@ -40,6 +40,10 @@ ATTR_ALIAS = "alias"
|
||||
ATTR_PRIORITY = "priority"
|
||||
ATTR_COST = "cost"
|
||||
ATTR_NOTES = "notes"
|
||||
ATTR_UP_DOWN = "up_down"
|
||||
ATTR_FREQUENCY = "frequency"
|
||||
ATTR_COUNTER_UP = "counter_up"
|
||||
ATTR_COUNTER_DOWN = "counter_down"
|
||||
|
||||
SERVICE_CAST_SKILL = "cast_skill"
|
||||
SERVICE_START_QUEST = "start_quest"
|
||||
@ -57,6 +61,7 @@ SERVICE_TRANSFORMATION = "transformation"
|
||||
|
||||
SERVICE_UPDATE_REWARD = "update_reward"
|
||||
SERVICE_CREATE_REWARD = "create_reward"
|
||||
SERVICE_UPDATE_HABIT = "update_habit"
|
||||
|
||||
DEVELOPER_ID = "4c4ca53f-c059-4ffa-966e-9d29dd405daf"
|
||||
X_CLIENT = f"{DEVELOPER_ID} - {APPLICATION_NAME} {__version__}"
|
||||
|
@ -230,6 +230,13 @@
|
||||
"sections": {
|
||||
"developer_options": "mdi:test-tube"
|
||||
}
|
||||
},
|
||||
"update_habit": {
|
||||
"service": "mdi:contrast-box",
|
||||
"sections": {
|
||||
"tag_options": "mdi:tag",
|
||||
"developer_options": "mdi:test-tube"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ from uuid import UUID
|
||||
from aiohttp import ClientError
|
||||
from habiticalib import (
|
||||
Direction,
|
||||
Frequency,
|
||||
HabiticaException,
|
||||
NotAuthorizedError,
|
||||
NotFoundError,
|
||||
@ -41,8 +42,11 @@ from .const import (
|
||||
ATTR_ARGS,
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_COST,
|
||||
ATTR_COUNTER_DOWN,
|
||||
ATTR_COUNTER_UP,
|
||||
ATTR_DATA,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_FREQUENCY,
|
||||
ATTR_ITEM,
|
||||
ATTR_KEYWORD,
|
||||
ATTR_NOTES,
|
||||
@ -54,6 +58,7 @@ from .const import (
|
||||
ATTR_TARGET,
|
||||
ATTR_TASK,
|
||||
ATTR_TYPE,
|
||||
ATTR_UP_DOWN,
|
||||
DOMAIN,
|
||||
EVENT_API_CALL_SUCCESS,
|
||||
SERVICE_ABORT_QUEST,
|
||||
@ -69,6 +74,7 @@ from .const import (
|
||||
SERVICE_SCORE_REWARD,
|
||||
SERVICE_START_QUEST,
|
||||
SERVICE_TRANSFORMATION,
|
||||
SERVICE_UPDATE_HABIT,
|
||||
SERVICE_UPDATE_REWARD,
|
||||
)
|
||||
from .coordinator import HabiticaConfigEntry
|
||||
@ -123,6 +129,13 @@ BASE_TASK_SCHEMA = vol.Schema(
|
||||
cv.string, cv.matches_regex("^[a-zA-Z0-9-_]*$")
|
||||
),
|
||||
vol.Optional(ATTR_COST): vol.All(vol.Coerce(float), vol.Range(0)),
|
||||
vol.Optional(ATTR_PRIORITY): vol.All(
|
||||
vol.Upper, vol.In(TaskPriority._member_names_)
|
||||
),
|
||||
vol.Optional(ATTR_UP_DOWN): vol.All(cv.ensure_list, [str]),
|
||||
vol.Optional(ATTR_COUNTER_UP): vol.All(int, vol.Range(0)),
|
||||
vol.Optional(ATTR_COUNTER_DOWN): vol.All(int, vol.Range(0)),
|
||||
vol.Optional(ATTR_FREQUENCY): vol.Coerce(Frequency),
|
||||
}
|
||||
)
|
||||
|
||||
@ -173,6 +186,12 @@ ITEMID_MAP = {
|
||||
"shiny_seed": Skill.SHINY_SEED,
|
||||
}
|
||||
|
||||
SERVICE_TASK_TYPE_MAP = {
|
||||
SERVICE_UPDATE_REWARD: TaskType.REWARD,
|
||||
SERVICE_CREATE_REWARD: TaskType.REWARD,
|
||||
SERVICE_UPDATE_HABIT: TaskType.HABIT,
|
||||
}
|
||||
|
||||
|
||||
def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
||||
"""Return config entry or raise if not found or not loaded."""
|
||||
@ -551,12 +570,12 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
|
||||
return result
|
||||
|
||||
async def create_or_update_task(call: ServiceCall) -> ServiceResponse:
|
||||
async def create_or_update_task(call: ServiceCall) -> ServiceResponse: # noqa: C901
|
||||
"""Create or update task action."""
|
||||
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
|
||||
coordinator = entry.runtime_data
|
||||
await coordinator.async_refresh()
|
||||
is_update = call.service == SERVICE_UPDATE_REWARD
|
||||
is_update = call.service in (SERVICE_UPDATE_REWARD, SERVICE_UPDATE_HABIT)
|
||||
current_task = None
|
||||
|
||||
if is_update:
|
||||
@ -565,7 +584,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
task
|
||||
for task in coordinator.data.tasks
|
||||
if call.data[ATTR_TASK] in (str(task.id), task.alias, task.text)
|
||||
and task.Type is TaskType.REWARD
|
||||
and task.Type is SERVICE_TASK_TYPE_MAP[call.service]
|
||||
)
|
||||
except StopIteration as e:
|
||||
raise ServiceValidationError(
|
||||
@ -648,6 +667,22 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
if (cost := call.data.get(ATTR_COST)) is not None:
|
||||
data["value"] = cost
|
||||
|
||||
if priority := call.data.get(ATTR_PRIORITY):
|
||||
data["priority"] = TaskPriority[priority]
|
||||
|
||||
if frequency := call.data.get(ATTR_FREQUENCY):
|
||||
data["frequency"] = frequency
|
||||
|
||||
if up_down := call.data.get(ATTR_UP_DOWN):
|
||||
data["up"] = "up" in up_down
|
||||
data["down"] = "down" in up_down
|
||||
|
||||
if counter_up := call.data.get(ATTR_COUNTER_UP):
|
||||
data["counterUp"] = counter_up
|
||||
|
||||
if counter_down := call.data.get(ATTR_COUNTER_DOWN):
|
||||
data["counterDown"] = counter_down
|
||||
|
||||
try:
|
||||
if is_update:
|
||||
if TYPE_CHECKING:
|
||||
@ -684,6 +719,13 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
schema=SERVICE_UPDATE_TASK_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_UPDATE_HABIT,
|
||||
create_or_update_task,
|
||||
schema=SERVICE_UPDATE_TASK_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_CREATE_REWARD,
|
||||
|
@ -144,7 +144,7 @@ update_reward:
|
||||
fields:
|
||||
config_entry: *config_entry
|
||||
task: *task
|
||||
rename:
|
||||
rename: &rename
|
||||
selector:
|
||||
text:
|
||||
notes: ¬es
|
||||
@ -160,7 +160,7 @@ update_reward:
|
||||
step: 0.01
|
||||
unit_of_measurement: "🪙"
|
||||
mode: box
|
||||
tag_options:
|
||||
tag_options: &tag_options
|
||||
collapsed: true
|
||||
fields:
|
||||
tag: &tag
|
||||
@ -176,7 +176,7 @@ update_reward:
|
||||
developer_options: &developer_options
|
||||
collapsed: true
|
||||
fields:
|
||||
alias:
|
||||
alias: &alias
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
@ -193,3 +193,62 @@ create_reward:
|
||||
selector: *cost_selector
|
||||
tag: *tag
|
||||
developer_options: *developer_options
|
||||
update_habit:
|
||||
fields:
|
||||
config_entry: *config_entry
|
||||
task: *task
|
||||
rename: *rename
|
||||
notes: *notes
|
||||
up_down:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- value: up
|
||||
label: "➕"
|
||||
- value: down
|
||||
label: "➖"
|
||||
multiple: true
|
||||
mode: list
|
||||
priority:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "trivial"
|
||||
- "easy"
|
||||
- "medium"
|
||||
- "hard"
|
||||
mode: dropdown
|
||||
translation_key: "priority"
|
||||
frequency:
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "daily"
|
||||
- "weekly"
|
||||
- "monthly"
|
||||
translation_key: "frequency"
|
||||
mode: dropdown
|
||||
tag_options: *tag_options
|
||||
developer_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
counter_up:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
step: 1
|
||||
unit_of_measurement: "➕"
|
||||
mode: box
|
||||
counter_down:
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
step: 1
|
||||
unit_of_measurement: "➖"
|
||||
mode: box
|
||||
alias: *alias
|
||||
|
@ -759,6 +759,70 @@
|
||||
"description": "[%key:component::habitica::common::developer_options_description%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"update_habit": {
|
||||
"name": "Update a habit",
|
||||
"description": "Updates a specific habit for the selected Habitica character",
|
||||
"fields": {
|
||||
"config_entry": {
|
||||
"name": "[%key:component::habitica::common::config_entry_name%]",
|
||||
"description": "Select the Habitica account to update a habit."
|
||||
},
|
||||
"task": {
|
||||
"name": "[%key:component::habitica::common::task_name%]",
|
||||
"description": "[%key:component::habitica::common::task_description%]"
|
||||
},
|
||||
"rename": {
|
||||
"name": "[%key:component::habitica::common::rename_name%]",
|
||||
"description": "[%key:component::habitica::common::rename_description%]"
|
||||
},
|
||||
"notes": {
|
||||
"name": "[%key:component::habitica::common::notes_name%]",
|
||||
"description": "[%key:component::habitica::common::notes_description%]"
|
||||
},
|
||||
"tag": {
|
||||
"name": "[%key:component::habitica::common::tag_name%]",
|
||||
"description": "[%key:component::habitica::common::tag_description%]"
|
||||
},
|
||||
"remove_tag": {
|
||||
"name": "[%key:component::habitica::common::remove_tag_name%]",
|
||||
"description": "[%key:component::habitica::common::remove_tag_description%]"
|
||||
},
|
||||
"alias": {
|
||||
"name": "[%key:component::habitica::common::alias_name%]",
|
||||
"description": "[%key:component::habitica::common::alias_description%]"
|
||||
},
|
||||
"priority": {
|
||||
"name": "Difficulty",
|
||||
"description": "Update the difficulty of a task."
|
||||
},
|
||||
"frequency": {
|
||||
"name": "Counter reset",
|
||||
"description": "Update when a habit's counter resets: daily resets at the start of a new day, weekly after Sunday night, and monthly at the beginning of a new month."
|
||||
},
|
||||
"up_down": {
|
||||
"name": "Rewards or losses",
|
||||
"description": "Update if the habit is good and rewarding (positive), bad and penalizing (negative), or both."
|
||||
},
|
||||
"counter_up": {
|
||||
"name": "Adjust positive counter",
|
||||
"description": "Update the up counter of a positive habit."
|
||||
},
|
||||
"counter_down": {
|
||||
"name": "Adjust negative counter",
|
||||
"description": "Update the down counter of a negative habit."
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"tag_options": {
|
||||
"name": "[%key:component::habitica::common::tag_options_name%]",
|
||||
"description": "[%key:component::habitica::common::tag_options_description%]"
|
||||
},
|
||||
"developer_options": {
|
||||
"name": "[%key:component::habitica::common::developer_options_name%]",
|
||||
"description": "[%key:component::habitica::common::developer_options_description%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
@ -793,6 +857,14 @@
|
||||
"medium": "Medium",
|
||||
"hard": "Hard"
|
||||
}
|
||||
},
|
||||
"frequency": {
|
||||
"options": {
|
||||
"daily": "Daily",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"yearly": "Yearly"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,15 @@ from unittest.mock import AsyncMock, patch
|
||||
from uuid import UUID
|
||||
|
||||
from aiohttp import ClientError
|
||||
from habiticalib import Direction, HabiticaTaskResponse, Skill, Task, TaskType
|
||||
from habiticalib import (
|
||||
Direction,
|
||||
Frequency,
|
||||
HabiticaTaskResponse,
|
||||
Skill,
|
||||
Task,
|
||||
TaskPriority,
|
||||
TaskType,
|
||||
)
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@ -14,7 +22,10 @@ from homeassistant.components.habitica.const import (
|
||||
ATTR_ALIAS,
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_COST,
|
||||
ATTR_COUNTER_DOWN,
|
||||
ATTR_COUNTER_UP,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_FREQUENCY,
|
||||
ATTR_ITEM,
|
||||
ATTR_KEYWORD,
|
||||
ATTR_NOTES,
|
||||
@ -25,6 +36,7 @@ from homeassistant.components.habitica.const import (
|
||||
ATTR_TARGET,
|
||||
ATTR_TASK,
|
||||
ATTR_TYPE,
|
||||
ATTR_UP_DOWN,
|
||||
DOMAIN,
|
||||
SERVICE_ABORT_QUEST,
|
||||
SERVICE_ACCEPT_QUEST,
|
||||
@ -38,6 +50,7 @@ from homeassistant.components.habitica.const import (
|
||||
SERVICE_SCORE_REWARD,
|
||||
SERVICE_START_QUEST,
|
||||
SERVICE_TRANSFORMATION,
|
||||
SERVICE_UPDATE_HABIT,
|
||||
SERVICE_UPDATE_REWARD,
|
||||
)
|
||||
from homeassistant.components.todo import ATTR_RENAME
|
||||
@ -919,6 +932,13 @@ async def test_get_tasks(
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("service", "task_id"),
|
||||
[
|
||||
(SERVICE_UPDATE_REWARD, "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"),
|
||||
(SERVICE_UPDATE_HABIT, "f21fa608-cfc6-4413-9fc7-0eb1b48ca43a"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("habitica")
|
||||
async def test_update_task_exceptions(
|
||||
hass: HomeAssistant,
|
||||
@ -927,15 +947,16 @@ async def test_update_task_exceptions(
|
||||
exception: Exception,
|
||||
expected_exception: Exception,
|
||||
exception_msg: str,
|
||||
service: str,
|
||||
task_id: str,
|
||||
) -> None:
|
||||
"""Test Habitica task action exceptions."""
|
||||
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||
|
||||
habitica.update_task.side_effect = exception
|
||||
with pytest.raises(expected_exception, match=exception_msg):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_UPDATE_REWARD,
|
||||
service,
|
||||
service_data={
|
||||
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||
ATTR_TASK: task_id,
|
||||
@ -1125,6 +1146,90 @@ async def test_create_reward(
|
||||
habitica.create_task.assert_awaited_with(call_args)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_data", "call_args"),
|
||||
[
|
||||
(
|
||||
{
|
||||
ATTR_RENAME: "RENAME",
|
||||
},
|
||||
Task(text="RENAME"),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_NOTES: "NOTES",
|
||||
},
|
||||
Task(notes="NOTES"),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_UP_DOWN: [""],
|
||||
},
|
||||
Task(up=False, down=False),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_UP_DOWN: ["up"],
|
||||
},
|
||||
Task(up=True, down=False),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_UP_DOWN: ["down"],
|
||||
},
|
||||
Task(up=False, down=True),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_PRIORITY: "trivial",
|
||||
},
|
||||
Task(priority=TaskPriority.TRIVIAL),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_FREQUENCY: "daily",
|
||||
},
|
||||
Task(frequency=Frequency.DAILY),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_COUNTER_UP: 1,
|
||||
ATTR_COUNTER_DOWN: 2,
|
||||
},
|
||||
Task(counterUp=1, counterDown=2),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_ALIAS: "ALIAS",
|
||||
},
|
||||
Task(alias="ALIAS"),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_update_habit(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
habitica: AsyncMock,
|
||||
service_data: dict[str, Any],
|
||||
call_args: Task,
|
||||
) -> None:
|
||||
"""Test Habitica habit action."""
|
||||
task_id = "f21fa608-cfc6-4413-9fc7-0eb1b48ca43a"
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_UPDATE_HABIT,
|
||||
service_data={
|
||||
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||
ATTR_TASK: task_id,
|
||||
**service_data,
|
||||
},
|
||||
return_response=True,
|
||||
blocking=True,
|
||||
)
|
||||
habitica.update_task.assert_awaited_with(UUID(task_id), call_args)
|
||||
|
||||
|
||||
async def test_tags(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
Loading…
x
Reference in New Issue
Block a user