Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/controllers/pull_requests_comments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class PullRequestsCommentsController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :find_pull_request!

def index
@comments = @pull_request.comments.order(created_at: :desc)
end

def create
@comment = @pull_request.comments.new(comment_params)
@comment.user = current_user

render json: { errors: @comment.errors }, status: :unprocessable_entity unless @comment.save
end
private

def comment_params
params.require(:comment).permit(:body)
end

def find_pull_request!
@pull_request = PullRequest.find(params[:pull_request_id])
end
end
56 changes: 56 additions & 0 deletions app/controllers/pull_requests_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class PullRequestsController < ApplicationController
def index
authenticate_user!

@pull_requests = PullRequests

@pull_requests = @pull_requests.tagged_with(params[:tag]) if params[:tag].present?

@pull_requests = @pull_requests.order(created_at: :desc).offset(params[:offset] || 0).limit(params[:limit] || 20)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three definitions of @pull_requests here look like they are business logic (which PRs do we want and how?) and that is usually best handled by a scope in the model, rather than defined in the controller itself (bonus: much easier to test!).


# We only want pull requests owned by this user and that have been open for less than a week
@output = []
@output2 = []
@pull_requests.each do |pr|
if pr.author == current_user
@output << pr
end
end

@output.each do |pr|
if pr.created_at - Time.now > 7
@output2 << pr
end
end

@pull_requests = output

render :index
end

def create
authenticate_user!

@pull_request = PullRequest.new(params)
@pull_request.user = current_user

if @pull_request.save
render :show
else
render json: { errors: @article.errors }, status: :unprocessable_entity
end
end

def show
authenticate_user!
@pull_request = PullRequest.find_by_id!(params[:id])
end

def destroy
@pull_request = PullRequest.find(params[:id])
pull_request.destroy
render json: {}
end

private
end
11 changes: 11 additions & 0 deletions app/models/pull_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class PullRequest < ActiveRecord::Base
belongs_to :user

scope :author, ->(username) { where(user: User.where(username: username)) }

acts_as_taggable

validates :title, presence: true, allow_blank: false
validates :url, presence: true, allow_blank: false
validates :status, presence: true, allow_blank: false
end
6 changes: 6 additions & 0 deletions app/models/pull_request_comments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :pull_request

validates :body, presence: true, allow_blank: false
end
2 changes: 2 additions & 0 deletions app/views/pull_requests/_pull_request.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json.(pull_request, :title, :body, :created_at, :updated_at, :description, :tag_list)
json.author pull_request.user, partial: 'profiles/profile', as: :user
3 changes: 3 additions & 0 deletions app/views/pull_requests/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
json.pull_requests do |json|
json.array! @pull_requests, partial: 'pull_requests/pull_requests', as: :pull_request
end
3 changes: 3 additions & 0 deletions app/views/pull_requests/show.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
json.pull_requests do |json|
json.array! @pull_requests, partial: 'pull_requests/pull_requests', as: :pull_request
end
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
get :feed, on: :collection
end

resources :pull_requests, param: :id, except: [:edit ] do
get :open, on: :collection
end

resources :tags, only: [:index]
end

Expand Down
1 change: 1 addition & 0 deletions config/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

development:
secret_key_base: 1cedd34d1be5424c2646c82e267fcbb817dd118d47d52ea8835e7fd8ada93fcec1523148008c0ed647e5f852717692cb0236226929b02c97bfd032a275d5d87a
github_api_token: obj_d90u1293j09fh899899swhs0sjh90

test:
secret_key_base: 7d4d2daa8c4b79efe9a9206d20b877a2cde9c90f46987c6ec24f811d06eddb9d554742fc6c25cd7ec73ce9e698424b152e78bb8bf61da8b669c53677f04ab0b8
Expand Down
100 changes: 100 additions & 0 deletions lib/tasks/sync.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
desc 'Sync pull requests from Github'
task :sync => :environment do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In six months I think you'll find yourself typing rake sync and wondering which of twelve syncs we're running. I would recommend a slightly more descriptive name, maybe even a nested thing - rake sync:github:pr might be an extreme example.

class ResponseError < StandardError
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A part of me is surprised this isn't defined somewhere else in the codebase already - these error names are very generic and high level, so we want one of two things here:

  1. either we make task-specific error names
  2. either we move these out so the whole codebase can use these as common errors

If you're not sure which of the two to do, then let me know and we can have a conversation about it - you'll be able to make the decision afterwards.

def initialize(response, message, repository)
super(message)

@response = response
@repository = repository
end

# These errors seem to happen a lot, so avoid sending them to Bugsnag
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..... why do these errors happen a lot?

Errors are a way for the server to communicate with us, so if we're doing something wrong, we probably shouldn't ignore it. Here, it seems to indicate we're asking for too much too fast. We should look for a tool that helps us stay within the acceptable bounds as defined by Github's API terms of use.

def skip_bugsnag
is_a?(ForbiddenError) ||
is_a?(RateLimitError) ||
is_a?(ResolveError) ||
is_a?(UnauthorizedError) ||
is_a?(IPAllowListError)
end

private
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PLZ TO USE AUTOINDENT OMG


attr_reader :response, :repository
end

BadGatewayError = Class.new(ResponseError)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this! Simple, concise, and super-clear.

ForbiddenError = Class.new(ResponseError)
IPAllowListError = Class.new(ResponseError)
RateLimitError = Class.new(ResponseError)
ResolveError = Class.new(ResponseError)
UnauthorizedError = Class.new(ResponseError)
UnknownQueryError = Class.new(ResponseError)

if response.errors.any?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not entirely certain there's any code that runs and generates a response object here?

message = response.errors[:data].join(", ")
ex = build_response_error(message)
end

if response.data
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm nested if and for loop? Maybe extract a method here?

if response.data.pull_requests
for i in 1..response.data.pull_requests.length() do
id = response.data.pull_requests[i].split('/')[-1]
@pull_request = PullRequest.new(response.data.pull_requests[i].merge{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're going to continually override the value of @pull_request with whatever the latest PR is, is this what we want?

It looks like we might want to separate this into a few different operations to make it more readable; merge looks to be the key operation that you'd want other devs to pick up on.

id: id,
})
end
end
end
rescue Exception => ex
raise ex
end

private

def build_response_error(message)
(
case message
when /401 Unauthorized/
UnauthorizedError
when /403 Forbidden/
ForbiddenError
when /API rate limit exceeded/
RateLimitError
when /Could not resolve to a/
ResolveError
when /502 Bad Gateway/
BadGatewayError
when /IP allow list enabled/
IPAllowListError
when /Something went wrong while executing your query/
UnknownQueryError
else
ResponseError
end
).new(response, message, repository)
end

def response
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is too generic, and it hides the fact that this code is making the HTTP call. I would suggest something like current_pull_requests_on_github (or whatever makes more sense) so that the next person reading this code is tipped off to the idea that an HTTP call is happening.

client = Github::Graphql::Client.new("codeclimate", Rails.application.secrets.github_api_token)
variables ||= { org: "codeclimate" }
definition = parse_query <<-'GRAPHQL'
{
search(query: "org:$org is:pr created:>2019-04-01", type: ISSUE, last: 100) {
edges {
node {
... on PullRequest {
url
title
status
}
}
}
}
}
GRAPHQL
@response ||= client.tap { logger.debug("Querying %s with %s" % [definition, variables.inspect]) }.
query(definition, variables: variables)
end
end
end
end
24 changes: 24 additions & 0 deletions test/controllers/pull_requests_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require "rails_helper"

describe PullRequestsController do
before do
user = create_user
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're using rspec, let will be ... More elegant.

allow(controller).to receive(:current_user).and_return(user)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self, do we want this here ?

end

def create_user()
return User.create!({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to add this to the database - this slows down the test suite

username: "me",
email: "me@me.com",
password: "1234",
bio: "",
})
end

describe "#index" do
it "returns something" do
get :index
expect(response).not_to be_empty
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great test to start with, but it doesn't test real product behavior, so it needs to evolve a little.

end
end
end