Skip to content

Commit

Permalink
Merge pull request #35 from voxpupuli/event-handling
Browse files Browse the repository at this point in the history
WIP: Event handling
  • Loading branch information
bastelfreak committed Jul 30, 2019
2 parents 3fa683b + fe6ad29 commit dbe3c4d
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 31 deletions.
15 changes: 7 additions & 8 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-07-20 16:07:34 +0200 using RuboCop version 0.73.0.
# on 2019-07-30 20:31:00 +0200 using RuboCop version 0.73.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -118,7 +118,7 @@ Layout/Tab:
Exclude:
- 'config/initializers/omniauth.rb'

# Offense count: 6
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: final_newline, final_blank_line
Expand All @@ -129,7 +129,6 @@ Layout/TrailingBlankLines:
- 'app/controllers/sessions_controller.rb'
- 'app/models/user.rb'
- 'config/initializers/sidekiq.rb'
- 'config/initializers/voxpupuli.rb'

# Offense count: 2
# Cop supports --auto-correct.
Expand All @@ -140,7 +139,7 @@ Layout/TrailingWhitespace:

# Offense count: 6
Metrics/AbcSize:
Max: 111
Max: 118

# Offense count: 1
Metrics/CyclomaticComplexity:
Expand Down Expand Up @@ -253,11 +252,11 @@ Style/CommentAnnotation:
Exclude:
- 'app/workers/repo_status_worker.rb'

# Offense count: 18
# Offense count: 23
Style/Documentation:
Enabled: false

# Offense count: 59
# Offense count: 64
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, never
Expand Down Expand Up @@ -333,15 +332,15 @@ Style/TrailingCommaInArguments:
Exclude:
- 'config/initializers/semantic_logger.rb'

# Offense count: 3
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinSize, WordRegex.
# SupportedStyles: percent, brackets
Style/WordArray:
Exclude:
- 'config/initializers/voxpupuli.rb'

# Offense count: 58
# Offense count: 69
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dist: xenial
language: ruby
cache: bundler
script:
- 'bundle exec rake $CHECK'
- 'bundle exec rails $CHECK'
matrix:
fast_finish: true
include:
Expand Down
7 changes: 6 additions & 1 deletion app/controllers/incoming_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ class IncomingController < ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :logged_in

##
# Parse the whole payload from Github and hand it over to the GithubEvent handler
def github
# parse the JSON payload, that we get as string, to a hash
useable_body = JSON.parse(request.body.read).to_h

# For debug purposes we currently log the requets into Sentry
# TODO: Remove this line since the GithubEvent handler reports unknown events.
Raven.capture_message('Received hook', extra: useable_body)

GithubEvent.new(useable_body, request.headers['X-GitHub-Event'])
end
end
19 changes: 19 additions & 0 deletions app/lib/github_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class GithubEvent
attr_reader :processor
delegate :process, to: :processor

##
# The GithubEvent handler checks which type of event we are dealing with.
#
# If it is a known event, create a specific handler and let it take care of
# the next steps. If not kick off a Sentry error.

def initialize(payload, type)
case type
when 'pull_request'
@processor = GithubEvent::PullRequest.new(payload)
else
Raven.capture_message('Unknown Hook Received', extra: payload)
end
end
end
13 changes: 13 additions & 0 deletions app/lib/github_event/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class GithubEvent
class Base
attr_reader :payload

##
# Receive the payload and start processing it

def initialize(payload)
@payload = payload
process
end
end
end
13 changes: 13 additions & 0 deletions app/lib/github_event/pull_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class GithubEvent
class PullRequest < GithubEvent::Base
attr_reader :gh_pull_request

##
# Currently we only care about syncing our database with GitHub as our validation
# will take action if necessary.

def process
::PullRequest.update_with_github(payload['pull_request'])
end
end
end
5 changes: 5 additions & 0 deletions app/models/label.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
class Label < ApplicationRecord
has_and_belongs_to_many :pull_requests

# TODO: Find out how to manage predefined labels
def self.needs_rebase
find_or_create_by(name: 'needs-rebase', color: '207de5')
end
end
87 changes: 86 additions & 1 deletion app/models/pull_request.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,89 @@
class PullRequest < ApplicationRecord
belongs_to :repository
# It is easier overall to use the GitHub ID for relation management.
# It allows us to maintain, update or the Repository or PullRequest without
# the counterpart.
belongs_to :repository, primary_key: :github_id, foreign_key: :gh_repository_id, inverse_of: :pull_requests

has_and_belongs_to_many :labels
after_save :queue_validation

##
# This method is used for updating our database with the current state of the PullRequest
# in GitHub. Therefore it's used with the payload of the webhook content processed by
# GithubEvent handler and by the content from the api fetched by the periodic check.

def self.update_with_github(gh_pull_request)
PullRequest.where(github_id: gh_pull_request['id']).first_or_initialize.tap do |pull_request|
pull_request.number = gh_pull_request['number']
pull_request.state = gh_pull_request['state']
pull_request.title = gh_pull_request['title']
pull_request.body = gh_pull_request['body']
pull_request.gh_created_at = gh_pull_request['created_at']
pull_request.gh_updated_at = gh_pull_request['updated_at']
pull_request.gh_repository_id = gh_pull_request['base']['repo']['id']
pull_request.closed_at = gh_pull_request['closed_at']
pull_request.merged_at = gh_pull_request['merged_at']
pull_request.mergeable = gh_pull_request['mergeable']
pull_request.save

gh_pull_request['labels'].each do |label|
pull_request.labels << Label.find_or_create_by(name: label['name'], color: label['color'])
end
end
end

##
# Ensure that the Label is attached to the PullRequest
#
# Therefore ensure that the Label exists for the corresponding repository
#
# Then ensure the Label is attached to this PullRequest by checking the list
# of attached Labels. Unfortunately this seems to be the only option
#
# If the list does not include the given Label we attach it

def ensure_label_is_attached(label)
repository.ensure_label_exists(label)

attached_labels = Github.client.labels_for_issue(gh_repository_id, number)
return if attached_labels.any? { |attached_label| attached_label['name'] == label.name }

Github.client.add_labels_to_an_issue(gh_repository_id, number, [label.name])
end

##
# We simply remove the given Label if it exists

def ensure_label_is_detached(label)
Github.client.remove_label(gh_repository_id, number, label.name)
end

##
# if the PullRequest is mergeable we need to check if the 'needs-rebase' Label
# is attached. If so, we need to remove it.
#
# If the PullRequest is not yet mergeable we need to attach the 'needs-rebase'
# Label. Therefore we also need to check if the Label exists on repository level.
#
# The saved_changes variable includes all the stuff that has changed.
# We currently don't care about them

def validate(_saved_changes)
if mergeable
ensure_label_is_detached(Label.needs_rebase)
else
repository.ensure_label_exists(Label.needs_rebase)
ensure_label_is_attached(label)
end
end

private

##
# Since we want to be a fancy responsive application we to all the validation
# stuff which might result in some new querys and api requests asyncronously.

def queue_validation
ValidatePullRequestWorker.perform_async(id, saved_changes) if saved_changes?
end
end
43 changes: 26 additions & 17 deletions app/models/repository.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class Repository < ApplicationRecord
has_many :pull_requests
has_many :open_pull_requests, -> { where(state: 'open') }, class_name: 'PullRequest'
# It is easier overall to use the GitHub ID for relation management.
# It allows us to maintain, update or the Repository or PullRequest without
# the counterpart.
has_many :pull_requests, primary_key: :github_id, foreign_key: :gh_repository_id, inverse_of: :repository
has_many :open_pull_requests, -> { where(state: 'open') }, class_name: 'PullRequest', primary_key: :github_id, foreign_key: :gh_repository_id

def actions_needed
@action_needed ||= begin
Expand All @@ -19,25 +22,31 @@ def github_url
'https://github.com/' + full_name
end

##
# Check if the Label exists in the Repository
# If we get a 404 create the Label.

def ensure_label_exists(label)
GitHub.client.label(github_id, label.name)
rescue Octokit::NotFound
Github.client.add_label(github_id, label.name, label.color)
end

##
# Delete the given Label from the Repository
#
def ensure_label_missing(label)
GitHub.client.delete_label!(github_id, label.name)
end

##
# Fetch all open and closed PullRequests and sync our database with them
#
def update_pull_requests
open_pull_requests = Github.client.pull_requests("voxpupuli/#{name}")
closed_pull_requests = Github.client.pull_requests("voxpupuli/#{name}", state: :closed)
(open_pull_requests + closed_pull_requests).each do |gh_pull_request|
PullRequest.where(repository_id: id, number: gh_pull_request.number).first_or_initialize.tap do |pull_request|
pull_request.number = gh_pull_request.number
pull_request.state = gh_pull_request.state
pull_request.title = gh_pull_request.title
pull_request.body = gh_pull_request.body
pull_request.gh_created_at = gh_pull_request.created_at
pull_request.gh_updated_at = gh_pull_request.updated_at
pull_request.closed_at = gh_pull_request.closed_at
pull_request.merged_at = gh_pull_request.merged_at
pull_request.save

gh_pull_request.labels.each do |label|
pull_request.labels << Label.find_or_create_by(name: label.name, color: label.color)
end
end
PullRequest.update_with_github(gh_pull_request)
end

pull_requests.count
Expand Down
2 changes: 0 additions & 2 deletions app/workers/repo_status_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ def save_data_to_redis(data)
# Categorizes repository based on their locally cached information
# implements https://github.com/voxpupuli/modulesync_config/blob/master/bin/get_all_the_diffs
# TODO: clean up stuff and make it less shitty
# rubocop:disable Metrics/AbcSize
def perform
repos = Repository.pluck(:name)
data = OpenStruct.new
Expand Down Expand Up @@ -144,5 +143,4 @@ def perform

save_data_to_redis(data)
end
# rubocop:enable Metrics/AbcSize
end
12 changes: 12 additions & 0 deletions app/workers/validate_pull_request_worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class ValidatePullRequestWorker
include Sidekiq::Worker

##
# As validation may take a bit more time we do it async.
# This worker is not meant to hold any logic but only trigger the
# validation in its asyncronous context.

def perform(id, saved_changes)
PullRequest.find(id).validate(saved_changes)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddMergeableAndGhRepoIdToPullRequests < ActiveRecord::Migration[5.2]
def change
add_column :pull_requests, :mergeable, :boolean
add_column :pull_requests, :gh_repository_id, :integer
add_column :pull_requests, :github_id, :integer
end
end
5 changes: 4 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_06_20_115024) do
ActiveRecord::Schema.define(version: 2019_07_30_094710) do

create_table "labels", force: :cascade do |t|
t.string "name"
Expand Down Expand Up @@ -38,6 +38,9 @@
t.integer "repository_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "mergeable"
t.integer "gh_repository_id"
t.integer "github_id"
t.index ["repository_id"], name: "index_pull_requests_on_repository_id"
end

Expand Down

0 comments on commit dbe3c4d

Please sign in to comment.