{"action":"created","comment":{"url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/comments/87652509","pull_request_review_id":8260184,"id":87652509,"diff_hunk":"@@ -0,0 +1,432 @@\n+#!/usr/bin/env ruby\n+require File.expand_path('../../../pegasus/src/env', __FILE__)\n+require 'state_abbr'\n+require src_dir 'database'\n+require_relative('../../dashboard/config/environment')\n+require 'cdo/properties'\n+require 'json'\n+\n+DASHBOARD_REPORTING_DB_READONLY = sequel_connect(\n+  CDO.dashboard_reporting_db_reader, CDO.dashboard_reporting_db_reader)\n+\n+UPDATE_BATCH_SIZE = 100\n+\n+DASHBOARD_DB_NAME = \"dashboard_#{Rails.env}\"\n+\n+# List of valid PD courses. If more courses get added, need to update this list and also schema in Pardot. We\n+# need to filter here to known courses in the Pardot schema - we can't blindly pass values through\n+COURSE_LIST = \"'CS Fundamentals','CS in Algebra','CS in Science','CS Principles','Exploring Computer Science','CS Discoveries'\"\n+\n+# values of forms.kind field we care about\n+FORM_KINDS = %w(BringToSchool2013 CSEdWeekEvent2013 DistrictPartnerSubmission HelpUs2013 Petition K5OnlineProfessionalDevelopmentPostSurvey)\n+\n+# information about presence of which forms submitted by a user get recorded in which\n+# rollup field with which value\n+FORM_INFOS =\n+  [\n+    {kind: \"'CSEdWeekEvent2013'\", dest_field: \"hoc_organizer_years\", dest_value: \"'2013'\"},\n+    {kind: \"'HocSignup2014'\", dest_field: \"hoc_organizer_years\", dest_value: \"'2014'\"},\n+    {kind: \"'HocSignup2015'\", dest_field: \"hoc_organizer_years\", dest_value: \"'2015'\"},\n+    {kind: \"'HocSignup2016'\", dest_field: \"hoc_organizer_years\", dest_value: \"'2016'\"},\n+    {kind: \"'Petition'\", dest_field: \"roles\", dest_value: \"'Petition Signer'\"}\n+  ].freeze\n+\n+def log(s)\n+  puts s      # emit to stdout\n+  CDO.log.info s   # emit to log file\n+end\n+\n+def log_completion(start)\n+  log \"   Completed in #{Time.now - start} seconds\"\n+end\n+\n+def truncate_contact_rollups\n+  start = Time.now\n+  log \"Truncating contact_rollups\"\n+  PEGASUS_DB.run \"TRUNCATE TABLE contact_rollups\"\n+  log_completion(start)\n+end\n+\n+def insert_from_dashboard_contacts\n+  start = Time.now\n+  log \"Inserting teacher contacts and IP geo data from dashboard.users\"\n+  PEGASUS_DB.run \"\n+    INSERT INTO contact_rollups (email, name, dashboard_user_id, roles, city, state, postal_code, country)\n+    SELECT email COLLATE utf8_general_ci, name, users.id, 'Teacher', city, state, postal_code, country FROM #{DASHBOARD_DB_NAME}.users AS users\n+    LEFT OUTER JOIN #{DASHBOARD_DB_NAME}.user_geos AS user_geos ON user_geos.user_id = users.id\n+    WHERE users.user_type = 'teacher' AND LENGTH(email) > 0 AND user_geos.indexed_at IS NOT NULL\n+    ON DUPLICATE KEY UPDATE contact_rollups.name = VALUES(name), contact_rollups.dashboard_user_id = VALUES(dashboard_user_id), roles = VALUES(roles)\"\n+  log_completion(start)\n+end\n+\n+def insert_from_dashboard_pd_enrollments\n+  start = Time.now\n+  log \"Inserting contacts from dashboard.pd_enrollments\"\n+  PEGASUS_DB.run \"\n+    INSERT INTO contact_rollups (email, name)\n+    SELECT email, name FROM #{DASHBOARD_DB_NAME}.pd_enrollments AS pd_enrollments\n+    WHERE LENGTH(pd_enrollments.email) > 0\n+    ON DUPLICATE KEY UPDATE name = contact_rollups.name\"\n+  log_completion(start)\n+end\n+\n+def insert_from_pegasus_contacts\n+  start = Time.now\n+  log \"Inserting contacts from pegasus.contacts\"\n+  PEGASUS_DB.run \"\n+    INSERT INTO contact_rollups (email, opted_out, name)\n+    SELECT email, IF(unsubscribed_at IS null, null, true) AS opted_out, name FROM contacts WHERE LENGTH(email) > 0\n+    ON DUPLICATE KEY UPDATE contact_rollups.name = VALUES(name)\"\n+  log_completion(start)\n+end\n+\n+def insert_from_pegasus_forms\n+  start = Time.now\n+  log \"Inserting contacts and IP geo data from pegasus.forms\"\n+  PEGASUS_DB.run \"\n+    INSERT IGNORE INTO contact_rollups (email, name, city, state, postal_code, country)\n+    SELECT email, name, city, state, postal_code, country FROM forms\n+    LEFT OUTER JOIN form_geos on form_geos.form_id = forms.id\n+    WHERE form_geos.indexed_at IS NOT NULL\"\n+  log_completion(start)\n+end\n+\n+def update_roles\n+  start = Time.now\n+  log \"Updating Facilitator, Workshop Organizer, District Contact roles from dashboard.users\"\n+  append_to_role_list_from_permission(\"facilitator\", \"Facilitator\")\n+  append_to_role_list_from_permission(\"workshop_organizer\", \"Workshop Organizer\")\n+  append_to_role_list_from_permission(\"district_contact\", \"District Contact\")\n+  log \"   Completed in \" + ((Time.now) - start).to_s + \" seconds\"\n+\n+  start = Time.now\n+  log \"Updating Professional Learning Partner role\"\n+  append_plp_to_role_list\n+  log_completion(start)\n+end\n+\n+def append_to_role_list_from_permission(permission_name, dest_value)\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups\n+    INNER JOIN #{DASHBOARD_DB_NAME}.users AS users ON users.id = contact_rollups.dashboard_user_id\n+    INNER JOIN #{DASHBOARD_DB_NAME}.user_permissions AS user_permissions ON user_permissions.user_id = users.id\n+    SET roles = CONCAT(COALESCE(CONCAT(roles, ','), ''), '#{dest_value}')\n+    WHERE LENGTH(users.email) > 0\n+    AND user_permissions.permission = '#{permission_name}' AND contact_rollups.id > 0\"\n+end\n+\n+def append_to_list_field_from_form(form_kinds, dest_field, dest_value)\n+  start = Time.now\n+  log \"Appending '#{dest_field}' field with #{dest_value} from forms of kind #{form_kinds}\"\n+\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups\n+    INNER JOIN forms ON forms.email = contact_rollups.email\n+    SET #{dest_field} = CONCAT(COALESCE(CONCAT(#{dest_field}, ','), ''), #{dest_value})\n+    WHERE forms.kind IN (#{form_kinds}) AND contact_rollups.id > 0\"\n+  log_completion(start)\n+end\n+\n+def append_plp_to_role_list\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups\n+    INNER JOIN #{DASHBOARD_DB_NAME}.users AS users ON users.id = contact_rollups.dashboard_user_id\n+    INNER JOIN #{DASHBOARD_DB_NAME}.professional_learning_partners AS professional_learning_partners ON professional_learning_partners.contact_id = users.id\n+    SET roles = CONCAT(COALESCE(CONCAT(roles, ','), ''), 'PLP')\n+    WHERE LENGTH(users.email) > 0 AND contact_rollups.id > 0\"\n+end\n+\n+def update_courses_facilitated\n+  start = Time.now\n+  log \"Updating courses_facilitated\"\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups,\n+    (SELECT facilitator_id, GROUP_CONCAT(course) AS courses FROM\n+      (SELECT DISTINCT facilitator_id, course FROM #{DASHBOARD_DB_NAME}.pd_course_facilitators\n+        WHERE COURSE IN (#{COURSE_LIST})\n+        ORDER by 1,2\n+      ) q\n+      GROUP BY facilitator_id\n+    ) src\n+    SET contact_rollups.courses_facilitated = src.courses\n+    WHERE contact_rollups.dashboard_user_id = src.facilitator_id\"\n+  log_completion(start)\n+end\n+\n+def update_professional_learning_enrollment\n+  start = Time.now\n+  log \"Updating professional learning enrollment\"\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups,\n+      (SELECT user_id, GROUP_CONCAT(course) AS courses FROM\n+         (SELECT DISTINCT pd_enrollments.user_id, pd_workshops.course FROM #{DASHBOARD_DB_NAME}.pd_enrollments AS pd_enrollments\n+            INNER JOIN #{DASHBOARD_DB_NAME}.pd_workshops AS pd_workshops ON pd_workshops.id = pd_enrollments.pd_workshop_id\n+            WHERE course IN (#{COURSE_LIST})\n+            ORDER BY 1,2\n+         ) q\n+      GROUP by user_id\n+      ) src\n+    SET contact_rollups.professional_learning_enrolled = src.courses\n+    WHERE contact_rollups.dashboard_user_id = src.user_id\"\n+  log_completion(start)\n+end\n+\n+def update_professional_learning_attendance\n+  start = Time.now\n+  log \"Updating professional learning attendance\"\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups,\n+      (SELECT teacher_id, GROUP_CONCAT(course) as courses FROM\n+        (SELECT DISTINCT teacher_id, course FROM\n+          (SELECT pd_attendances.teacher_id, pd_workshops.course\n+            FROM #{DASHBOARD_DB_NAME}.pd_attendances AS pd_attendances\n+              INNER JOIN #{DASHBOARD_DB_NAME}.pd_sessions AS pd_sessions ON pd_sessions.id = pd_attendances.pd_session_id\n+              INNER JOIN #{DASHBOARD_DB_NAME}.pd_workshops AS pd_workshops ON pd_workshops.id = pd_sessions.pd_workshop_id\n+            WHERE course IN (#{COURSE_LIST})\n+            UNION\n+            SELECT teacher_id,\n+              CASE program_type\n+                WHEN 1 THEN 'CS in Science'\n+                WHEN 2 THEN 'CS in Algebra'\n+                WHEN 3 THEN 'Exploring Computer Science'\n+                WHEN 4 THEN 'CS Principles'\n+                WHEN 5 THEN 'CS Fundamentals'\n+              END AS course\n+            FROM #{DASHBOARD_DB_NAME}.workshop_attendance AS workshop_attendance\n+              INNER JOIN #{DASHBOARD_DB_NAME}.segments AS segments ON segments.id = workshop_attendance.segment_id\n+              INNER JOIN #{DASHBOARD_DB_NAME}.workshops AS workshops ON workshops.id = segments.workshop_id\n+            ) q1\n+          ORDER BY 1,2\n+          ) q2\n+        GROUP BY teacher_id\n+      ) src\n+    SET contact_rollups.professional_learning_attended = src.courses\n+    WHERE contact_rollups.dashboard_user_id = src.teacher_id\"\n+  log_completion(start)\n+end\n+\n+def mysql_multi_connection\n+  # return a connection with the MULTI_STATEMENTS flag set that allows multiple statements in one DB call\n+  Sequel.connect(CDO.pegasus_db_writer.sub('mysql:', 'mysql2:'), flags: ::Mysql2::Client::MULTI_STATEMENTS)\n+end\n+\n+def update_geo_data_from_forms(form_kind)\n+  start = Time.now\n+  log \"Updating geo data for form kind #{form_kind}\"\n+\n+  record_count = 0\n+  num_updated = 0\n+  num_parse_errors = 0\n+  update_batch = \"\"\n+  conn = mysql_multi_connection\n+  time_last_output = start\n+\n+  PEGASUS_DB[:forms].where(kind: form_kind).each do |form|\n+    record_count += 1\n+    begin\n+      data = JSON.parse(form[:data])\n+    rescue JSON::ParserError\n+      # we have just a couple of records with invalid JSON (duplicate fields), rescue and skip these\n+      num_parse_errors += 1\n+      next\n+    end\n+\n+    email = data['email_s'].presence\n+    next if email.nil?\n+\n+    # get user-supplied address/location data from form if present, from any of the differently-named location fields across all form kinds\n+    street_address = data['location_street_address_s'].presence || data['user_street_address_s'].presence\n+    city = data['location_city_s'].presence || data['user_city_s'].presence || data['teacher_city_s'].presence\n+    state = data['location_state_s'].presence || data['teacher_state_s'].presence\n+    if state.nil?\n+      # note that the 'user_state_s' record is in fact a *state code*, not a state name\n+      state_code = data['location_state_code_s'].presence || data['state_code_s'].presence || data['user_state_s'].presence\n+      state = get_us_state_from_abbr(abbr: state_code, include_dc: true).presence unless state_code.nil?\n+    end\n+    postal_code = data['location_postal_code_s'].presence || data['zip_code_s'].presence || data['user_postal_code_s'].presence\n+    country = data['location_country_s'].presence || data['country_s'].presence || data['teacher_country_s'].presence\n+\n+    # skip if this form data has no address/location info at all\n+    next if street_address.nil? && city.nil? && state.nil? && postal_code.nil? && country.nil?\n+\n+    # Truncate all fields to 255 chars. We have some data longer than 255 chars but it is all garbage that somebody typed.\n+    street_address = street_address[0...255] unless street_address.nil?\n+    city = city[0...255] unless city.nil?\n+    state = state[0...255] unless state.nil?\n+    postal_code = postal_code[0...255] unless postal_code.nil?\n+    country = country[0...255] unless country.nil?\n+\n+    # add to batch to update\n+    update_batch += conn[:contact_rollups].where(email: email).update_sql(\n+      street_address: street_address, city: city, state: state, postal_code: postal_code, country: country) + \";\"\n+\n+    num_updated += 1\n+\n+    if num_updated % UPDATE_BATCH_SIZE == 0\n+      begin\n+        conn.run update_batch\n+      rescue => e\n+        log \"Error: #{e}. Cmd: #{update_batch}\"\n+        raise\n+      end\n+\n+      # Discard the connection and get a new one after each batch. Currently, if you use multiple statements in a batch, the first call will succeed; the next call on\n+      # the same connection AFTER you do a multi-statement call will fail with a MySQL error that commands are out of order. The innertubes suggest that this may be a problem in mysql2 gem.\n+      # Fortunately, it is fairly cheap to get a new connection for each batch (time to get new connection is negligible compared to batch time).\n+      conn.disconnect\n+      conn = mysql_multi_connection\n+\n+      update_batch = \"\"\n+      if Time.now - time_last_output > 5\n+        log \"#{record_count} \"\n+        time_last_output = Time.now\n+      end\n+    end\n+  end\n+\n+  conn.run update_batch unless update_batch.empty?\n+  log \"- #{record_count} source records, #{num_updated} with geo data, #{num_parse_errors} skipped due to invalid JSON, #{Time.now - start} seconds\"\n+end\n+\n+def update_grades_taught\n+  start = Time.now\n+  log \"Updating grades taught from dashboard.users\"\n+  PEGASUS_DB.run \"\n+    UPDATE contact_rollups,\n+    (SELECT dashboard_user_id, GROUP_CONCAT(grade) as grades FROM\n+      (SELECT DISTINCT contact_rollups.dashboard_user_id, sections.grade from contact_rollups\n+        INNER JOIN #{DASHBOARD_DB_NAME}.sections AS sections on sections.user_id = contact_rollups.dashboard_user_id\n+        WHERE contact_rollups.dashboard_user_id is not null\n+        AND length(sections.grade) > 0\n+        ORDER by 1,2\n+      ) q\n+      GROUP BY dashboard_user_id\n+    ) src\n+    SET contact_rollups.grades_taught = src.grades\n+    WHERE contact_rollups.id > 0 AND (contact_rollups.dashboard_user_id = src.dashboard_user_id)\"\n+  log_completion(start)\n+end\n+\n+def update_ages_taught_for_age(age)\n+  start = Time.now\n+  log \"Updating ages taught for age #{age}\"\n+\n+  min_birthday_clause = \"\"\n+  max_birthday_clause = \"\"\n+  # for age = 21, include 21 and above\n+  min_birthday_clause = \"students.birthday > DATE_ADD(NOW(), INTERVAL -#{age + 1} YEAR) AND\" unless age >= 21\n+  # for age = 4, include 4 and below\n+  max_birthday_clause = \"students.birthday <= DATE_ADD(NOW(), INTERVAL -#{age} YEAR) AND\" unless age <= 4\n+\n+  sql = \"\n+    UPDATE contact_rollups, (\n+    SELECT DISTINCT sections.user_id AS teacher_user_id\n+    FROM #{DASHBOARD_DB_NAME}.users AS students\n+      INNER JOIN #{DASHBOARD_DB_NAME}.followers AS followers\n+        ON followers.student_user_id = students.id\n+      INNER JOIN #{DASHBOARD_DB_NAME}.sections AS sections\n+        ON sections.id = followers.section_id\n+    WHERE\n+      -- filter by student age\n+      #{min_birthday_clause}\n+      #{max_birthday_clause}\n+      -- filter out teachers who have joined section\n+      LENGTH(students.email) = 0","path":"bin/cron/build_contact_rollups","position":334,"original_position":334,"commit_id":"15cbfe24a2420ed7ab79d4d82d4e6dcc6717803c","original_commit_id":"15cbfe24a2420ed7ab79d4d82d4e6dcc6717803c","user":{"login":"jeremydstone","id":19172895,"avatar_url":"https://avatars.githubusercontent.com/u/19172895?v=3","gravatar_id":"","url":"https://api.github.com/users/jeremydstone","html_url":"https://github.com/jeremydstone","followers_url":"https://api.github.com/users/jeremydstone/followers","following_url":"https://api.github.com/users/jeremydstone/following{/other_user}","gists_url":"https://api.github.com/users/jeremydstone/gists{/gist_id}","starred_url":"https://api.github.com/users/jeremydstone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeremydstone/subscriptions","organizations_url":"https://api.github.com/users/jeremydstone/orgs","repos_url":"https://api.github.com/users/jeremydstone/repos","events_url":"https://api.github.com/users/jeremydstone/events{/privacy}","received_events_url":"https://api.github.com/users/jeremydstone/received_events","type":"User","site_admin":false},"body":"Yes, good point","created_at":"2016-11-11T20:20:08Z","updated_at":"2016-11-11T20:20:08Z","html_url":"https://github.com/code-dot-org/code-dot-org/pull/11698#discussion_r87652509","pull_request_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698","_links":{"self":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/comments/87652509"},"html":{"href":"https://github.com/code-dot-org/code-dot-org/pull/11698#discussion_r87652509"},"pull_request":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698"}}},"pull_request":{"url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698","id":92882422,"html_url":"https://github.com/code-dot-org/code-dot-org/pull/11698","diff_url":"https://github.com/code-dot-org/code-dot-org/pull/11698.diff","patch_url":"https://github.com/code-dot-org/code-dot-org/pull/11698.patch","issue_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/11698","number":11698,"state":"open","locked":false,"title":"Scripts to build contact rollups and sync into Pardot","user":{"login":"jeremydstone","id":19172895,"avatar_url":"https://avatars.githubusercontent.com/u/19172895?v=3","gravatar_id":"","url":"https://api.github.com/users/jeremydstone","html_url":"https://github.com/jeremydstone","followers_url":"https://api.github.com/users/jeremydstone/followers","following_url":"https://api.github.com/users/jeremydstone/following{/other_user}","gists_url":"https://api.github.com/users/jeremydstone/gists{/gist_id}","starred_url":"https://api.github.com/users/jeremydstone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeremydstone/subscriptions","organizations_url":"https://api.github.com/users/jeremydstone/orgs","repos_url":"https://api.github.com/users/jeremydstone/repos","events_url":"https://api.github.com/users/jeremydstone/events{/privacy}","received_events_url":"https://api.github.com/users/jeremydstone/received_events","type":"User","site_admin":false},"body":"This contains two scripts:\r\n\r\n- build_contact_rollups - script, eventually to be run as cron job, to build contact_rollups table. Originally PR'd in https://github.com/code-dot-org/code-dot-org/pull/11243. This PR contains changes from feedback in that PR including file name change.\r\n- pardot_import - does initial import from build_contact_rollups into Pardot. Does not currently do the ongoing delta sync operation. ","created_at":"2016-11-08T23:44:51Z","updated_at":"2016-11-11T20:20:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6bd58f7f6242f7f9b22baf8bac9259bddcce8c8c","assignee":{"login":"wjordan","id":56541,"avatar_url":"https://avatars.githubusercontent.com/u/56541?v=3","gravatar_id":"","url":"https://api.github.com/users/wjordan","html_url":"https://github.com/wjordan","followers_url":"https://api.github.com/users/wjordan/followers","following_url":"https://api.github.com/users/wjordan/following{/other_user}","gists_url":"https://api.github.com/users/wjordan/gists{/gist_id}","starred_url":"https://api.github.com/users/wjordan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wjordan/subscriptions","organizations_url":"https://api.github.com/users/wjordan/orgs","repos_url":"https://api.github.com/users/wjordan/repos","events_url":"https://api.github.com/users/wjordan/events{/privacy}","received_events_url":"https://api.github.com/users/wjordan/received_events","type":"User","site_admin":false},"assignees":[{"login":"wjordan","id":56541,"avatar_url":"https://avatars.githubusercontent.com/u/56541?v=3","gravatar_id":"","url":"https://api.github.com/users/wjordan","html_url":"https://github.com/wjordan","followers_url":"https://api.github.com/users/wjordan/followers","following_url":"https://api.github.com/users/wjordan/following{/other_user}","gists_url":"https://api.github.com/users/wjordan/gists{/gist_id}","starred_url":"https://api.github.com/users/wjordan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wjordan/subscriptions","organizations_url":"https://api.github.com/users/wjordan/orgs","repos_url":"https://api.github.com/users/wjordan/repos","events_url":"https://api.github.com/users/wjordan/events{/privacy}","received_events_url":"https://api.github.com/users/wjordan/received_events","type":"User","site_admin":false},{"login":"aoby","id":8405316,"avatar_url":"https://avatars.githubusercontent.com/u/8405316?v=3","gravatar_id":"","url":"https://api.github.com/users/aoby","html_url":"https://github.com/aoby","followers_url":"https://api.github.com/users/aoby/followers","following_url":"https://api.github.com/users/aoby/following{/other_user}","gists_url":"https://api.github.com/users/aoby/gists{/gist_id}","starred_url":"https://api.github.com/users/aoby/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aoby/subscriptions","organizations_url":"https://api.github.com/users/aoby/orgs","repos_url":"https://api.github.com/users/aoby/repos","events_url":"https://api.github.com/users/aoby/events{/privacy}","received_events_url":"https://api.github.com/users/aoby/received_events","type":"User","site_admin":false},{"login":"ashercodeorg","id":14206260,"avatar_url":"https://avatars.githubusercontent.com/u/14206260?v=3","gravatar_id":"","url":"https://api.github.com/users/ashercodeorg","html_url":"https://github.com/ashercodeorg","followers_url":"https://api.github.com/users/ashercodeorg/followers","following_url":"https://api.github.com/users/ashercodeorg/following{/other_user}","gists_url":"https://api.github.com/users/ashercodeorg/gists{/gist_id}","starred_url":"https://api.github.com/users/ashercodeorg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ashercodeorg/subscriptions","organizations_url":"https://api.github.com/users/ashercodeorg/orgs","repos_url":"https://api.github.com/users/ashercodeorg/repos","events_url":"https://api.github.com/users/ashercodeorg/events{/privacy}","received_events_url":"https://api.github.com/users/ashercodeorg/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698/commits","review_comments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698/comments","review_comment_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/comments{/number}","comments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/11698/comments","statuses_url":"https://api.github.com/repos/code-dot-org/code-dot-org/statuses/15cbfe24a2420ed7ab79d4d82d4e6dcc6717803c","head":{"label":"code-dot-org:pardot2","ref":"pardot2","sha":"15cbfe24a2420ed7ab79d4d82d4e6dcc6717803c","user":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"repo":{"id":24250009,"name":"code-dot-org","full_name":"code-dot-org/code-dot-org","owner":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/code-dot-org/code-dot-org","description":"The code powering code.org and studio.code.org","fork":false,"url":"https://api.github.com/repos/code-dot-org/code-dot-org","forks_url":"https://api.github.com/repos/code-dot-org/code-dot-org/forks","keys_url":"https://api.github.com/repos/code-dot-org/code-dot-org/keys{/key_id}","collaborators_url":"https://api.github.com/repos/code-dot-org/code-dot-org/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/code-dot-org/code-dot-org/teams","hooks_url":"https://api.github.com/repos/code-dot-org/code-dot-org/hooks","issue_events_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/events{/number}","events_url":"https://api.github.com/repos/code-dot-org/code-dot-org/events","assignees_url":"https://api.github.com/repos/code-dot-org/code-dot-org/assignees{/user}","branches_url":"https://api.github.com/repos/code-dot-org/code-dot-org/branches{/branch}","tags_url":"https://api.github.com/repos/code-dot-org/code-dot-org/tags","blobs_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/refs{/sha}","trees_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/trees{/sha}","statuses_url":"https://api.github.com/repos/code-dot-org/code-dot-org/statuses/{sha}","languages_url":"https://api.github.com/repos/code-dot-org/code-dot-org/languages","stargazers_url":"https://api.github.com/repos/code-dot-org/code-dot-org/stargazers","contributors_url":"https://api.github.com/repos/code-dot-org/code-dot-org/contributors","subscribers_url":"https://api.github.com/repos/code-dot-org/code-dot-org/subscribers","subscription_url":"https://api.github.com/repos/code-dot-org/code-dot-org/subscription","commits_url":"https://api.github.com/repos/code-dot-org/code-dot-org/commits{/sha}","git_commits_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/commits{/sha}","comments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/comments{/number}","issue_comment_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/comments{/number}","contents_url":"https://api.github.com/repos/code-dot-org/code-dot-org/contents/{+path}","compare_url":"https://api.github.com/repos/code-dot-org/code-dot-org/compare/{base}...{head}","merges_url":"https://api.github.com/repos/code-dot-org/code-dot-org/merges","archive_url":"https://api.github.com/repos/code-dot-org/code-dot-org/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/code-dot-org/code-dot-org/downloads","issues_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues{/number}","pulls_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls{/number}","milestones_url":"https://api.github.com/repos/code-dot-org/code-dot-org/milestones{/number}","notifications_url":"https://api.github.com/repos/code-dot-org/code-dot-org/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/code-dot-org/code-dot-org/labels{/name}","releases_url":"https://api.github.com/repos/code-dot-org/code-dot-org/releases{/id}","deployments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/deployments","created_at":"2014-09-20T00:46:11Z","updated_at":"2016-10-22T02:50:56Z","pushed_at":"2016-11-11T20:18:35Z","git_url":"git://github.com/code-dot-org/code-dot-org.git","ssh_url":"git@github.com:code-dot-org/code-dot-org.git","clone_url":"https://github.com/code-dot-org/code-dot-org.git","svn_url":"https://github.com/code-dot-org/code-dot-org","homepage":"http://code.org","size":5176232,"stargazers_count":148,"watchers_count":148,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":96,"mirror_url":null,"open_issues_count":78,"forks":96,"open_issues":78,"watchers":148,"default_branch":"staging"}},"base":{"label":"code-dot-org:staging","ref":"staging","sha":"112066128148a87c5ada191aca63e7d66b30947e","user":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"repo":{"id":24250009,"name":"code-dot-org","full_name":"code-dot-org/code-dot-org","owner":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/code-dot-org/code-dot-org","description":"The code powering code.org and studio.code.org","fork":false,"url":"https://api.github.com/repos/code-dot-org/code-dot-org","forks_url":"https://api.github.com/repos/code-dot-org/code-dot-org/forks","keys_url":"https://api.github.com/repos/code-dot-org/code-dot-org/keys{/key_id}","collaborators_url":"https://api.github.com/repos/code-dot-org/code-dot-org/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/code-dot-org/code-dot-org/teams","hooks_url":"https://api.github.com/repos/code-dot-org/code-dot-org/hooks","issue_events_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/events{/number}","events_url":"https://api.github.com/repos/code-dot-org/code-dot-org/events","assignees_url":"https://api.github.com/repos/code-dot-org/code-dot-org/assignees{/user}","branches_url":"https://api.github.com/repos/code-dot-org/code-dot-org/branches{/branch}","tags_url":"https://api.github.com/repos/code-dot-org/code-dot-org/tags","blobs_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/refs{/sha}","trees_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/trees{/sha}","statuses_url":"https://api.github.com/repos/code-dot-org/code-dot-org/statuses/{sha}","languages_url":"https://api.github.com/repos/code-dot-org/code-dot-org/languages","stargazers_url":"https://api.github.com/repos/code-dot-org/code-dot-org/stargazers","contributors_url":"https://api.github.com/repos/code-dot-org/code-dot-org/contributors","subscribers_url":"https://api.github.com/repos/code-dot-org/code-dot-org/subscribers","subscription_url":"https://api.github.com/repos/code-dot-org/code-dot-org/subscription","commits_url":"https://api.github.com/repos/code-dot-org/code-dot-org/commits{/sha}","git_commits_url":"https://api.github.com/repos/code-dot-org/code-dot-org/git/commits{/sha}","comments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/comments{/number}","issue_comment_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/comments{/number}","contents_url":"https://api.github.com/repos/code-dot-org/code-dot-org/contents/{+path}","compare_url":"https://api.github.com/repos/code-dot-org/code-dot-org/compare/{base}...{head}","merges_url":"https://api.github.com/repos/code-dot-org/code-dot-org/merges","archive_url":"https://api.github.com/repos/code-dot-org/code-dot-org/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/code-dot-org/code-dot-org/downloads","issues_url":"https://api.github.com/repos/code-dot-org/code-dot-org/issues{/number}","pulls_url":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls{/number}","milestones_url":"https://api.github.com/repos/code-dot-org/code-dot-org/milestones{/number}","notifications_url":"https://api.github.com/repos/code-dot-org/code-dot-org/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/code-dot-org/code-dot-org/labels{/name}","releases_url":"https://api.github.com/repos/code-dot-org/code-dot-org/releases{/id}","deployments_url":"https://api.github.com/repos/code-dot-org/code-dot-org/deployments","created_at":"2014-09-20T00:46:11Z","updated_at":"2016-10-22T02:50:56Z","pushed_at":"2016-11-11T20:18:35Z","git_url":"git://github.com/code-dot-org/code-dot-org.git","ssh_url":"git@github.com:code-dot-org/code-dot-org.git","clone_url":"https://github.com/code-dot-org/code-dot-org.git","svn_url":"https://github.com/code-dot-org/code-dot-org","homepage":"http://code.org","size":5176232,"stargazers_count":148,"watchers_count":148,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":96,"mirror_url":null,"open_issues_count":78,"forks":96,"open_issues":78,"watchers":148,"default_branch":"staging"}},"_links":{"self":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698"},"html":{"href":"https://github.com/code-dot-org/code-dot-org/pull/11698"},"issue":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/11698"},"comments":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/issues/11698/comments"},"review_comments":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698/comments"},"review_comment":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/pulls/11698/commits"},"statuses":{"href":"https://api.github.com/repos/code-dot-org/code-dot-org/statuses/15cbfe24a2420ed7ab79d4d82d4e6dcc6717803c"}}}}
{"actor":{"display_login":"jeremydstone"}}