Subject
Symptoms
Platform/Tools
Solution
Interaction with Todoist
This will consist of two parts – custom module in SuiteCRM to keep access credentials for ToDoist and track the integration status and a helper module to implement API access from within my custom code in SuiteCRM.
New customer module
I used SuiteCRM Studio to create my custom module named Bandigrator. Its function is to keep credentials to access my ToDoist account. It also allows editing this information.
With the recent changes in ToDoist API I realized that it is now a unique token, which shall be used instead of email and password so I added one more field to the module and redeployed it. I also had to adjust Detail and Edit views to include this new field.
Talking to the Todoist API
In my first implementation, I used the module created by these folks: http://kohanaphp.com/license.html. With the changes in the ToDoist API I had to completely rewrite it. This is placed in the file custom/include/Task/todoist2.php
:
class Todoist2 { ... public function get_projects(array $params = NULL); ... public function get_task($task_id){ ... public function add_task($project, array $item) ... public function update_task($task_id, array $params) ... public function complete_task($task_id) ...
It performs basic actions on the ToDoist API. I kept the amount of the code to the minimum so probably not all possible actions are implemented, but only these needed to support my Use Cases. I’m also very proud of creating unit test cases with PHPUnit for this module; they are in tests/custom/include/Task/todoist2Test.php.
Plugging it in into the Task object lifecycle
Custom hook
First entry point is in the file custom/modules/Tasks/logic_hooks.php. For the after_save
event, we will add our own handler, which will be called every time after a Task is saved.
... // position, file, function $hook_array['after_save'] = Array(); $hook_array['after_save'][] = Array(1, 'INSERT_INTO_PM_ENTRY_TABLE', 'modules/PM_ProcessManager/insertIntoPmEntryTable.php','insertIntoPmEntryTable', 'setPmEntryTable'); $hook_array['after_save'][] = Array(99, 'Sync SugarCRM tasks with Todoist.com', 'custom/modules/Tasks/tasks_save.php','Tasks_Save', 'updateTask'); ...
Its implementation is in the file custom/modules/Tasks/tasks_save.php:
.... class Tasks_Save { function updateTask(&$bean, $event, $arguments) { //1. Check event conditions are met if any if ($event == 'after_save' and $bean->assigned_user_id != '') { $sync = new TaskSyncTodoist($bean->assigned_user_id); if ($sync->is_todoist_sync()) { $sync->updateTask($bean,$event, $arguments); ...
Hook logic is very simple. It will check that certain conditions are met, create a new instance of the implementation class TaskSyncTodoist
, verify that everything is set for the interaction with the ToDoist API via the call to the is_todoist_sync()
function and then pass it over for execution via the updateTask()
function where the actual interaction takes place.
Implementation
Here is where the music plays – custom/include/Task/TaskSyncTodoist.php:
class TaskSyncTodoist extends SugarBean { ... public function updateTask(&$bean, $event, $arguments) { ... public function is_todoist_sync() { ...
There are two public functions in this class. is_todoist_sync()
will check if everything is set for this user to interact with the ToDost API, in its current implementation it checks for the ToDoist token. updateTask()
will perform the needed action, i.e. create new, update or mark completed an existing ToDoist task. After changes in the ToDoist API this function had to be rewritten to support the new logic, in particular, to use one fixed API token instead of a combination of an email and password. An important change was also a careful handling of possible exceptions generated by the API wrapper in custom/include/Task/todoist2.php.
For example:
... $GLOBALS['log']->info("TaskSyncTodoist::updateTask: new status for task " . $bean->id . " will be " . $bean->status); try { $this->completeTodoistTask($row['todoist_id']); $GLOBALS['log']->info("TaskSyncTodoist::updateTask: todoist task " . $row['todoist_id'] . " completed"); } catch (Exception $e) { $GLOBALS['log']->info("TaskSyncTodoist::updateTask: something went wrong when talking to ToDoIst API to complete task: " . $e->getMessage()); ...
This was missing in the first implementation and this was the reason for the empty html page while saving new Task.
Also there some more code added to also record the last response from the ToDoist API in case of an error and the respective error code. In case of success, the todoist_status
column in the bandi_todoist_task
table will be NULL.
Discussion
Not much to discuss here, it is simple as it is. One possible improvement could be to create a link pointing back to the respective Task in SuiteCRM from the ToIst task; shall not be that complicated. On a contrary, the idea to have two ways communication doesn’t seem to be good; I couldn’t figure out a simple way of transferring Task updates in ToDoist back to Suite CRM. Only for SuiteCRM to periodically poll taks on ToDoist, but it hardly worths the trouble.
Caveats
Speaking of caveats, the biggest challenge was the changed API on the ToDoist site and issues with debugging. Many API functions use http POST and it was a little tricky. Still, I’m glad of my unit tests and hope that I fished out all possible regressions 😉
Bonus – PHPUnit and SugarCRM/SuiteCRM
This is a real challenge – how to use PHPHUnit for the first time? Here are examples. Run them from within tests folder of your SuiteCRM/SugarCRM installation.
This will test the ToDoist API wrapper calss:
php phpunit.php custom/include/Task/todoist2Test.php
This will test the hook implementation class:
php phpunit.php custom/include/Task/TaskSyncTodoistTest.php