class Lita::Handlers::GithubPinger

Public Instance Methods

act_on_assign(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 136
def act_on_assign(body, response)
  type = detect_type(body)

  if type.nil?
    puts "Neither pull request or issue detected, exiting..."
    return
  end

  puts "Detected that someone got assigned to a #{type.tr('_', ' ')}."

  assignee_login = body[type]["assignee"]["login"]
  assignee = find_engineer(github: assignee_login)

  puts "Looking up preferences..."
  should_notify = assignee[:github_preferences][:notify_about_assignment]

  if !should_notify
    puts "will not notify, preference for :github_preferences[:notify_about_assignment] is not true"
    return response
  end

  puts "#{assignee} determined as the assignee."

  url = body[type]["html_url"]

  message = "*Heads up!* You've been assigned to a #{type.tr('_', ' ')}:\n#{url}"

  puts "Sending DM to #{assignee}..."
  send_dm(assignee[:usernames][:slack], message)

  response
end
act_on_build_failure(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 86
def act_on_build_failure(body, response)
  commit_url = body["commit"]["html_url"]
  committer = find_engineer(github: body["commit"]["committer"]["login"])

  puts "Detected a status failure for commit #{body["sha"]}"
  message = ":x: Your commit failed CI."
  message += "\n#{commit_url}"

  if committer
    frequency = if committer[:travis_preferences]
      committer[:travis_preferences][:frequency]
    else
      committer[:status_preferences][:frequency]
    end

    return if ["off", "only_passes"].include?(frequency)

    send_dm(committer[:usernames][:slack], message)
  else
    puts "Could not find configuration for GitHub username " + body["commit"]["committer"]["login"]
  end

  response
end
act_on_build_success(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 111
def act_on_build_success(body, response)
  commit_url = body["commit"]["html_url"]
  committer = find_engineer(github: body["commit"]["committer"]["login"])

  puts "Detected a status success for commit #{body["sha"]}"
  message = ":white_check_mark: Your commit has passed CI."
  message += "\n#{commit_url}"

  if committer
    frequency = if committer[:travis_preferences]
      committer[:travis_preferences][:frequency]
    else
      committer[:status_preferences][:frequency]
    end

    return if ["off", "only_failures"].include?(frequency)

    send_dm(committer[:usernames][:slack], message)
  else
    puts "Could not find configuration for GitHub username " + body["commit"]["committer"]["login"]
  end

  response
end
act_on_comment(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 275
def act_on_comment(body, response)
  puts "Detected a comment. Extracting data... "

  comment_url = body["comment"]["html_url"]
  comment     = body["comment"]["body"]
  context     = body["pull_request"] || body["issue"]

  commenter = find_engineer(github: body["comment"]["user"]["login"])
  pr_owner  = find_engineer(github: context["user"]["login"])
  lita_commenter = Lita::User.fuzzy_find(commenter[:usernames][:slack])

  puts "Reacting to PR comment #{comment_url}"
  puts "Found commenter #{commenter}"
  puts "Found pr owner #{pr_owner}"

  # Sanity Checks - might be a new engineer around that hasn't set up
  # their config.

  engineers_to_ping = []
  # automatically include the creator of the PR, unless he's
  # commenting on his own PR

  if commenter != pr_owner && ["all_discussion", nil].include?(pr_owner[:github_preferences][:frequency])
    puts "PR owner was not the commenter, and has a :frequency of 'all_discussion' or nil"
    puts "Therefore, adding the PR owner to list of engineers to ping."
    engineers_to_ping << pr_owner
  end

  # Is anyone mentioned in this comment?
  if comment.include?("@")
    puts "Found @mentions in the body of the comment! Extracting usernames... "

    # get each @mentioned engineer in the comment
    mentions = comment
      .split("@")[1..-1] # "a @b @c d" => ["b ", "c d"]
      .map { |snip| snip.split(" ").first } # ["b ", "c d"] => ["b", "c"]
      .map { |name| name.gsub(/[^0-9a-z\-_]/i, '') }

    puts "Done. Got #{mentions}"
    puts "Converting usernames to engineers..."

    mentioned_engineers = mentions.map { |username| find_engineer(github: username) }

    puts "Done. Got #{mentioned_engineers}"

    # add them to the list of usernames to ping
    engineers_to_ping = engineers_to_ping.concat(mentioned_engineers).uniq.compact
  end

  puts "New list of engineers to ping: #{engineers_to_ping}."
  puts "Starting pinging process for each engineer..."
  engineers_to_ping.each do |engineer|
    puts "looking at #{engineer}'s preferences..'"
    next if engineer[:github_preferences][:frequency] == "off"

    case engineer[:github_preferences][:ping_location]
    when "dm", nil
      puts "Preference was either 'dm' or nil, so sending DM."
      private_message  = "New PR comment from <@#{lita_commenter.id}|#{commenter[:usernames][:slack]}>:\n"
      private_message += "#{comment_url}\n#{comment}"
      send_dm(engineer[:usernames][:slack], private_message)
    when "eng-pr", "eng_pr"
      puts "Preference was either 'eng-pr' or 'eng_pr', so alerting #eng-pr."
      public_message  = "@#{engineer[:usernames][:slack]}, new PR mention: "
      public_message += "#{comment_url}\n#{comment}"
      alert_eng_pr(public_message)
    end
  end

  puts "GitHub Hook successfully processed."

  response
end
act_on_pr_labeled(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 224
def act_on_pr_labeled(body, response)
  type = detect_type(body)
  puts "Detected that someone labeled a #{type.tr('_', ' ')}."

  if type.nil?
    puts "Neither pull request or issue detected, exiting..."
    return
  end

  if body["pull_request"]["labels"].none? { |label| label["name"].downcase.include?('review') }
    puts "Labels do not include a review label, exiting..."
    return
  end

  if config.enable_round_robin
    puts "round robin is enabled, selecting the next engineer.."

    chosen_reviewer = get_next_round_robin_reviewer

    pr_owner = find_engineer(github: body["pull_request"]["user"]["login"])
    pr_owner = pr_owner[:usernames][:slack] unless pr_owner.nil?

    if chosen_reviewer === pr_owner
      update_next_round_robin_reviewer
      chosen_reviewer = get_next_round_robin_reviewer
    end

    puts "#{chosen_reviewer} determined as the reviewer."

    url = body[type]["html_url"]

    message_for_reviewer = "You're next in line to review a PR! Please review, or assign to an engineer with more context if you think you are unable to give a thorough review. \n#{url}"
    message_for_owner = "#{chosen_reviewer} has been notified via round-robin to review #{body["pull_request"]["html_url"]}"

    puts "Sending DM to #{chosen_reviewer}..."
    send_dm(chosen_reviewer, message_for_reviewer)

    if pr_owner
      puts "Notifying #{pr_owner} of assignment."
      send_dm(pr_owner, message_for_owner)
    else
      puts "Couldn't find a config for pr owner #{body["pull_request"]["user"]["login"]}. Make sure they are in the lita config!"
      puts "Skipping notifying PR owner of RR assignment."
    end

    update_next_round_robin_reviewer
    
    response
  end
end
act_on_review_requested(body, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 169
def act_on_review_requested(body, response)
  puts "Detected a review request."

  puts "looking at previously notified reviewers for this PR"

  pr = body["pull_request"]
  url = pr["html_url"]

  while redis.locked?("change-reviewers")
    puts "Redis is locked, waiting..."
    sleep 1
  end

  redis.lock("change-reviewers") do |lock|
    puts "Redis lock aquired."
    notified_engineers = redis.get(REVIEW_REDIS_KEY + ":" + url)

    notified_engineers = if notified_engineers
      JSON.parse(notified_engineers)
    else
      []
    end

    pr["requested_reviewers"].each do |reviewer|
      engineer = find_engineer(github: reviewer["login"])

      if !engineer
        puts "Could not find engineer #{reviewer["login"]}"
        next
      end

      if notified_engineers.include?(reviewer["login"])
        puts "#{reviewer["login"]} has already been notified to review PR, skipping..."
        next
      end

      puts "#{engineer} determined as a reviewer."

      puts "Looking up preferences..."
      should_notify = engineer[:github_preferences][:notify_about_review_requests]

      if !should_notify
        puts "will not notify, preference for :github_preferences[:notify_about_review_requests] is not true"
      else
        message = "You've been asked to review a pull request:\n#{url}"
        send_dm(engineer[:usernames][:slack], message)
        notified_engineers.push(reviewer["login"])
      end
    end

    redis.set(REVIEW_REDIS_KEY + ":" + url, notified_engineers.to_json)
  end
  response
end
alert_eng_pr(message) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 374
def alert_eng_pr(message)
  puts "Alerting #eng-pr about content #{message[0..5]}... "
  room = Lita::Room.fuzzy_find("eng-pr")
  source = Lita::Source.new(room: room)
  robot.send_message(source, message)
  puts "Done."
end
detect_type(body) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 407
def detect_type(body)
  if body["pull_request"]
    "pull_request"
  elsif body["issue"]
    "issue"
  end
end
find_engineer(slack: nil, github: nil, name: nil) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 382
def find_engineer(slack: nil, github: nil, name: nil)
  if name
    return config.engineers[name]
  end

  config.engineers.values.select do |eng|
    if slack
      eng[:usernames][:slack] == slack
    elsif github
      eng[:usernames][:github] == github
    end
  end.first
end
get_next_round_robin_reviewer() click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 349
def get_next_round_robin_reviewer
  engineers_with_rr_enabled = config.engineers.values.select { |eng| eng[:enable_round_robin] }
  next_reviewer = redis.get(RR_REDIS_KEY)

  if next_reviewer.nil?
    next_reviewer = engineers_with_rr_enabled[0][:usernames][:slack]
  end

  next_reviewer
end
ghping(request, response) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 54
def ghping(request, response)
  puts "########## New GitHub Event! ##########"
  body = MultiJson.load(request.body)

  puts body["action"]
  puts body["state"]

  if body["comment"] && body["action"] == "created"
    act_on_comment(body, response)
  end

  if body["action"] && body["action"] == "assigned"
    act_on_assign(body, response)
  end

  if body["action"] && body["action"] == "review_requested"
    act_on_review_requested(body, response)
  end

  if body["action"] && body["action"] == "labeled"
    act_on_pr_labeled(body, response)
  end

  if body["state"] && body["state"] == "success"
    act_on_build_success(body, response)
  end

  if body["state"] && body["state"] == "failure"
    act_on_build_failure(body, response)
  end
end
send_dm(username, content) click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 396
def send_dm(username, content)
  puts "Sending DM to #{username} with content #{content[0..5]}... "
  if user = Lita::User.fuzzy_find(username)
    source = Lita::Source.new(user: user)
    robot.send_message(source, content)
    puts "Done."
  else
    alert_eng_pr("Could not find user with name #{username}, please configure everbot.")
  end
end
update_next_round_robin_reviewer() click to toggle source
# File lib/lita/handlers/github_pinger.rb, line 360
def update_next_round_robin_reviewer
  engineers_with_rr_enabled = config.engineers.values.select { |eng| eng[:enable_round_robin] }
  current_reviewer = get_next_round_robin_reviewer

  current_reviewer_index = engineers_with_rr_enabled.find_index do |eng|
    eng[:usernames][:slack] == current_reviewer
  end

  next_reviewer_index = (current_reviewer_index + 1) % engineers_with_rr_enabled.length
  next_reviewer = engineers_with_rr_enabled[next_reviewer_index][:usernames][:slack]

  redis.set(RR_REDIS_KEY, next_reviewer)
end