From 7759ab6919373bf1a2dd89b2780bbc31c0163210 Mon Sep 17 00:00:00 2001 From: ChristianKuehnel Date: Fri, 29 Dec 2017 19:20:36 +0100 Subject: [PATCH] Remember the Milk - updating and completing tasks (#11069) * Remember the Milk - updating and completing tasks Added new feature so that tasks can be updated and completed. For this feature a task id must be set when creating the task. * fixed hould complaints * fixed review comments by @MartinHjelmare * removed unnecessary check as proposed by @MartinHjelmare --- .../components/remember_the_milk/__init__.py | 116 ++++++++++++++++-- .../remember_the_milk/services.yaml | 19 ++- tests/components/test_remember_the_milk.py | 38 +++++- 3 files changed, 161 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index 4a788297c60..aa3ca4b4543 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -15,7 +15,8 @@ import json import voluptuous as vol from homeassistant.config import load_yaml_config_file -from homeassistant.const import (CONF_API_KEY, STATE_OK, CONF_TOKEN, CONF_NAME) +from homeassistant.const import (CONF_API_KEY, STATE_OK, CONF_TOKEN, + CONF_NAME, CONF_ID) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -31,6 +32,10 @@ DEFAULT_NAME = DOMAIN GROUP_NAME_RTM = 'remember the milk accounts' CONF_SHARED_SECRET = 'shared_secret' +CONF_ID_MAP = 'id_map' +CONF_LIST_ID = 'list_id' +CONF_TIMESERIES_ID = 'timeseries_id' +CONF_TASK_ID = 'task_id' RTM_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, @@ -44,9 +49,15 @@ CONFIG_SCHEMA = vol.Schema({ CONFIG_FILE_NAME = '.remember_the_milk.conf' SERVICE_CREATE_TASK = 'create_task' +SERVICE_COMPLETE_TASK = 'complete_task' SERVICE_SCHEMA_CREATE_TASK = vol.Schema({ vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_ID): cv.string, +}) + +SERVICE_SCHEMA_COMPLETE_TASK = vol.Schema({ + vol.Required(CONF_ID): cv.string, }) @@ -84,10 +95,14 @@ def _create_instance(hass, account_name, api_key, shared_secret, entity = RememberTheMilk(account_name, api_key, shared_secret, token, stored_rtm_config) component.add_entity(entity) - hass.services.async_register( + hass.services.register( DOMAIN, '{}_create_task'.format(account_name), entity.create_task, description=descriptions.get(SERVICE_CREATE_TASK), schema=SERVICE_SCHEMA_CREATE_TASK) + hass.services.register( + DOMAIN, '{}_complete_task'.format(account_name), entity.complete_task, + description=descriptions.get(SERVICE_COMPLETE_TASK), + schema=SERVICE_SCHEMA_COMPLETE_TASK) def _register_new_account(hass, account_name, api_key, shared_secret, @@ -168,8 +183,7 @@ class RememberTheMilkConfiguration(object): def set_token(self, profile_name, token): """Store a new server token for a profile.""" - if profile_name not in self._config: - self._config[profile_name] = dict() + self._initialize_profile(profile_name) self._config[profile_name][CONF_TOKEN] = token self.save_config() @@ -181,6 +195,44 @@ class RememberTheMilkConfiguration(object): self._config.pop(profile_name, None) self.save_config() + def _initialize_profile(self, profile_name): + """Initialize the data structures for a profile.""" + if profile_name not in self._config: + self._config[profile_name] = dict() + if CONF_ID_MAP not in self._config[profile_name]: + self._config[profile_name][CONF_ID_MAP] = dict() + + def get_rtm_id(self, profile_name, hass_id): + """Get the rtm ids for a home assistant task id. + + The id of a rtm tasks consists of the tuple: + list id, timeseries id and the task id. + """ + self._initialize_profile(profile_name) + ids = self._config[profile_name][CONF_ID_MAP].get(hass_id) + if ids is None: + return None + return ids[CONF_LIST_ID], ids[CONF_TIMESERIES_ID], ids[CONF_TASK_ID] + + def set_rtm_id(self, profile_name, hass_id, list_id, time_series_id, + rtm_task_id): + """Add/Update the rtm task id for a home assistant task id.""" + self._initialize_profile(profile_name) + id_tuple = { + CONF_LIST_ID: list_id, + CONF_TIMESERIES_ID: time_series_id, + CONF_TASK_ID: rtm_task_id, + } + self._config[profile_name][CONF_ID_MAP][hass_id] = id_tuple + self.save_config() + + def delete_rtm_id(self, profile_name, hass_id): + """Delete a key mapping.""" + self._initialize_profile(profile_name) + if hass_id in self._config[profile_name][CONF_ID_MAP]: + del self._config[profile_name][CONF_ID_MAP][hass_id] + self.save_config() + class RememberTheMilk(Entity): """MVP implementation of an interface to Remember The Milk.""" @@ -225,19 +277,65 @@ class RememberTheMilk(Entity): import rtmapi try: - task_name = call.data.get('name') + task_name = call.data.get(CONF_NAME) + hass_id = call.data.get(CONF_ID) + rtm_id = None + if hass_id is not None: + rtm_id = self._rtm_config.get_rtm_id(self._name, hass_id) result = self._rtm_api.rtm.timelines.create() timeline = result.timeline.value - self._rtm_api.rtm.tasks.add( - timeline=timeline, name=task_name, parse='1') - _LOGGER.debug('created new task "%s" in account %s', - task_name, self.name) + + if hass_id is None or rtm_id is None: + result = self._rtm_api.rtm.tasks.add( + timeline=timeline, name=task_name, parse='1') + _LOGGER.debug('created new task "%s" in account %s', + task_name, self.name) + self._rtm_config.set_rtm_id(self._name, + hass_id, + result.list.id, + result.list.taskseries.id, + result.list.taskseries.task.id) + else: + self._rtm_api.rtm.tasks.setName(name=task_name, + list_id=rtm_id[0], + taskseries_id=rtm_id[1], + task_id=rtm_id[2], + timeline=timeline) + _LOGGER.debug('updated task with id "%s" in account ' + '%s to name %s', + hass_id, self.name, task_name) except rtmapi.RtmRequestFailedException as rtm_exception: _LOGGER.error('Error creating new Remember The Milk task for ' 'account %s: %s', self._name, rtm_exception) return False return True + def complete_task(self, call): + """Complete a task that was previously created by this component.""" + import rtmapi + + hass_id = call.data.get(CONF_ID) + rtm_id = self._rtm_config.get_rtm_id(self._name, hass_id) + if rtm_id is None: + _LOGGER.error('Could not find task with id %s in account %s. ' + 'So task could not be closed.', + hass_id, self._name) + return False + try: + result = self._rtm_api.rtm.timelines.create() + timeline = result.timeline.value + self._rtm_api.rtm.tasks.complete(list_id=rtm_id[0], + taskseries_id=rtm_id[1], + task_id=rtm_id[2], + timeline=timeline) + self._rtm_config.delete_rtm_id(self._name, hass_id) + _LOGGER.debug('Completed task with id %s in account %s', + hass_id, self._name) + except rtmapi.RtmRequestFailedException as rtm_exception: + _LOGGER.error('Error creating new Remember The Milk task for ' + 'account %s: %s', self._name, rtm_exception) + return True + @property def name(self): """Return the name of the device.""" diff --git a/homeassistant/components/remember_the_milk/services.yaml b/homeassistant/components/remember_the_milk/services.yaml index ebf242013f1..74a2c3a4d4f 100644 --- a/homeassistant/components/remember_the_milk/services.yaml +++ b/homeassistant/components/remember_the_milk/services.yaml @@ -1,9 +1,24 @@ # Describes the format for available Remember The Milk services create_task: - description: Create a new task in your Remember The Milk account + description: > + Create (or update) a new task in your Remember The Milk account. If you want to update a task + later on, you have to set an "id" when creating the task. + Note: Updating a tasks does not support the smart syntax. fields: name: description: name of the new task, you can use the smart syntax here - example: 'do this ^today #from_hass' \ No newline at end of file + example: 'do this ^today #from_hass' + + id: + description: (optional) identifier for the task you're creating, can be used to update or complete the task later on + example: myid + +complete_task: + description: Complete a tasks that was privously created. + + fields: + id: + description: identifier that was defined when creating the task + example: myid \ No newline at end of file diff --git a/tests/components/test_remember_the_milk.py b/tests/components/test_remember_the_milk.py index b59c840d765..65e7cd73c1f 100644 --- a/tests/components/test_remember_the_milk.py +++ b/tests/components/test_remember_the_milk.py @@ -1,6 +1,7 @@ """Tests for the Remember The Milk component.""" import logging +import json import unittest from unittest.mock import patch, mock_open, Mock @@ -19,7 +20,16 @@ class TestConfiguration(unittest.TestCase): self.hass = get_test_home_assistant() self.profile = "myprofile" self.token = "mytoken" - self.json_string = '{"myprofile": {"token": "mytoken"}}' + self.json_string = json.dumps( + {"myprofile": { + "token": "mytoken", + "id_map": {"1234": { + "list_id": "0", + "timeseries_id": "1", + "task_id": "2" + }} + } + }) def tearDown(self): """Exit home assistant.""" @@ -47,3 +57,29 @@ class TestConfiguration(unittest.TestCase): patch("os.path.isfile", Mock(return_value=True)): config = rtm.RememberTheMilkConfiguration(self.hass) self.assertIsNotNone(config) + + def test_id_map(self): + """Test the hass to rtm task is mapping.""" + hass_id = "hass-id-1234" + list_id = "mylist" + timeseries_id = "my_timeseries" + rtm_id = "rtm-id-4567" + with patch("builtins.open", mock_open()), \ + patch("os.path.isfile", Mock(return_value=False)): + config = rtm.RememberTheMilkConfiguration(self.hass) + + self.assertEqual(None, config.get_rtm_id(self.profile, hass_id)) + config.set_rtm_id(self.profile, hass_id, list_id, timeseries_id, + rtm_id) + self.assertEqual((list_id, timeseries_id, rtm_id), + config.get_rtm_id(self.profile, hass_id)) + config.delete_rtm_id(self.profile, hass_id) + self.assertEqual(None, config.get_rtm_id(self.profile, hass_id)) + + def test_load_key_map(self): + """Test loading an existing key map from the file.""" + with patch("builtins.open", mock_open(read_data=self.json_string)), \ + patch("os.path.isfile", Mock(return_value=True)): + config = rtm.RememberTheMilkConfiguration(self.hass) + self.assertEqual(('0', '1', '2',), + config.get_rtm_id(self.profile, "1234"))