Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 3e054c0

Browse files
author
Jon Yurek
committed
Services return the HTTP call's status
In order to properly record the data for ServiceEvents in codeclimate proper, we need to have the calls to the external services return something useful. The way this works is by moving all of the actual HTTP POSTing into a dedicated method that wraps the response. Failures are handled by the `WithErrorHandling` invocation middleware as normal. A successful request will be parsed for standard things like status code, the params we sent, the url we hit, and whether it worked or not, and a message. Failures will have a message explaining the error. `receive_test` methods will include a user-friendly error message. Any other error will forego HTTP information. This is all tested and common to each service, so they don't need their own specific tests for it. A successful request can be modified by adding extra attributes into the hash via a block. The response body is passed to the block to be parsed inside it (since we get both JSON and XML from various services). Relevant values can be extracted in this block. Parse errors will trigger the generic error handling and return a "this failed" message to the caller. The uniquely-added attributes are tested on a per-service basis. Some services make multiple requests. The first one to fail is what we return an error message for. If none fail, it's all good.
1 parent 0b76fc7 commit 3e054c0

29 files changed

+543
-241
lines changed

lib/cc/service/http.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,23 @@ def default_http_options
1515
end
1616
end
1717

18-
def http_get(url = nil, params = nil, headers = nil)
18+
def get(url = nil, body = nil, headers = nil, &block)
19+
raw_get(url, body, headers, &block)
20+
end
21+
22+
def post(url, body = nil, headers = nil, &block)
23+
block ||= lambda{|*args| Hash.new }
24+
response = raw_post(url, body, headers)
25+
{
26+
ok: response.success?,
27+
params: body.as_json,
28+
endpoint_url: url,
29+
status: response.status,
30+
message: "Success"
31+
}.merge(block.call(response))
32+
end
33+
34+
def raw_get(url = nil, params = nil, headers = nil)
1935
http.get do |req|
2036
req.url(url) if url
2137
req.params.update(params) if params
@@ -24,7 +40,7 @@ def http_get(url = nil, params = nil, headers = nil)
2440
end
2541
end
2642

27-
def http_post(url = nil, body = nil, headers = nil)
43+
def raw_post(url = nil, body = nil, headers = nil)
2844
block = Proc.new if block_given?
2945
http_method :post, url, body, headers, &block
3046
end

lib/cc/service/invocation/with_error_handling.rb

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,35 @@ def initialize(invocation, logger, prefix = nil)
88

99
def call
1010
@invocation.call
11-
rescue => ex
12-
@logger.error(error_message(ex))
13-
14-
nil
11+
rescue CC::Service::HTTPError => e
12+
@logger.error(error_message(e))
13+
{
14+
ok: false,
15+
params: e.params,
16+
status: e.status,
17+
endpoint_url: e.endpoint_url,
18+
message: error_message(e)
19+
}
20+
rescue => e
21+
@logger.error(error_message(e))
22+
{
23+
ok: false,
24+
message: error_message(e)
25+
}
1526
end
1627

1728
private
1829

19-
def error_message(ex)
20-
if ex.respond_to?(:response_body)
21-
response_body = ". Response: <#{ex.response_body.inspect}>"
30+
def error_message(e)
31+
if e.respond_to?(:response_body)
32+
response_body = ". Response: <#{e.response_body.inspect}>"
2233
else
2334
response_body = ""
2435
end
2536

2637
message = "Exception invoking service:"
2738
message << " [#{@prefix}]" if @prefix
28-
message << " (#{ex.class}) #{ex.message}"
39+
message << " (#{e.class}) #{e.message}"
2940
message << response_body
3041
end
3142
end

lib/cc/service/response_check.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
class CC::Service
22
class HTTPError < StandardError
3-
attr_reader :response_body, :status
3+
attr_reader :response_body, :status, :params, :endpoint_url
44

55
def initialize(message, env)
66
@response_body = env[:body]
77
@status = env[:status]
8+
@params = env[:params]
9+
@endpoint_url = env[:url]
810

911
super(message)
1012
end

lib/cc/services/asana.rb

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,20 @@ class Config < CC::Service::Config
1414
validates :workspace_id, presence: true
1515
end
1616

17+
ENDPOINT = "https://app.asana.com/api/1.0/tasks"
18+
1719
self.title = "Asana"
1820
self.description = "Create tasks in Asana"
1921
self.issue_tracker = true
2022

2123
def receive_test
2224
result = create_task("Test task from Code Climate")
23-
24-
{
25-
ok: true,
26-
message: "Ticked <a href='#{result[:url]}'>#{result[:id]}</a> created."
27-
}
28-
rescue => ex
29-
{ ok: false, message: ex.message }
25+
result.merge(
26+
message: "Ticket <a href='#{result[:url]}'>#{result[:id]}</a> created."
27+
)
3028
end
3129

30+
3231
def receive_quality
3332
title = "Refactor #{constant_name} from #{rating} on Code Climate"
3433

@@ -45,6 +44,18 @@ def receive_vulnerability
4544
private
4645

4746
def create_task(name)
47+
params = generate_params(name)
48+
authenticate_http
49+
http.headers["Content-Type"] = "application/json"
50+
post(ENDPOINT, params.to_json) do |response|
51+
body = JSON.parse(response.body)
52+
id = body['data']['id']
53+
url = "https://app.asana.com/0/#{config.workspace_id}/#{id}"
54+
{ id: id, url: url }
55+
end
56+
end
57+
58+
def generate_params(name)
4859
params = {
4960
data: { workspace: config.workspace_id, name: name }
5061
}
@@ -58,18 +69,11 @@ def create_task(name)
5869
params[:data][:assignee] = config.assignee
5970
end
6071

61-
http.headers["Content-Type"] = "application/json"
62-
http.basic_auth(config.api_key, "")
63-
64-
url = "https://app.asana.com/api/1.0/tasks"
65-
res = http_post(url, params.to_json)
66-
67-
body = JSON.parse(res.body)
68-
69-
id = body['data']['id']
70-
url = "https://app.asana.com/0/#{config.workspace_id}/#{id}"
72+
params
73+
end
7174

72-
{ id: id, url: url }
75+
def authenticate_http
76+
http.basic_auth(config.api_key, "")
7377
end
7478

7579
end

lib/cc/services/campfire.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ class Config < CC::Service::Config
1515
self.description = "Send messages to a Campfire chat room"
1616

1717
def receive_test
18-
speak(formatter.format_test)
19-
20-
{ ok: true, message: "Test message sent" }
21-
rescue => ex
22-
{ ok: false, message: ex.message }
18+
speak(formatter.format_test).merge(
19+
message: "Test message sent"
20+
)
2321
end
2422

2523
def receive_coverage
@@ -42,10 +40,10 @@ def formatter
4240

4341
def speak(line)
4442
http.headers['Content-Type'] = 'application/json'
45-
body = { message: { body: line } }
43+
params = { message: { body: line } }
4644

4745
http.basic_auth(config.token, "X")
48-
http_post(speak_uri, body.to_json)
46+
post(speak_uri, params.to_json)
4947
end
5048

5149
def speak_uri

lib/cc/services/flowdock.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ class Config < CC::Service::Config
1313
self.description = "Send messages to a Flowdock inbox"
1414

1515
def receive_test
16-
notify("Test", repo_name, formatter.format_test)
17-
18-
{ ok: true, message: "Test message sent" }
19-
rescue => ex
20-
{ ok: false, message: ex.message }
16+
notify("Test", repo_name, formatter.format_test).merge(
17+
message: "Test message sent"
18+
)
2119
end
2220

2321
def receive_coverage
@@ -56,7 +54,9 @@ def notify(subject, project, content)
5654
}
5755

5856
url = "#{BASE_URL}/messages/team_inbox/#{config.api_token}"
57+
http.headers['Content-Type'] = 'application/json'
5958
http.headers["User-Agent"] = "Code Climate"
60-
http_post(url, params)
59+
60+
post(url, params)
6161
end
6262
end

lib/cc/services/github_issues.rb

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,9 @@ class Config < CC::Service::Config
2121

2222
def receive_test
2323
result = create_issue("Test ticket from Code Climate", "")
24-
25-
{
26-
ok: true,
24+
result.merge(
2725
message: "Issue <a href='#{result[:url]}'>##{result[:number]}</a> created."
28-
}
29-
rescue => ex
30-
{ ok: false, message: ex.message }
26+
)
3127
end
3228

3329
def receive_quality
@@ -59,15 +55,14 @@ def create_issue(title, issue_body)
5955
http.headers["User-Agent"] = "Code Climate"
6056

6157
url = "#{BASE_URL}/repos/#{config.project}/issues"
62-
res = http_post(url, params.to_json)
63-
64-
body = JSON.parse(res.body)
65-
66-
{
67-
id: body["id"],
68-
number: body["number"],
69-
url: body["html_url"]
70-
}
58+
post(url, params.to_json) do |response|
59+
body = JSON.parse(response.body)
60+
{
61+
id: body["id"],
62+
number: body["number"],
63+
url: body["html_url"]
64+
}
65+
end
7166
end
7267

7368
end

lib/cc/services/github_pull_requests.rb

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,6 @@ class Config < CC::Service::Config
1313
validates :oauth_token, presence: true
1414
end
1515

16-
class ResponseAggregator
17-
def initialize(status_response, comment_response)
18-
@status_response = status_response
19-
@comment_response = comment_response
20-
end
21-
22-
def response
23-
return @status_response if @status_response[:ok] && @comment_response[:ok]
24-
message = if !@status_response[:ok] && !@comment_response[:ok]
25-
"Unable to post comment or update status"
26-
elsif !@status_response[:ok]
27-
"Unable to update status: #{@status_response[:message]}"
28-
elsif !@comment_response[:ok]
29-
"Unable to post comment: #{@comment_response[:message]}"
30-
end
31-
{ ok: false, message: message }
32-
end
33-
end
34-
3516
self.title = "GitHub Pull Requests"
3617
self.description = "Update pull requests on GitHub"
3718

@@ -46,11 +27,14 @@ def receive_test
4627
setup_http
4728

4829
if config.update_status && config.add_comment
49-
ResponseAggregator.new(receive_test_status, receive_test_comment).response
30+
receive_test_status
31+
receive_test_comment
5032
elsif config.update_status
5133
receive_test_status
5234
elsif config.add_comment
5335
receive_test_comment
36+
else
37+
{ ok: true, message: "Nothing happened" }
5438
end
5539
end
5640

@@ -76,14 +60,13 @@ def receive_pull_request
7660

7761
def update_status(state, description)
7862
if config.update_status
79-
body = {
63+
params = {
8064
state: state,
8165
description: description,
8266
target_url: @payload["details_url"],
8367
context: "codeclimate"
84-
}.to_json
85-
86-
http_post(status_url, body)
68+
}
69+
post(status_url, params.to_json)
8770
end
8871
end
8972

@@ -93,37 +76,44 @@ def add_comment
9376
body: COMMENT_BODY % @payload["compare_url"]
9477
}.to_json
9578

96-
http_post(comments_url, body)
79+
post(comments_url, body) do |response|
80+
doc = JSON.parse(response.body)
81+
{ id: doc["id"] }
82+
end
9783
end
9884
end
9985

10086
def receive_test_status
101-
http_post(base_status_url("0" * 40), "{}")
102-
103-
rescue HTTPError => ex
104-
if ex.status == 422 # response message: "No commit found for SHA"
105-
{ ok: true, message: "OAuth token is valid" }
106-
else ex.status == 401 # response message: "Bad credentials"
107-
{ ok: false, message: ex.message }
87+
url = base_status_url("0" * 40)
88+
params = {}
89+
raw_post(url, params.to_json)
90+
rescue CC::Service::HTTPError => e
91+
if e.status == 422
92+
{
93+
ok: true,
94+
params: params.as_json,
95+
status: e.status,
96+
endpoint_url: url,
97+
message: "OAuth token is valid"
98+
}
99+
else
100+
raise
108101
end
109-
rescue => ex
110-
{ ok: false, message: ex.message }
111102
end
112103

113104
def receive_test_comment
114-
response = http_get(user_url)
105+
response = get(user_url)
115106
if response_includes_repo_scope?(response)
116107
{ ok: true, message: "OAuth token is valid" }
117108
else
118109
{ ok: false, message: "OAuth token requires 'repo' scope to post comments." }
119110
end
120-
121111
rescue => ex
122112
{ ok: false, message: ex.message }
123113
end
124114

125115
def comment_present?
126-
response = http_get(comments_url)
116+
response = get(comments_url)
127117
comments = JSON.parse(response.body)
128118

129119
comments.any? { |comment| comment["body"] =~ BODY_REGEX }

0 commit comments

Comments
 (0)