{"action":"created","comment":{"url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/comments/49694268","id":49694268,"diff_hunk":"@@ -0,0 +1,562 @@\n+/**\n+ * Copyright ©2015. The Regents of the University of California (Regents). All Rights Reserved.\n+ *\n+ * Permission to use, copy, modify, and distribute this software and its documentation\n+ * for educational, research, and not-for-profit purposes, without fee and without a\n+ * signed licensing agreement, is hereby granted, provided that the above copyright\n+ * notice, this paragraph and the following two paragraphs appear in all copies,\n+ * modifications, and distributions.\n+ *\n+ * Contact The Office of Technology Licensing, UC Berkeley, 2150 Shattuck Avenue,\n+ * Suite 510, Berkeley, CA 94720-1620, (510) 643-7201, otl@berkeley.edu,\n+ * http://ipira.berkeley.edu/industry-info for commercial licensing opportunities.\n+ *\n+ * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,\n+ * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF\n+ * THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED\n+ * OF THE POSSIBILITY OF SUCH DAMAGE.\n+ *\n+ * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE\n+ * SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED\n+ * \"AS IS\". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,\n+ * ENHANCEMENTS, OR MODIFICATIONS.\n+ */\n+\n+var _ = require('lodash');\n+var async = require('async');\n+var EventEmitter = require('events');\n+var moment = require('moment-timezone');\n+\n+var ActivitiesAPI = require('col-activities');\n+var CourseAPI = require('col-course');\n+var CollabosphereConstants = require('col-core/lib/constants');\n+var DB = require('col-core/lib/db');\n+var EmailUtil = require('col-core/lib/email');\n+var log = require('col-core/lib/logger')('col-activities/notifications');\n+var UsersAPI = require('col-users');\n+var UserConstants = require('col-users/lib/constants');\n+\n+\n+var emitter = module.exports = new EventEmitter();\n+var EVENT_NAMES = module.exports.EVENT_NAMES = {\n+  'COURSE_COLLECTED': 'courseCollected',\n+  'COURSES_COLLECTED': 'coursesCollected'\n+};\n+\n+/**\n+ * Run through all the active courses and send out the daily notification email\n+ */\n+var collect = module.exports.collect = function() {\n+  var start = Date.now();\n+\n+  // 1. Get all the active courses\n+  CourseAPI.getCourses(false, function(err, courses) {\n+    if (err) {\n+        return log.error({'err': err}, 'Unable to retrieve the courses when sending notifications');\n+    }\n+\n+    // 2. Iterate through all the courses\n+    async.eachSeries(courses, collectCourse, function(err) {\n+      if (err) {\n+          return log.error({'err': err}, 'Unable to retrieve the courses when sending notifications');\n+      }\n+\n+      log.info('Sent emails for all courses, took %d ms', Date.now() - start);\n+\n+      emitter.emit(EVENT_NAMES.COURSES_COLLECTED);\n+    });\n+  });\n+};\n+\n+/**\n+ * Send out the daily notifications for a specific course.\n+ *\n+ * Note that this method is only made publicly available so it can be called from the integration\n+ * tests. Do not call this method directly.\n+ *\n+ * @param  {Course}         course          The course for which to send out the daily notifications\n+ * @param  {Function}       callback        Standard callback function\n+ * @api private\n+ */\n+var collectCourse = module.exports.collectCourse = function(course, callback) {\n+  var start = Date.now();\n+\n+  // 1. Get all the data for this course\n+  getCourseData(course, function(err, data) {\n+    if (err) {\n+      // Don't pass the error up\n+      emitter.emit(EVENT_NAMES.COURSE_COLLECTED, course);\n+      return callback();\n+    }\n+\n+    // If there's no course data than we can return early\n+    if (_.isEmpty(data.assets) && _.isEmpty(data.whiteboards)) {\n+      emitter.emit(EVENT_NAMES.COURSE_COLLECTED, course);\n+      return callback();\n+    }\n+\n+    // 2. Iterate through all the users in this course and send each user an e-mail\n+    var userIterator = handleUser.bind(null, course, data);\n+    async.eachSeries(data.users, userIterator, function() {\n+      log.info('Sent emails for course %d, took %d ms', course.id, Date.now() - start);\n+\n+      emitter.emit(EVENT_NAMES.COURSE_COLLECTED, course);\n+      return callback();\n+    });\n+  });\n+};\n+\n+/**\n+ * Send notifications for a user in a course.\n+ *\n+ * Note that this function won't pass up any errors.\n+ *\n+ * @param  {Course}     course        The course for which to send out the daily notifications\n+ * @param  {Object}     data          The relevant data in the given course\n+ * @param  {User}       user          The user to notify\n+ * @param  {Function}   callback      Standard callback function\n+ * @api private\n+ */\n+var handleUser = function(course, data, user, callback) {\n+  // Ignore users that don't have an email address\n+  if (!user.canvas_email) {\n+      return callback();\n+  }\n+\n+  // Get all the activities for the user\n+  var activities = getActivitiesForUser(data, user);\n+\n+  // If nothing noteworthy happened for this user, then they should not get an email\n+  if (_.isEmpty(activities)) {\n+    return callback();\n+  }\n+\n+  var subject = getSubject(course, activities, user);\n+\n+  // The data that's used in the daily email template\n+  var data = {\n+    'activities': activities,\n+    'getSummaryActors': getSummaryActors,\n+  };\n+  EmailUtil.sendEmail(subject, user, course, data, 'daily', function(err) {\n+    // Don't pass errors up as otherwise all the following users would be ignored.\n+    // The error itself is logged in the sendEmail implementation\n+    return callback();\n+  });\n+};\n+\n+/**\n+ * Get the subject for the daily email\n+ *\n+ * @param  {Course}                                 course        The course for which to send out the daily notifications\n+ * @param  {(AssetActivity|WhiteboardActivity)[]}   activities    The set of activities for which to generate an email subject\n+ * @param  {User}                                   currentUser   The user to whom the email will be sent\n+ * @return {String}                                               An appropriate email subject\n+ * @api private\n+ */\n+var getSubject = function(course, activities, currentUser) {\n+  var subject = null;\n+  if (activities.length === 1) {\n+    var activity = activities[0];\n+    if (activity.type === 'asset_comment') {\n+      var commenters = _.pluck(activity.asset.comments, 'user');\n+      var summaryActors = getSummaryActors(commenters, currentUser);\n+      subject = summaryActors + ' commented on your asset ‘' + activity.asset.title + '’';\n+    } else if (activity.type === 'asset_comment_reply') {\n+      var commenters = _.pluck(activity.asset.comments, 'user');\n+      var summaryActors = getSummaryActors(commenters, currentUser);\n+      var comment = activity.asset.comments[0].body;\n+      if (comment.length > 40) {\n+        comment = comment.substring(0, 40) + '...';\n+      }\n+      subject = summaryActors + ' replied to your comment ‘' + comment + '’';\n+    } else if (activity.type === 'whiteboard_chat') {\n+      var commenters = _.pluck(activity.whiteboard.chats, 'user');\n+      var summaryActors = getSummaryActors(commenters, currentUser);\n+      subject = summaryActors + ' commented on your whiteboard ‘' + activity.whiteboard.title + '’';\n+    }\n+\n+  // Default to a generic subject title if there's more than 1 activity\n+  // If the canvas instance for this course has no name, we change the subject slightly as the\n+  // \"from name\" already contains \"Asset Library and Whiteboards\"\n+  } else {\n+    subject = 'Today\\'s activity in the Asset Library and Whiteboards';\n+    if (!course.canvas.name) {\n+      subject = 'Today\\'s activity'\n+    }\n+  }\n+\n+  return subject;\n+};\n+\n+/* Activity aggregation */\n+\n+/**\n+ * Get the relevant activities for a user\n+ *\n+ * @param  {Object}       data    The relevant course data\n+ * @param  {User}         user    The user for which to get the activities\n+ * @return {Activity[]}           A set of activities for which the user can be notified\n+ * @api private\n+ */\n+var getActivitiesForUser = function(data, user) {\n+  // This variable will contain all activities that can be emailed to the user\n+  var emailActivities = [];\n+\n+  // Asset comments\n+  _.each(data.assets, function(asset) {\n+    // Whether the user owns the asset\n+    var isOwner = asset.users[user.id];\n+\n+    // All comments made by other users\n+    var otherComments = _.filter(asset.comments, function(comment) {\n+      return (comment.user_id !== user.id);\n+    });\n+\n+    // All replies made to the current user\n+    var replies = _.filter(asset.comments, function(comment) {\n+      return (comment.parent && comment.parent.user_id === user.id);\n+    });\n+\n+    // All replies made to the current user by other users\n+    var otherReplies = _.filter(replies, function(reply) {\n+      return (reply.user_id !== user.id);\n+    });\n+\n+    // If we own the asset, we want all the new comments (that weren't made by ourselves)\n+    if (isOwner && !_.isEmpty(otherComments)) {\n+      emailActivities.push(new AssetActivity(asset, 'asset_comment'));\n+    }\n+\n+    // If there's a reply on a comment of ours we only want those replies\n+    else if (!_.isEmpty(replies) && !_.isEmpty(otherReplies)) {\n+      emailActivities.push(new AssetActivity(asset, 'asset_comment_reply', replies));\n+    }\n+  });\n+\n+  // Add whiteboards that the user is a member of which has new chat messages\n+  _.each(data.whiteboards, function(whiteboard) {\n+    var otherChatMessages = _.filter(whiteboard.chats, function(chat) {\n+      return (chat.user_id !== user.id);\n+    });\n+\n+    if (_.find(whiteboard.users, {'id': user.id}) && !_.isEmpty(otherChatMessages)) {\n+      emailActivities.push(new WhiteboardActivity(whiteboard))\n+    }\n+  });\n+\n+  return _.chain(emailActivities)\n+    .sortByOrder(['lastActivity'], ['desc'])\n+    .value();\n+\n+};\n+\n+/**\n+ * An Asset activity\n+ *\n+ * @param {Asset}       asset       The asset on which there's some activity\n+ * @param {String}      type        One of `asset_comment` or `asset_comment_reply`\n+ * @param {Comment[]}   [replies]   In case it's an `asset_comment_reply` activity, this should be the set of comments that are replies to a comment of the current user\n+ */\n+var AssetActivity = function(asset, type, replies) {\n+  // Take a copy of the asset\n+  asset = _.extend({}, asset);\n+\n+  if (type === 'asset_comment_reply') {\n+    asset.comments = replies;\n+  }\n+\n+  // Flatten the comments\n+  var comments = replies || asset.comments;\n+  var commentsById = {};\n+  _.each(comments, function(comment) {\n+    commentsById[comment.id] = _.extend({}, comment);\n+    if (comment.parent) {\n+      commentsById[comment.parent.id] = _.extend({}, comment.parent);\n+    }\n+  });\n+  comments = _.values(commentsById);\n+\n+  // Order the comments from newest to oldest\n+  comments.sort(function(a, b) {\n+    return b.id - a.id;\n+  });\n+\n+  // Extract the top-level comments\n+  var commentsTree = [];\n+  for (var i = 0; i < comments.length; i++) {\n+    var comment = comments[i];\n+    if (!comment.parent_id) {\n+      comment.level = 0;\n+      commentsTree.unshift(comment);\n+\n+      // Find all replies for the current comment\n+      for (var r = 0; r < comments.length; r++) {\n+        var reply = comments[r];\n+        if (reply.parent_id === comment.id) {\n+          reply.level = 1;\n+          commentsTree.splice(1, 0, reply);\n+        }\n+      }\n+    }\n+  }\n+\n+  asset.comments = commentsTree;\n+\n+  var that = {\n+    'type':  type,\n+    'lastActivity': comments[0].created_at,\n+    'asset': asset\n+  };\n+\n+  return that;\n+};\n+\n+/**\n+ * A whiteboard activity\n+ *\n+ * @param {Whiteboard}    whiteboard    The whiteboard for which there's an activity\n+ */\n+var WhiteboardActivity = function(whiteboard) {\n+  var that = {\n+    'type': 'whiteboard_chat',\n+    'lastActivity': _.chain(whiteboard.chats).sortBy('created_at').last().value().created_at,\n+    'whiteboard': whiteboard\n+  };\n+\n+  return that;\n+};\n+\n+/**\n+ * Given a set of users and the current user, get a string representation for those users.\n+ *\n+ * For example,\n+ *   - [Jack]                   results in: \"Jack\"\n+ *   - [Jack, Jill]             results in: \"Jack and Jill\"\n+ *   - [Jack, Jill, John]       results in: \"Jack, Jill and 1 other\"\n+ *   - [Jack, Jill, John, Dave] results in: \"Jack, Jill and 2 others\"\n+ *\n+ * @param  {User[]}   users         A set of users\n+ * @param  {User}     currentUser   The current user\n+ * @return {String}                 A string representation of the set of users\n+ * @api private\n+ */\n+var getSummaryActors = function(users, currentUser) {\n+  var actors = _.chain(users)\n+    .uniq('id')\n+    .filter(function(user) {\n+      return (user.id !== currentUser.id);\n+    })\n+    .value()\n+\n+  if (actors.length === 1) {\n+    return actors[0].canvas_full_name\n+  } else if (actors.length === 2) {\n+    return actors[0].canvas_full_name + ' and ' + actors[1].canvas_full_name\n+  } else if (actors.length === 3) {\n+    return actors[0].canvas_full_name + ', ' + actors[1].canvas_full_name + ' and 1 other'\n+  } else {\n+    var nOthers = actors.length - 2;\n+    return actors[0].canvas_full_name + ', ' + actors[1].canvas_full_name + ' and ' + nOthers + ' others';\n+  }\n+};\n+\n+\n+/* Data retrieval */\n+\n+\n+/**\n+ * Get the course data that had any activity in the last day. This will include:\n+ *  - assets that were commented on\n+ *  - assets were a comment was placed on a reply\n+ *  - whiteboards that received chat messages\n+ *\n+ * @param  {Context}    course                        The course for which to get the course data\n+ * @param  {Function}   callback                      Standard callback\n+ * @param  {Object}     callback.err                  An error object, if any\n+ * @param  {Object}     callback.data                 The relevant daily data in the course\n+ * @param  {Object}     callback.data.users           All the users in the course (including those that weren't active)\n+ * @param  {Object}     callback.data.assets          Assets with recent comments\n+ * @param  {Object}     callback.data.whiteboards     Whiteboards with recent chat messages","path":"node_modules/col-activities/lib/notifications/daily.js","position":381,"original_position":381,"commit_id":"29346a9bac52bf4dde8a3ffcc86c876ee433d23a","original_commit_id":"29346a9bac52bf4dde8a3ffcc86c876ee433d23a","user":{"login":"nicolaasmatthijs","id":109850,"avatar_url":"https://avatars.githubusercontent.com/u/109850?v=3","gravatar_id":"","url":"https://api.github.com/users/nicolaasmatthijs","html_url":"https://github.com/nicolaasmatthijs","followers_url":"https://api.github.com/users/nicolaasmatthijs/followers","following_url":"https://api.github.com/users/nicolaasmatthijs/following{/other_user}","gists_url":"https://api.github.com/users/nicolaasmatthijs/gists{/gist_id}","starred_url":"https://api.github.com/users/nicolaasmatthijs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nicolaasmatthijs/subscriptions","organizations_url":"https://api.github.com/users/nicolaasmatthijs/orgs","repos_url":"https://api.github.com/users/nicolaasmatthijs/repos","events_url":"https://api.github.com/users/nicolaasmatthijs/events{/privacy}","received_events_url":"https://api.github.com/users/nicolaasmatthijs/received_events","type":"User","site_admin":false},"body":"Might want to specify that the recent chat messages are also included in this","created_at":"2016-01-14T07:20:17Z","updated_at":"2016-01-14T07:20:17Z","html_url":"https://github.com/ets-berkeley-edu/collabosphere/pull/118#discussion_r49694268","pull_request_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118","_links":{"self":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/comments/49694268"},"html":{"href":"https://github.com/ets-berkeley-edu/collabosphere/pull/118#discussion_r49694268"},"pull_request":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118"}}},"pull_request":{"url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118","id":55023763,"html_url":"https://github.com/ets-berkeley-edu/collabosphere/pull/118","diff_url":"https://github.com/ets-berkeley-edu/collabosphere/pull/118.diff","patch_url":"https://github.com/ets-berkeley-edu/collabosphere/pull/118.patch","issue_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/118","number":118,"state":"open","locked":false,"title":"Email notifications","user":{"login":"simong","id":92180,"avatar_url":"https://avatars.githubusercontent.com/u/92180?v=3","gravatar_id":"","url":"https://api.github.com/users/simong","html_url":"https://github.com/simong","followers_url":"https://api.github.com/users/simong/followers","following_url":"https://api.github.com/users/simong/following{/other_user}","gists_url":"https://api.github.com/users/simong/gists{/gist_id}","starred_url":"https://api.github.com/users/simong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simong/subscriptions","organizations_url":"https://api.github.com/users/simong/orgs","repos_url":"https://api.github.com/users/simong/repos","events_url":"https://api.github.com/users/simong/events{/privacy}","received_events_url":"https://api.github.com/users/simong/received_events","type":"User","site_admin":false},"body":"This will send out daily email notifications by using the SendGrid API.\r\nCurrently it covers:\r\n * New comments on your assets\r\n * New replies on your comments\r\n * New chat messages on your whiteboards\r\n\r\nIt also sends an email when somebody gets added to a whiteboard","created_at":"2016-01-04T21:23:08Z","updated_at":"2016-01-14T07:20:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"74394859056cc50f0f1688ac37879fb4e34504ad","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118/commits","review_comments_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118/comments","review_comment_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/118/comments","statuses_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/statuses/29346a9bac52bf4dde8a3ffcc86c876ee433d23a","head":{"label":"ets-berkeley-edu:notifications","ref":"notifications","sha":"29346a9bac52bf4dde8a3ffcc86c876ee433d23a","user":{"login":"ets-berkeley-edu","id":283538,"avatar_url":"https://avatars.githubusercontent.com/u/283538?v=3","gravatar_id":"","url":"https://api.github.com/users/ets-berkeley-edu","html_url":"https://github.com/ets-berkeley-edu","followers_url":"https://api.github.com/users/ets-berkeley-edu/followers","following_url":"https://api.github.com/users/ets-berkeley-edu/following{/other_user}","gists_url":"https://api.github.com/users/ets-berkeley-edu/gists{/gist_id}","starred_url":"https://api.github.com/users/ets-berkeley-edu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ets-berkeley-edu/subscriptions","organizations_url":"https://api.github.com/users/ets-berkeley-edu/orgs","repos_url":"https://api.github.com/users/ets-berkeley-edu/repos","events_url":"https://api.github.com/users/ets-berkeley-edu/events{/privacy}","received_events_url":"https://api.github.com/users/ets-berkeley-edu/received_events","type":"Organization","site_admin":false},"repo":{"id":30094168,"name":"collabosphere","full_name":"ets-berkeley-edu/collabosphere","owner":{"login":"ets-berkeley-edu","id":283538,"avatar_url":"https://avatars.githubusercontent.com/u/283538?v=3","gravatar_id":"","url":"https://api.github.com/users/ets-berkeley-edu","html_url":"https://github.com/ets-berkeley-edu","followers_url":"https://api.github.com/users/ets-berkeley-edu/followers","following_url":"https://api.github.com/users/ets-berkeley-edu/following{/other_user}","gists_url":"https://api.github.com/users/ets-berkeley-edu/gists{/gist_id}","starred_url":"https://api.github.com/users/ets-berkeley-edu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ets-berkeley-edu/subscriptions","organizations_url":"https://api.github.com/users/ets-berkeley-edu/orgs","repos_url":"https://api.github.com/users/ets-berkeley-edu/repos","events_url":"https://api.github.com/users/ets-berkeley-edu/events{/privacy}","received_events_url":"https://api.github.com/users/ets-berkeley-edu/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ets-berkeley-edu/collabosphere","description":"","fork":false,"url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere","forks_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/forks","keys_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/teams","hooks_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/hooks","issue_events_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/events{/number}","events_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/events","assignees_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/assignees{/user}","branches_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/branches{/branch}","tags_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/tags","blobs_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/refs{/sha}","trees_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/statuses/{sha}","languages_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/languages","stargazers_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/stargazers","contributors_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/contributors","subscribers_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/subscribers","subscription_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/subscription","commits_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/commits{/sha}","git_commits_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/commits{/sha}","comments_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/comments{/number}","issue_comment_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/comments{/number}","contents_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/contents/{+path}","compare_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/merges","archive_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/downloads","issues_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues{/number}","pulls_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls{/number}","milestones_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/milestones{/number}","notifications_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/labels{/name}","releases_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/releases{/id}","created_at":"2015-01-30T22:46:30Z","updated_at":"2015-10-30T14:05:00Z","pushed_at":"2016-01-04T21:23:32Z","git_url":"git://github.com/ets-berkeley-edu/collabosphere.git","ssh_url":"git@github.com:ets-berkeley-edu/collabosphere.git","clone_url":"https://github.com/ets-berkeley-edu/collabosphere.git","svn_url":"https://github.com/ets-berkeley-edu/collabosphere","homepage":null,"size":4257,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":7,"forks":0,"open_issues":7,"watchers":0,"default_branch":"master"}},"base":{"label":"ets-berkeley-edu:master","ref":"master","sha":"30b13ad5c5baecc9925627f5ed7e58c3a2ff6ce6","user":{"login":"ets-berkeley-edu","id":283538,"avatar_url":"https://avatars.githubusercontent.com/u/283538?v=3","gravatar_id":"","url":"https://api.github.com/users/ets-berkeley-edu","html_url":"https://github.com/ets-berkeley-edu","followers_url":"https://api.github.com/users/ets-berkeley-edu/followers","following_url":"https://api.github.com/users/ets-berkeley-edu/following{/other_user}","gists_url":"https://api.github.com/users/ets-berkeley-edu/gists{/gist_id}","starred_url":"https://api.github.com/users/ets-berkeley-edu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ets-berkeley-edu/subscriptions","organizations_url":"https://api.github.com/users/ets-berkeley-edu/orgs","repos_url":"https://api.github.com/users/ets-berkeley-edu/repos","events_url":"https://api.github.com/users/ets-berkeley-edu/events{/privacy}","received_events_url":"https://api.github.com/users/ets-berkeley-edu/received_events","type":"Organization","site_admin":false},"repo":{"id":30094168,"name":"collabosphere","full_name":"ets-berkeley-edu/collabosphere","owner":{"login":"ets-berkeley-edu","id":283538,"avatar_url":"https://avatars.githubusercontent.com/u/283538?v=3","gravatar_id":"","url":"https://api.github.com/users/ets-berkeley-edu","html_url":"https://github.com/ets-berkeley-edu","followers_url":"https://api.github.com/users/ets-berkeley-edu/followers","following_url":"https://api.github.com/users/ets-berkeley-edu/following{/other_user}","gists_url":"https://api.github.com/users/ets-berkeley-edu/gists{/gist_id}","starred_url":"https://api.github.com/users/ets-berkeley-edu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ets-berkeley-edu/subscriptions","organizations_url":"https://api.github.com/users/ets-berkeley-edu/orgs","repos_url":"https://api.github.com/users/ets-berkeley-edu/repos","events_url":"https://api.github.com/users/ets-berkeley-edu/events{/privacy}","received_events_url":"https://api.github.com/users/ets-berkeley-edu/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ets-berkeley-edu/collabosphere","description":"","fork":false,"url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere","forks_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/forks","keys_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/teams","hooks_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/hooks","issue_events_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/events{/number}","events_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/events","assignees_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/assignees{/user}","branches_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/branches{/branch}","tags_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/tags","blobs_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/refs{/sha}","trees_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/statuses/{sha}","languages_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/languages","stargazers_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/stargazers","contributors_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/contributors","subscribers_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/subscribers","subscription_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/subscription","commits_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/commits{/sha}","git_commits_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/git/commits{/sha}","comments_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/comments{/number}","issue_comment_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/comments{/number}","contents_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/contents/{+path}","compare_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/merges","archive_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/downloads","issues_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues{/number}","pulls_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls{/number}","milestones_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/milestones{/number}","notifications_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/labels{/name}","releases_url":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/releases{/id}","created_at":"2015-01-30T22:46:30Z","updated_at":"2015-10-30T14:05:00Z","pushed_at":"2016-01-04T21:23:32Z","git_url":"git://github.com/ets-berkeley-edu/collabosphere.git","ssh_url":"git@github.com:ets-berkeley-edu/collabosphere.git","clone_url":"https://github.com/ets-berkeley-edu/collabosphere.git","svn_url":"https://github.com/ets-berkeley-edu/collabosphere","homepage":null,"size":4257,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":7,"forks":0,"open_issues":7,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118"},"html":{"href":"https://github.com/ets-berkeley-edu/collabosphere/pull/118"},"issue":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/118"},"comments":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/issues/118/comments"},"review_comments":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118/comments"},"review_comment":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/pulls/118/commits"},"statuses":{"href":"https://api.github.com/repos/ets-berkeley-edu/collabosphere/statuses/29346a9bac52bf4dde8a3ffcc86c876ee433d23a"}}}}