Opportunities, Tasks and ToDoist for SugarCRM — part II

Subject

Well, there was the part I, so it’s time to write part II. Hre I’ll focus on the integration with the ToDoIst site (https://todoist.com) using their REST API. Here is the link to the documentation: https://developer.todoist.com/rest/v8/

Symptoms

Actually, I wrote a draft of this article a long time ago and haven’t touched my CRM application for a while. During this time my VPS server had to be reinstalled, sources restored from the backup, etc. And when I started to use it again I realized that I’m not able to create new Tasks manually. Tasks get created and stored in the database, but the page doesn’t refresh properly and in fact displays an empty HTML. That was how I learned about changes in ToDoIst API and this required a major rework on my end.  So there is now a new shiny version of the ToDoIst integration implementation to create new ToDoIst tasks when creating or updating Tasks in CRM, both manually and through the workflow.
Here is my goal – on every task creation, be it manually or via a workflow (see my previous post Opportunities, Tasks and ToDoist for SugarCRM – part I) I would like to create a new Task in my ToDoist account. When this Task gets updated or closed in SuiteCRM this has to be also synched with the ToDoist task.

Platform/Tools

There was one big change, I moved to SuiteCRM. This was a relatively simple action, just run the upgrade script and you are done. There were just very few non-upgrade-safe things, like custom logic hooks and I had to fix them. So now it is SuiteCRM Version 7.1.5.
Update: In the meantime, I have upgraded to Version 7.7.9. It was a seamless upgrade, I went the path to download the respective release upgrade packages from the official site and to apply them one by one using SuiteCRM upgrade wizard. My PHP version is currently 5.4.4-14.

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

 

 

Comments

comments

Leave a Reply