{"action":"created","comment":{"url":"https://api.github.com/repos/akvo/akvo-flow/pulls/comments/28399061","id":28399061,"diff_hunk":"@@ -0,0 +1,242 @@\n+/*\n+ *  Copyright (C) 2015 Stichting Akvo (Akvo Foundation)\n+ *\n+ *  This file is part of Akvo FLOW.\n+ *\n+ *  Akvo FLOW is free software: you can redistribute it and modify it under the terms of\n+ *  the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,\n+ *  either version 3 of the License or any later version.\n+ *\n+ *  Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n+ *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n+ *  See the GNU Affero General Public License included below for more details.\n+ *\n+ *  The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.\n+ */\n+\n+package org.akvo.flow.events;\n+\n+import static com.gallatinsystems.common.util.MemCacheUtils.initCache;\n+import static org.akvo.flow.events.EventUtils.getEventAndActionType;\n+import static org.akvo.flow.events.EventUtils.newContext;\n+import static org.akvo.flow.events.EventUtils.newEntity;\n+import static org.akvo.flow.events.EventUtils.newEvent;\n+import static org.akvo.flow.events.EventUtils.newSource;\n+import static org.akvo.flow.events.EventUtils.populateEntityProperties;\n+\n+import java.io.IOException;\n+import java.io.OutputStreamWriter;\n+import java.io.StringWriter;\n+import java.net.HttpURLConnection;\n+import java.net.MalformedURLException;\n+import java.net.URL;\n+import java.util.Date;\n+import java.util.HashMap;\n+import java.util.Map;\n+import java.util.logging.Level;\n+import java.util.logging.Logger;\n+\n+import net.sf.jsr107cache.Cache;\n+\n+import org.akvo.flow.events.EventUtils.Action;\n+import org.akvo.flow.events.EventUtils.EventTypes;\n+import org.akvo.flow.events.EventUtils.Key;\n+import org.akvo.flow.events.EventUtils.Prop;\n+import org.codehaus.jackson.map.ObjectMapper;\n+import org.springframework.security.core.Authentication;\n+import org.springframework.security.core.context.SecurityContextHolder;\n+\n+import com.gallatinsystems.common.util.PropertyUtil;\n+import com.gallatinsystems.framework.dao.BaseDAO;\n+import com.google.appengine.api.datastore.DeleteContext;\n+import com.google.appengine.api.datastore.Entity;\n+import com.google.appengine.api.datastore.PostDelete;\n+import com.google.appengine.api.datastore.PostPut;\n+import com.google.appengine.api.datastore.PutContext;\n+import com.google.appengine.api.utils.SystemProperty;\n+\n+public class EventLogger {\n+    private static Logger logger = Logger.getLogger(EventLogger.class.getName());\n+\n+    private static final long MIN_TIME_DIFF = 60000; // 60 seconds\n+\n+    private void sendNotification() {\n+        try {\n+            String urlPath = PropertyUtil.getProperty(Prop.EVENT_NOTIFICATION);\n+\n+            if (urlPath == null || urlPath.trim().length() == 0) {\n+                logger.log(Level.SEVERE, \"Event notification URL not present in appengine-web.xml\");\n+                return;\n+            }\n+\n+            URL url = new URL(urlPath.trim());\n+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n+            connection.setDoOutput(true);\n+            connection.setRequestMethod(\"POST\");\n+            connection.setRequestProperty(\"Content-Type\", \"application/json\");\n+\n+            Map<String, String> messageMap = new HashMap<String, String>();\n+            String appId = SystemProperty.applicationId.get();\n+            messageMap.put(Key.APP_ID, appId);\n+            messageMap.put(Key.URL, appId + \".appspot.com\");\n+\n+            ObjectMapper m = new ObjectMapper();\n+            OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());\n+            m.writeValue(writer, messageMap);\n+            writer.close();\n+            if (connection.getResponseCode() != HttpURLConnection.HTTP_NO_CONTENT) {\n+                logger.log(Level.SEVERE, \"Unified log notification failed with status code: \"\n+                        + connection.getResponseCode());\n+            }\n+        } catch (MalformedURLException e) {\n+            logger.log(Level.SEVERE,\n+                    \"Unified log notification failed with malformed URL exception\", e);\n+        } catch (IOException e) {\n+            logger.log(Level.SEVERE, \"Unified log notification failed with IO exception\", e);\n+        } catch (Exception e) {\n+            logger.log(Level.SEVERE, \"Unified log notification failed with error\", e);\n+        }\n+    }\n+\n+    /*\n+     * Notify the log that a new event is ready to be downloaded.\n+     */\n+    private void notifyLog() {\n+        Cache cache = initCache(60 * 60); // 1 hour\n+        if (cache == null) {\n+            // cache is not accessible, so we will notify anyway\n+            logger.log(Level.WARNING,\n+                    \"cache not accessible, but still sending notification to unified log\");\n+            sendNotification();\n+            return;\n+        }\n+\n+        if (cache.containsKey(Action.UNIFIED_LOG_NOTIFIED)) {\n+            // check if the time the last notification was send is less than one minute ago\n+            Date cacheDate = (Date) cache.get(Action.UNIFIED_LOG_NOTIFIED);\n+            Date nowDate = new Date();\n+            Long deltaMils = nowDate.getTime() - cacheDate.getTime();\n+            if (deltaMils < MIN_TIME_DIFF) {\n+                // it is too soon, so don't send the notification\n+                return;\n+            }\n+        }\n+        // if we are here, either the key is not in the cache, or it is too old\n+        // in both cases, we send the notification and add a fresh value to the cache\n+        sendNotification();\n+        cache.put(Action.UNIFIED_LOG_NOTIFIED, new Date());\n+    }\n+\n+    private void storeEvent(Map<String, Object> event, Date timestamp) {\n+        try {\n+            ObjectMapper m = new ObjectMapper();\n+            StringWriter w = new StringWriter();\n+            m.writeValue(w, event);\n+            BaseDAO<EventQueue> eventDao = new BaseDAO<EventQueue>(EventQueue.class);\n+            EventQueue eventQ = new EventQueue();\n+            eventQ.setCreatedDateTime(timestamp);\n+            eventQ.setPayload(w.toString());\n+            eventDao.save(eventQ);\n+            notifyLog();\n+        } catch (Exception e) {\n+            logger.log(Level.SEVERE, \"could not store \" + event.get(\"eventType\")\n+                    + \" event. Error: \" + e.toString());\n+        }\n+    }\n+\n+    @PostPut(kinds = {\n+            \"SurveyGroup\", \"Survey\", \"QuestionGroup\", \"Question\", \"SurveyInstance\",\n+            \"QuestionAnswerStore\", \"SurveyedLocale\", \"DeviceFiles\"\n+    })\n+    void logPut(PutContext context) {\n+\n+        try {\n+            if (!\"true\".equals(PropertyUtil.getProperty(Prop.ENABLE_CHANGE_EVENTS))) {\n+                return;\n+            }\n+\n+            Entity current = context.getCurrentElement();\n+\n+            // determine type of event and type of action\n+            EventTypes types = getEventAndActionType(current.getKey().getKind());\n+\n+            // determine if this entity was created or updated\n+            Date lastUpdateDatetime = (Date) current.getProperty(Prop.LAST_UPDATE_DATE_TIME);\n+            Date createdDateTime = (Date) current.getProperty(Prop.CREATED_DATE_TIME);\n+            String actionType = createdDateTime.equals(lastUpdateDatetime) ? Action.CREATED\n+                    : Action.UPDATED;\n+\n+            // create event source\n+            // get the authentication information. This seems to contain the userId, but\n+            // according to the documentation, should hold the 'password'\n+            final Authentication authentication = SecurityContextHolder.getContext()\n+                    .getAuthentication();\n+\n+            Map<String, Object> eventSource = newSource(authentication.getPrincipal());\n+\n+            Date timestamp = (Date) context.getCurrentElement().getProperty(\n+                    Prop.LAST_UPDATE_DATE_TIME);\n+            // create event context map\n+            Map<String, Object> eventContext = newContext(timestamp, eventSource);\n+\n+            // create event entity\n+            Map<String, Object> eventEntity = newEntity(types.type, context\n+                    .getCurrentElement().getKey().getId());\n+\n+            populateEntityProperties(types.type, context.getCurrentElement(), eventEntity);\n+\n+            // create event\n+            Map<String, Object> event = newEvent(SystemProperty.applicationId.get(),\n+                    types.action + actionType, eventEntity, eventContext);\n+\n+            // store it\n+            storeEvent(event, timestamp);\n+\n+        } catch (Exception e) {\n+            logger.log(Level.SEVERE, \"Could not handle datastore put event: \" + e.getMessage());","path":"GAE/src/org/akvo/flow/events/EventLogger.java","position":196,"original_position":196,"commit_id":"f683a029222e6bba20d5c5ae432b83aa28622480","original_commit_id":"f683a029222e6bba20d5c5ae432b83aa28622480","user":{"login":"iperdomo","id":178474,"avatar_url":"https://avatars.githubusercontent.com/u/178474?v=3","gravatar_id":"","url":"https://api.github.com/users/iperdomo","html_url":"https://github.com/iperdomo","followers_url":"https://api.github.com/users/iperdomo/followers","following_url":"https://api.github.com/users/iperdomo/following{/other_user}","gists_url":"https://api.github.com/users/iperdomo/gists{/gist_id}","starred_url":"https://api.github.com/users/iperdomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iperdomo/subscriptions","organizations_url":"https://api.github.com/users/iperdomo/orgs","repos_url":"https://api.github.com/users/iperdomo/repos","events_url":"https://api.github.com/users/iperdomo/events{/privacy}","received_events_url":"https://api.github.com/users/iperdomo/received_events","type":"User","site_admin":false},"body":"idem ^^^","created_at":"2015-04-15T07:35:41Z","updated_at":"2015-04-15T07:35:41Z","html_url":"https://github.com/akvo/akvo-flow/pull/1240#discussion_r28399061","pull_request_url":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240","_links":{"self":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/comments/28399061"},"html":{"href":"https://github.com/akvo/akvo-flow/pull/1240#discussion_r28399061"},"pull_request":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240"}}},"pull_request":{"url":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240","id":33263394,"html_url":"https://github.com/akvo/akvo-flow/pull/1240","diff_url":"https://github.com/akvo/akvo-flow/pull/1240.diff","patch_url":"https://github.com/akvo/akvo-flow/pull/1240.patch","issue_url":"https://api.github.com/repos/akvo/akvo-flow/issues/1240","number":1240,"state":"open","locked":false,"title":"Feature/1130 send log events","user":{"login":"jonase","id":101023,"avatar_url":"https://avatars.githubusercontent.com/u/101023?v=3","gravatar_id":"","url":"https://api.github.com/users/jonase","html_url":"https://github.com/jonase","followers_url":"https://api.github.com/users/jonase/followers","following_url":"https://api.github.com/users/jonase/following{/other_user}","gists_url":"https://api.github.com/users/jonase/gists{/gist_id}","starred_url":"https://api.github.com/users/jonase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonase/subscriptions","organizations_url":"https://api.github.com/users/jonase/orgs","repos_url":"https://api.github.com/users/jonase/repos","events_url":"https://api.github.com/users/jonase/events{/privacy}","received_events_url":"https://api.github.com/users/jonase/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-04-14T14:35:37Z","updated_at":"2015-04-15T07:35:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"51cc3e91b697e496e8dbf65e45e4ff2616691a6b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240/commits","review_comments_url":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240/comments","review_comment_url":"https://api.github.com/repos/akvo/akvo-flow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/akvo/akvo-flow/issues/1240/comments","statuses_url":"https://api.github.com/repos/akvo/akvo-flow/statuses/f683a029222e6bba20d5c5ae432b83aa28622480","head":{"label":"akvo:feature/1130-send-log-events","ref":"feature/1130-send-log-events","sha":"f683a029222e6bba20d5c5ae432b83aa28622480","user":{"login":"akvo","id":159843,"avatar_url":"https://avatars.githubusercontent.com/u/159843?v=3","gravatar_id":"","url":"https://api.github.com/users/akvo","html_url":"https://github.com/akvo","followers_url":"https://api.github.com/users/akvo/followers","following_url":"https://api.github.com/users/akvo/following{/other_user}","gists_url":"https://api.github.com/users/akvo/gists{/gist_id}","starred_url":"https://api.github.com/users/akvo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akvo/subscriptions","organizations_url":"https://api.github.com/users/akvo/orgs","repos_url":"https://api.github.com/users/akvo/repos","events_url":"https://api.github.com/users/akvo/events{/privacy}","received_events_url":"https://api.github.com/users/akvo/received_events","type":"Organization","site_admin":false},"repo":{"id":3843585,"name":"akvo-flow","full_name":"akvo/akvo-flow","owner":{"login":"akvo","id":159843,"avatar_url":"https://avatars.githubusercontent.com/u/159843?v=3","gravatar_id":"","url":"https://api.github.com/users/akvo","html_url":"https://github.com/akvo","followers_url":"https://api.github.com/users/akvo/followers","following_url":"https://api.github.com/users/akvo/following{/other_user}","gists_url":"https://api.github.com/users/akvo/gists{/gist_id}","starred_url":"https://api.github.com/users/akvo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akvo/subscriptions","organizations_url":"https://api.github.com/users/akvo/orgs","repos_url":"https://api.github.com/users/akvo/repos","events_url":"https://api.github.com/users/akvo/events{/privacy}","received_events_url":"https://api.github.com/users/akvo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akvo/akvo-flow","description":"Akvo FLOW","fork":false,"url":"https://api.github.com/repos/akvo/akvo-flow","forks_url":"https://api.github.com/repos/akvo/akvo-flow/forks","keys_url":"https://api.github.com/repos/akvo/akvo-flow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akvo/akvo-flow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akvo/akvo-flow/teams","hooks_url":"https://api.github.com/repos/akvo/akvo-flow/hooks","issue_events_url":"https://api.github.com/repos/akvo/akvo-flow/issues/events{/number}","events_url":"https://api.github.com/repos/akvo/akvo-flow/events","assignees_url":"https://api.github.com/repos/akvo/akvo-flow/assignees{/user}","branches_url":"https://api.github.com/repos/akvo/akvo-flow/branches{/branch}","tags_url":"https://api.github.com/repos/akvo/akvo-flow/tags","blobs_url":"https://api.github.com/repos/akvo/akvo-flow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akvo/akvo-flow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akvo/akvo-flow/git/refs{/sha}","trees_url":"https://api.github.com/repos/akvo/akvo-flow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akvo/akvo-flow/statuses/{sha}","languages_url":"https://api.github.com/repos/akvo/akvo-flow/languages","stargazers_url":"https://api.github.com/repos/akvo/akvo-flow/stargazers","contributors_url":"https://api.github.com/repos/akvo/akvo-flow/contributors","subscribers_url":"https://api.github.com/repos/akvo/akvo-flow/subscribers","subscription_url":"https://api.github.com/repos/akvo/akvo-flow/subscription","commits_url":"https://api.github.com/repos/akvo/akvo-flow/commits{/sha}","git_commits_url":"https://api.github.com/repos/akvo/akvo-flow/git/commits{/sha}","comments_url":"https://api.github.com/repos/akvo/akvo-flow/comments{/number}","issue_comment_url":"https://api.github.com/repos/akvo/akvo-flow/issues/comments{/number}","contents_url":"https://api.github.com/repos/akvo/akvo-flow/contents/{+path}","compare_url":"https://api.github.com/repos/akvo/akvo-flow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akvo/akvo-flow/merges","archive_url":"https://api.github.com/repos/akvo/akvo-flow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akvo/akvo-flow/downloads","issues_url":"https://api.github.com/repos/akvo/akvo-flow/issues{/number}","pulls_url":"https://api.github.com/repos/akvo/akvo-flow/pulls{/number}","milestones_url":"https://api.github.com/repos/akvo/akvo-flow/milestones{/number}","notifications_url":"https://api.github.com/repos/akvo/akvo-flow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akvo/akvo-flow/labels{/name}","releases_url":"https://api.github.com/repos/akvo/akvo-flow/releases{/id}","created_at":"2012-03-27T12:45:18Z","updated_at":"2015-04-10T14:45:24Z","pushed_at":"2015-04-15T05:46:53Z","git_url":"git://github.com/akvo/akvo-flow.git","ssh_url":"git@github.com:akvo/akvo-flow.git","clone_url":"https://github.com/akvo/akvo-flow.git","svn_url":"https://github.com/akvo/akvo-flow","homepage":"","size":155725,"stargazers_count":28,"watchers_count":28,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"open_issues_count":228,"forks":13,"open_issues":228,"watchers":28,"default_branch":"develop"}},"base":{"label":"akvo:develop","ref":"develop","sha":"b5f66ceea49585bda8e5918e5a8fb888fccce415","user":{"login":"akvo","id":159843,"avatar_url":"https://avatars.githubusercontent.com/u/159843?v=3","gravatar_id":"","url":"https://api.github.com/users/akvo","html_url":"https://github.com/akvo","followers_url":"https://api.github.com/users/akvo/followers","following_url":"https://api.github.com/users/akvo/following{/other_user}","gists_url":"https://api.github.com/users/akvo/gists{/gist_id}","starred_url":"https://api.github.com/users/akvo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akvo/subscriptions","organizations_url":"https://api.github.com/users/akvo/orgs","repos_url":"https://api.github.com/users/akvo/repos","events_url":"https://api.github.com/users/akvo/events{/privacy}","received_events_url":"https://api.github.com/users/akvo/received_events","type":"Organization","site_admin":false},"repo":{"id":3843585,"name":"akvo-flow","full_name":"akvo/akvo-flow","owner":{"login":"akvo","id":159843,"avatar_url":"https://avatars.githubusercontent.com/u/159843?v=3","gravatar_id":"","url":"https://api.github.com/users/akvo","html_url":"https://github.com/akvo","followers_url":"https://api.github.com/users/akvo/followers","following_url":"https://api.github.com/users/akvo/following{/other_user}","gists_url":"https://api.github.com/users/akvo/gists{/gist_id}","starred_url":"https://api.github.com/users/akvo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akvo/subscriptions","organizations_url":"https://api.github.com/users/akvo/orgs","repos_url":"https://api.github.com/users/akvo/repos","events_url":"https://api.github.com/users/akvo/events{/privacy}","received_events_url":"https://api.github.com/users/akvo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akvo/akvo-flow","description":"Akvo FLOW","fork":false,"url":"https://api.github.com/repos/akvo/akvo-flow","forks_url":"https://api.github.com/repos/akvo/akvo-flow/forks","keys_url":"https://api.github.com/repos/akvo/akvo-flow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akvo/akvo-flow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akvo/akvo-flow/teams","hooks_url":"https://api.github.com/repos/akvo/akvo-flow/hooks","issue_events_url":"https://api.github.com/repos/akvo/akvo-flow/issues/events{/number}","events_url":"https://api.github.com/repos/akvo/akvo-flow/events","assignees_url":"https://api.github.com/repos/akvo/akvo-flow/assignees{/user}","branches_url":"https://api.github.com/repos/akvo/akvo-flow/branches{/branch}","tags_url":"https://api.github.com/repos/akvo/akvo-flow/tags","blobs_url":"https://api.github.com/repos/akvo/akvo-flow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akvo/akvo-flow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akvo/akvo-flow/git/refs{/sha}","trees_url":"https://api.github.com/repos/akvo/akvo-flow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akvo/akvo-flow/statuses/{sha}","languages_url":"https://api.github.com/repos/akvo/akvo-flow/languages","stargazers_url":"https://api.github.com/repos/akvo/akvo-flow/stargazers","contributors_url":"https://api.github.com/repos/akvo/akvo-flow/contributors","subscribers_url":"https://api.github.com/repos/akvo/akvo-flow/subscribers","subscription_url":"https://api.github.com/repos/akvo/akvo-flow/subscription","commits_url":"https://api.github.com/repos/akvo/akvo-flow/commits{/sha}","git_commits_url":"https://api.github.com/repos/akvo/akvo-flow/git/commits{/sha}","comments_url":"https://api.github.com/repos/akvo/akvo-flow/comments{/number}","issue_comment_url":"https://api.github.com/repos/akvo/akvo-flow/issues/comments{/number}","contents_url":"https://api.github.com/repos/akvo/akvo-flow/contents/{+path}","compare_url":"https://api.github.com/repos/akvo/akvo-flow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akvo/akvo-flow/merges","archive_url":"https://api.github.com/repos/akvo/akvo-flow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akvo/akvo-flow/downloads","issues_url":"https://api.github.com/repos/akvo/akvo-flow/issues{/number}","pulls_url":"https://api.github.com/repos/akvo/akvo-flow/pulls{/number}","milestones_url":"https://api.github.com/repos/akvo/akvo-flow/milestones{/number}","notifications_url":"https://api.github.com/repos/akvo/akvo-flow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akvo/akvo-flow/labels{/name}","releases_url":"https://api.github.com/repos/akvo/akvo-flow/releases{/id}","created_at":"2012-03-27T12:45:18Z","updated_at":"2015-04-10T14:45:24Z","pushed_at":"2015-04-15T05:46:53Z","git_url":"git://github.com/akvo/akvo-flow.git","ssh_url":"git@github.com:akvo/akvo-flow.git","clone_url":"https://github.com/akvo/akvo-flow.git","svn_url":"https://github.com/akvo/akvo-flow","homepage":"","size":155725,"stargazers_count":28,"watchers_count":28,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":13,"mirror_url":null,"open_issues_count":228,"forks":13,"open_issues":228,"watchers":28,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240"},"html":{"href":"https://github.com/akvo/akvo-flow/pull/1240"},"issue":{"href":"https://api.github.com/repos/akvo/akvo-flow/issues/1240"},"comments":{"href":"https://api.github.com/repos/akvo/akvo-flow/issues/1240/comments"},"review_comments":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240/comments"},"review_comment":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/akvo/akvo-flow/pulls/1240/commits"},"statuses":{"href":"https://api.github.com/repos/akvo/akvo-flow/statuses/f683a029222e6bba20d5c5ae432b83aa28622480"}}}}