Skip to content

Commit

Permalink
Include GitHub on the new Basecamp timeline.
Browse files Browse the repository at this point in the history
Rename the old Basecamp hook to Basecamp Classic, but keep the internal
name for back-compat.
  • Loading branch information
jeremy committed Apr 25, 2012
1 parent 3825344 commit fddd20c
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 150 deletions.
18 changes: 6 additions & 12 deletions docs/basecamp
Expand Up @@ -4,24 +4,18 @@ Basecamp
Install Notes
-------------

1. url should be your Basecamp url
2. username should be the username or API token of the user that you want to use to post messages into your Basecamp - you can setup a user just for this purpose
3. password should be the password of the user that you want to use to post the messages. If username is an API token, set password to 'x'.
4. project should be the name of the project that you want to post the message into (not the id)
5. category should be the name of the category that you want to post the message using (not the id)
6. ssl should be enabled for accounts that need SSL.
1. project_url is the URL of your Basecamp project: https://basecamp.com/1234/projects/5678
2. email_address is the email you sign in to Basecamp with. This person must have access to the project. To add events on behalf of other people, make the person an admin on the project.
3. password is the password you sign in to Basecamp with.


Developer Notes
---------------

data
- url
- username
- project_url
- email_address
- password
- project
- category
- ssl

payload
- refer to docs/github_payload
- refer to docs/github_payload
27 changes: 27 additions & 0 deletions docs/basecamp_classic
@@ -0,0 +1,27 @@
Basecamp Classic
================

Install Notes
-------------

1. url should be your Basecamp Classic url
2. username should be the username or API token of the user that you want to use to post messages into your Basecamp - you can setup a user just for this purpose
3. password should be the password of the user that you want to use to post the messages. If username is an API token, set password to 'x'.
4. project should be the name of the project that you want to post the message into (not the id)
5. category should be the name of the category that you want to post the message using (not the id)
6. ssl should be enabled for accounts that need SSL.


Developer Notes
---------------

data
- url
- username
- password
- project
- category
- ssl

payload
- refer to docs/github_payload
170 changes: 49 additions & 121 deletions services/basecamp.rb
@@ -1,143 +1,71 @@
class Service::Basecamp < Service
string :url, :project, :category, :username
password :password
boolean :ssl
string :project_url, :email_address
password :password
white_list :project_url, :email_address
default_events :push, :pull_request, :issues

white_list :url, :project, :category, :username

def receive_push
raise_config_error "Invalid basecamp domain" if basecamp_domain.nil?

repository = payload['repository']['name']
name_with_owner = File.join(payload['repository']['owner']['name'], repository)
branch = ref_name

commits = payload['commits'].reject { |commit| commit['message'].to_s.strip == '' }
return if commits.empty?

::Basecamp.establish_connection! basecamp_domain,
data['username'], data['password'], data['ssl'].to_i == 1

commits.each do |commit|
gitsha = commit['id']
short_git_sha = gitsha[0..5]
timestamp = Date.parse(commit['timestamp'])

added = commit['added'].map { |f| ['A', f] }
removed = commit['removed'].map { |f| ['R', f] }
modified = commit['modified'].map { |f| ['M', f] }
changed_paths = (added + removed + modified).sort_by { |(char, file)| file }
changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n ")

# Shorten the elements of the subject
commit_title = commit['message'][/^([^\n]+)/, 1]
if commit_title.length > 50
commit_title = commit_title.slice(0,50) << '...'
end

title = "Commit on #{name_with_owner}: #{short_git_sha}: #{commit_title}"

body = <<-EOH
*Author:* #{commit['author']['name']} <#{commit['author']['email']}>
*Commit:* <a href="#{commit['url']}">#{gitsha}</a>
*Date:* #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')})
*Branch:* #{branch}
*Home:* #{payload['repository']['url']}
h2. Log Message
<pre>#{commit['message']}</pre>
EOH

if changed_paths.size > 0
body << <<-EOH
h2. Changed paths
<pre> #{changed_paths}</pre>
EOH
end
def hook_name
'bcx'
end

post_message :title => title, :body => body
end
def receive_push
commit = payload['commits'].last || {}
author = commit['author'] || commit['committer'] || payload['pusher']

rescue SocketError => boom
if boom.to_s =~ /getaddrinfo: Name or service not known/
raise_config_error "Invalid basecamp domain name"
else
raise
end
rescue ActiveResource::UnauthorizedAccess => boom
raise_config_error "Unauthorized. Verify the project URL and credentials."
rescue ActiveResource::ForbiddenAccess => boom
raise_config_error boom.to_s
rescue ActiveResource::Redirection => boom
raise_config_error "Invalid project URL: #{boom}"
rescue RuntimeError => boom
if boom.to_s =~ /\((?:403|401|422)\)/
raise_config_error "Invalid credentials: #{boom}"
elsif boom.to_s =~ /\((?:404|301)\)/
raise_config_error "Invalid project URL: #{boom}"
elsif boom.to_s == 'Unprocessable Entity (422)'
# do nothing
else
raise
end
message = summary_message.sub("[#{repo_name}] #{pusher_name} ", '')
create_event 'committed', message, summary_url, author['email']
end

attr_writer :basecamp
attr_writer :project_id
attr_writer :category_id
def receive_pull_request
base_ref = pull.base.label.split(':').last
head_ref = pull.head.label.split(':').last
head_ref = pull.head.label if head_ref == base_ref

def basecamp_domain
@basecamp_domain ||= Addressable::URI.parse(data['url'].to_s).host
rescue Addressable::URI::InvalidURIError
create_event "#{action} a pull request",
"#{pull.title} (#{base_ref}..#{head_ref})",
pull.html_url
end

def build_message(options = {})
m = ::Basecamp::Message.new :project_id => project_id
m.category_id = category_id
options.each do |key, value|
m.send "#{key}=", value
end
m
def receive_issues
create_event "#{action} an issue", issue.title, issue.html_url
end

def post_message(options = {})
build_message(options).save
end

def all_projects
Array(::Basecamp::Project.all)
end
private

def all_categories
Array(::Basecamp::Category.post_categories(project_id))
def create_event(action, message, url, author_email = nil)
http_post_event :service => 'github',
:creator_email_address => author_email,
:description => action,
:title => message,
:url => url
end

def project_id
@project_id ||= begin
name = data['project'].to_s
name.downcase!
projects = all_projects.select { |p| p.name.downcase == name }
case projects.size
when 1 then projects.first.id
when 0 then raise_config_error("Invalid Project: #{name.downcase}")
else raise_config_error("Multiple projects named: #{name.downcase}")
end
def http_post_event(params)
http.basic_auth data['email_address'], data['password']
http.headers['User-Agent'] = 'GitHub service hook'
http.headers['Content-Type'] = 'application/json'
http.headers['Accept'] = 'application/json'

response = http_post(events_api_url, params.to_json)

case response.status
when 401; raise_config_error "Invalid email + password: #{response.body.inspect}"
when 403; raise_config_error "No access to project: #{response.body.inspect}"
when 404; raise_config_error "No such project: #{response.body.inspect}"
when 422; raise_config_error "Validation error: #{response.body.inspect}"
end
end

def category_id
@category_id ||= begin
name = data['category'].to_s
name.downcase!
categories = all_categories.select { |c| c.name.downcase == name }
case categories.size
when 1 then categories.first.id
when 0 then raise_config_error("Invalid Category: #{name.downcase}")
else raise_config_error("Multiple categories named: #{name.downcase}")
end
EVENTS_API_URL = 'https://basecamp.com:443/%d/api/v1/projects/%d/events.json'
def events_api_url
if data['project_url'] =~ %r{^https://basecamp\.com/(\d+)/projects/(\d+)}
EVENTS_API_URL % [$1, $2]
elsif data['project_url'] =~ /basecamphq\.com/
raise_config_error "That's a URL for a Basecamp Classic project, not the new Basecamp. Check out the Basecamp Classic service hook instead!"
else
raise_config_error "That's not a URL to a Basecamp project! Navigate to the Basecamp project you'd like to post to and note the URL. It should look something like: https://basecamp.com/123456/projects/7890123 -- paste that URL here."
end
end
end

0 comments on commit fddd20c

Please sign in to comment.