{"id":505,"date":"2017-09-06T18:43:16","date_gmt":"2017-09-06T14:43:16","guid":{"rendered":"http:\/\/www.bandidor.info\/wp\/?p=505"},"modified":"2017-09-06T20:12:46","modified_gmt":"2017-09-06T16:12:46","slug":"opportunities-tasks-and-todoist-for-sugarcrm-part-ii","status":"publish","type":"post","link":"https:\/\/www.bandidor.info\/wp\/?p=505","title":{"rendered":"Opportunities, Tasks and ToDoist for SugarCRM &#8212; part II"},"content":{"rendered":"<h1><strong><span style=\"font-family: verdana, geneva;\">Subject<\/span><\/strong><\/h1>\n<div>Well, there was the part I, so it&#8217;s time to write part II. Hre I&#8217;ll focus on the integration with the ToDoIst site <a href=\"https:\/\/todoist.com\" target=\"_blank\" rel=\"noopener\">(https:\/\/todoist.com<\/a>) using their REST API. Here is the link to the documentation:&nbsp;https:\/\/developer.todoist.com\/rest\/v8\/<\/div>\n<h1><strong><span style=\"font-family: verdana, geneva;\">Symptoms<\/span><\/strong><\/h1>\n<div>Actually, I wrote a draft of this article a long time ago and haven&#8217;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&#8217;m not able to create new Tasks manually. Tasks get created and stored in the database, but the page doesn&#8217;t&nbsp;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. &nbsp;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.<\/div>\n<div>Here is my goal &#8211; on every task creation, be it manually or via a workflow (see my previous post Opportunities, Tasks and ToDoist for SugarCRM &#8211; 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.<\/div>\n<h1><span style=\"font-family: verdana, geneva;\"><strong>Platform\/Tools<\/strong><\/span><\/h1>\n<div>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 <strong>SuiteCRM&nbsp;Version 7.1.5.<\/strong><\/div>\n<div><span style=\"text-decoration: underline;\"><strong>Update:&nbsp;<\/strong><\/span>In the meantime, I have upgraded to <b>Version 7.7.9. <\/b>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&nbsp;5.4.4-14.<\/div>\n<h1><strong><span style=\"font-family: verdana, geneva;\">Solution<\/span><\/strong><\/h1>\n<h2>Interaction with Todoist<\/h2>\n<p>This will consist of two parts &#8211; 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. &nbsp;<\/p>\n<h3>New customer module<\/h3>\n<p>I used SuiteCRM Studio&nbsp;to create my custom&nbsp;module named Bandigrator. Its function is to keep credentials to access my ToDoist account. It also allows editing this information. &nbsp;<\/p>\n<p>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.<\/p>\n<h3>Talking to the Todoist API<\/h3>\n<p>In my first implementation, I used the module created by these folks:&nbsp;<a href=\"http:\/\/kohanaphp.com\/license.html\">http:\/\/kohanaphp.com\/license.html.<\/a>&nbsp;With the changes in the ToDoist API I had to completely rewrite it. This is placed in the file <code>custom\/include\/Task\/todoist2.php<\/code>:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\nclass Todoist2 {\r\n\r\n...\r\n\r\npublic function get_projects(array $params = NULL);\r\n\r\n...\r\n\r\npublic function get_task($task_id){\r\n\r\n...\r\n\r\npublic function add_task($project, array $item)\r\n\r\n...\r\n\r\npublic function update_task($task_id, array $params)\r\n\r\n...\r\n\r\npublic function complete_task($task_id)\r\n\r\n...\r\n\r\n<\/pre>\n<p>It performs basic actions on the ToDoist API. I kept the amount of the code to the minimum so probably not all possible actions&nbsp;are implemented, but only these needed to support my Use Cases. I&#8217;m also very proud of creating unit test cases with PHPUnit for this module; they are in&nbsp;<code>tests\/custom\/include\/Task\/todoist2Test.php.<\/code><\/p>\n<h2>Plugging it in into the Task object lifecycle<\/h2>\n<h3>Custom hook<\/h3>\n<p>First entry point is in the file&nbsp;custom\/modules\/Tasks\/logic_hooks.php. For the <code>after_save<\/code> event, we will add our own handler, which will be called every time after a Task is saved.<\/p>\n<pre class=\"brush: php; highlight: [6]; title: ; notranslate\" title=\"\">\r\n\r\n...\r\n\r\n\/\/ position, file, function\r\n$hook_array&#x5B;'after_save'] = Array();\r\n$hook_array&#x5B;'after_save']&#x5B;] = Array(1, 'INSERT_INTO_PM_ENTRY_TABLE', 'modules\/PM_ProcessManager\/insertIntoPmEntryTable.php','insertIntoPmEntryTable', 'setPmEntryTable');\r\n$hook_array&#x5B;'after_save']&#x5B;] = Array(99, 'Sync SugarCRM tasks with Todoist.com', 'custom\/modules\/Tasks\/tasks_save.php','Tasks_Save', 'updateTask');\r\n...\r\n\r\n<\/pre>\n<p>Its implementation is in the file&nbsp;custom\/modules\/Tasks\/tasks_save.php:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n....\r\n\r\nclass Tasks_Save {\r\n    function updateTask(&amp;$bean, $event, $arguments) {\r\n        \/\/1. Check event conditions are met if any\r\n        if ($event == 'after_save' and $bean-&gt;assigned_user_id != '') {\r\n            $sync = new TaskSyncTodoist($bean-&gt;assigned_user_id);\r\n            if ($sync-&gt;is_todoist_sync()) {\r\n                $sync-&gt;updateTask($bean,$event, $arguments);\r\n\r\n...\r\n\r\n<\/pre>\n<p>Hook logic is very simple. It will check that certain conditions are met, create a new instance of the implementation class&nbsp;<code>TaskSyncTodoist<\/code>, verify that everything is set for the interaction with the ToDoist API via the call to the&nbsp;<code>is_todoist_sync()<\/code> function and then pass it over for execution via the <code>updateTask()<\/code> function where the actual interaction takes place.<\/p>\n<h3>Implementation<\/h3>\n<p>Here is where the music plays &#8211;&nbsp;custom\/include\/Task\/TaskSyncTodoist.php:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\nclass TaskSyncTodoist extends SugarBean {\r\n\r\n...\r\n\r\npublic function updateTask(&amp;$bean, $event, $arguments) {\r\n\r\n...\r\n\r\npublic function is_todoist_sync() {\r\n\r\n...\r\n\r\n<\/pre>\n<p>There are two public functions in this class.<code>&nbsp;is_todoist_sync()<\/code> 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.&nbsp;<code>updateTask()<\/code> 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&nbsp;<code>custom\/include\/Task\/todoist2.php. <\/code>For example:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n...\r\n$GLOBALS&#x5B;'log']-&gt;info(&quot;TaskSyncTodoist::updateTask: new status for task &quot; . $bean-&gt;id . &quot; will be &quot; . $bean-&gt;status);\r\ntry {\r\n    $this-&gt;completeTodoistTask($row&#x5B;'todoist_id']);\r\n    $GLOBALS&#x5B;'log']-&gt;info(&quot;TaskSyncTodoist::updateTask: todoist task &quot; . $row&#x5B;'todoist_id'] . &quot; completed&quot;);\r\n} catch (Exception $e) {\r\n    $GLOBALS&#x5B;'log']-&gt;info(&quot;TaskSyncTodoist::updateTask: something went wrong when talking to ToDoIst API to complete task: &quot; . $e-&gt;getMessage());\r\n\r\n...\r\n\r\n<\/pre>\n<p>This was missing in the first implementation and this was the reason for the empty html&nbsp;page while saving new Task.<\/p>\n<p>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 <code>todoist_status<\/code> column in the&nbsp;<code>bandi_todoist_task<\/code> table will be <em>NULL<\/em>.<\/p>\n<h1><strong><span style=\"font-family: verdana, geneva;\">Discussion<\/span><\/strong><\/h1>\n<p>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&#8217;t seem to be good; I couldn&#8217;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.<\/p>\n<h1><strong><span style=\"font-family: verdana, geneva;\">Caveats<\/span><\/strong><\/h1>\n<p>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&#8217;m glad of my unit tests and hope that I fished out all possible regressions \ud83d\ude09<\/p>\n<h1><strong><span style=\"font-family: verdana, geneva;\">Bonus &#8211; PHPUnit and SugarCRM\/SuiteCRM<\/span><\/strong><\/h1>\n<p>This is a real challenge &#8211; how to use PHPHUnit for the first time? Here are examples. Run them from within tests folder of your SuiteCRM\/SugarCRM installation.<\/p>\n<p>This will test the ToDoist API wrapper calss:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n\r\nphp phpunit.php custom\/include\/Task\/todoist2Test.php\r\n\r\n<\/pre>\n<p>This will test the hook implementation class:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n\r\nphp phpunit.php custom\/include\/Task\/TaskSyncTodoistTest.php\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Subject Well, there was the part I, so it&#8217;s time to write part II. Hre I&#8217;ll focus on the integration with the ToDoIst site (https:\/\/todoist.com) using their REST API. Here is the link to the documentation:&nbsp;https:\/\/developer.todoist.com\/rest\/v8\/ Symptoms Actually, I wrote a draft of this article a long time ago and haven&#8217;t touched my CRM application&#8230;<\/p>\n","protected":false},"author":1,"featured_media":702,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[12,28],"tags":[25,29,33],"class_list":["post-505","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sugarcrm","category-suitecrm","tag-sugarcrm","tag-suitecrm","tag-todoist"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.bandidor.info\/wp\/wp-content\/uploads\/2015\/03\/20170730_200957.jpg","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2EszU-89","_links":{"self":[{"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/posts\/505","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=505"}],"version-history":[{"count":28,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/posts\/505\/revisions"}],"predecessor-version":[{"id":701,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/posts\/505\/revisions\/701"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=\/wp\/v2\/media\/702"}],"wp:attachment":[{"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=505"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=505"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bandidor.info\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=505"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}