codeclimate-services 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +121 -0
- data/Rakefile +11 -0
- data/bin/bundler +16 -0
- data/bin/coderay +16 -0
- data/bin/nokogiri +16 -0
- data/bin/pry +16 -0
- data/bin/rake +16 -0
- data/codeclimate-services.gemspec +29 -0
- data/config/cacert.pem +4026 -0
- data/config/load.rb +4 -0
- data/lib/axiom/types/password.rb +7 -0
- data/lib/cc/formatters/linked_formatter.rb +60 -0
- data/lib/cc/formatters/plain_formatter.rb +36 -0
- data/lib/cc/formatters/snapshot_formatter.rb +101 -0
- data/lib/cc/formatters/ticket_formatter.rb +27 -0
- data/lib/cc/helpers/coverage_helper.rb +25 -0
- data/lib/cc/helpers/issue_helper.rb +9 -0
- data/lib/cc/helpers/quality_helper.rb +44 -0
- data/lib/cc/helpers/vulnerability_helper.rb +31 -0
- data/lib/cc/presenters/github_pull_requests_presenter.rb +54 -0
- data/lib/cc/service/config.rb +4 -0
- data/lib/cc/service/formatter.rb +34 -0
- data/lib/cc/service/helper.rb +54 -0
- data/lib/cc/service/http.rb +87 -0
- data/lib/cc/service/invocation/invocation_chain.rb +15 -0
- data/lib/cc/service/invocation/with_error_handling.rb +45 -0
- data/lib/cc/service/invocation/with_metrics.rb +37 -0
- data/lib/cc/service/invocation/with_retries.rb +17 -0
- data/lib/cc/service/invocation/with_return_values.rb +18 -0
- data/lib/cc/service/invocation.rb +57 -0
- data/lib/cc/service/response_check.rb +42 -0
- data/lib/cc/service.rb +127 -0
- data/lib/cc/services/asana.rb +90 -0
- data/lib/cc/services/campfire.rb +55 -0
- data/lib/cc/services/flowdock.rb +61 -0
- data/lib/cc/services/github_issues.rb +80 -0
- data/lib/cc/services/github_pull_requests.rb +210 -0
- data/lib/cc/services/hipchat.rb +57 -0
- data/lib/cc/services/jira.rb +93 -0
- data/lib/cc/services/lighthouse.rb +79 -0
- data/lib/cc/services/pivotal_tracker.rb +78 -0
- data/lib/cc/services/slack.rb +124 -0
- data/lib/cc/services/version.rb +5 -0
- data/lib/cc/services.rb +9 -0
- data/pull_request_test.rb +47 -0
- data/service_test.rb +86 -0
- data/test/asana_test.rb +85 -0
- data/test/axiom/types/password_test.rb +22 -0
- data/test/campfire_test.rb +144 -0
- data/test/fixtures.rb +68 -0
- data/test/flowdock_test.rb +148 -0
- data/test/formatters/snapshot_formatter_test.rb +47 -0
- data/test/github_issues_test.rb +96 -0
- data/test/github_pull_requests_test.rb +293 -0
- data/test/helper.rb +50 -0
- data/test/hipchat_test.rb +130 -0
- data/test/invocation_error_handling_test.rb +51 -0
- data/test/invocation_return_values_test.rb +21 -0
- data/test/invocation_test.rb +167 -0
- data/test/jira_test.rb +80 -0
- data/test/lighthouse_test.rb +74 -0
- data/test/pivotal_tracker_test.rb +73 -0
- data/test/presenters/github_pull_requests_presenter_test.rb +49 -0
- data/test/service_test.rb +63 -0
- data/test/slack_test.rb +222 -0
- data/test/support/fake_logger.rb +11 -0
- data/test/with_metrics_test.rb +19 -0
- metadata +263 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class CC::Service::Invocation
|
2
|
+
class WithRetries
|
3
|
+
def initialize(invocation, retries)
|
4
|
+
@invocation = invocation
|
5
|
+
@retries = retries
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
@invocation.call
|
10
|
+
rescue => ex
|
11
|
+
raise ex if @retries.zero?
|
12
|
+
|
13
|
+
@retries -= 1
|
14
|
+
retry
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CC::Service::Invocation
|
2
|
+
class WithReturnValues
|
3
|
+
def initialize(invocation, message = nil)
|
4
|
+
@invocation = invocation
|
5
|
+
@message = message || "An internal error happened"
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
result = @invocation.call
|
10
|
+
if result.nil?
|
11
|
+
{ ok: false, message: @message }
|
12
|
+
else
|
13
|
+
result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'cc/service/invocation/invocation_chain'
|
2
|
+
require 'cc/service/invocation/with_retries'
|
3
|
+
require 'cc/service/invocation/with_metrics'
|
4
|
+
require 'cc/service/invocation/with_error_handling'
|
5
|
+
require 'cc/service/invocation/with_return_values'
|
6
|
+
|
7
|
+
class CC::Service::Invocation
|
8
|
+
MIDDLEWARE = {
|
9
|
+
retries: WithRetries,
|
10
|
+
metrics: WithMetrics,
|
11
|
+
error_handling: WithErrorHandling,
|
12
|
+
return_values: WithReturnValues,
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_reader :result
|
16
|
+
|
17
|
+
# Build a chain of invocation wrappers which eventually calls receive
|
18
|
+
# on the given service, then execute that chain.
|
19
|
+
#
|
20
|
+
# Order is important. Each call to #with, wraps the last.
|
21
|
+
#
|
22
|
+
# Usage:
|
23
|
+
#
|
24
|
+
# CC::Service::Invocation.invoke(service) do |i|
|
25
|
+
# i.with :retries, 3
|
26
|
+
# i.with :metrics, $statsd
|
27
|
+
# i.with :error_handling, Rails.logger
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# In the above example, service.receive could happen 4 times (once,
|
31
|
+
# then three retries) before an exception is re-raised up to the
|
32
|
+
# metrics collector, then up again to the error handling. If the order
|
33
|
+
# were reversed, the error handling middleware would prevent the other
|
34
|
+
# middleware from seeing any exceptions at all.
|
35
|
+
def self.invoke(service, &block)
|
36
|
+
instance = new(service, &block)
|
37
|
+
instance.result
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(service)
|
41
|
+
@chain = InvocationChain.new { service.receive }
|
42
|
+
|
43
|
+
yield(self) if block_given?
|
44
|
+
|
45
|
+
@result = @chain.call
|
46
|
+
end
|
47
|
+
|
48
|
+
def with(middleware, *args)
|
49
|
+
if klass = MIDDLEWARE[middleware]
|
50
|
+
wrap(klass, *args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def wrap(klass, *args)
|
55
|
+
@chain.wrap(klass, *args)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class CC::Service
|
2
|
+
class HTTPError < StandardError
|
3
|
+
attr_reader :response_body, :status, :params, :endpoint_url
|
4
|
+
attr_accessor :user_message
|
5
|
+
|
6
|
+
def initialize(message, env)
|
7
|
+
@response_body = env[:body]
|
8
|
+
@status = env[:status]
|
9
|
+
@params = env[:params]
|
10
|
+
@endpoint_url = env[:url].to_s
|
11
|
+
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ResponseCheck < Faraday::Response::Middleware
|
17
|
+
ErrorStatuses = 400...600
|
18
|
+
|
19
|
+
def on_complete(env)
|
20
|
+
if ErrorStatuses === env[:status]
|
21
|
+
message = error_message(env) ||
|
22
|
+
"API request unsuccessful (#{env[:status]})"
|
23
|
+
|
24
|
+
raise HTTPError.new(message, env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def error_message(env)
|
31
|
+
# We only handle Jira (or responses which look like Jira's). We will add
|
32
|
+
# more logic here over time to account for other service's typical error
|
33
|
+
# responses as we see them.
|
34
|
+
if env[:response_headers]["content-type"] =~ /application\/json/
|
35
|
+
errors = JSON.parse(env[:body])["errors"]
|
36
|
+
errors.is_a?(Hash) && errors.values.map(&:capitalize).join(", ")
|
37
|
+
end
|
38
|
+
rescue JSON::ParserError
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/cc/service.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module CC
|
2
|
+
class Service
|
3
|
+
require "cc/service/config"
|
4
|
+
require "cc/service/http"
|
5
|
+
require "cc/service/helper"
|
6
|
+
require "cc/service/formatter"
|
7
|
+
require "cc/service/invocation"
|
8
|
+
require "axiom/types/password"
|
9
|
+
|
10
|
+
dir = File.expand_path '../helpers', __FILE__
|
11
|
+
Dir["#{dir}/*_helper.rb"].sort.each do |helper|
|
12
|
+
require helper
|
13
|
+
end
|
14
|
+
|
15
|
+
dir = File.expand_path '../formatters', __FILE__
|
16
|
+
Dir["#{dir}/*_formatter.rb"].sort.each do |formatter|
|
17
|
+
require formatter
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_services
|
21
|
+
path = File.expand_path("../services/**/*.rb", __FILE__)
|
22
|
+
Dir[path].sort.each { |lib| require(lib) }
|
23
|
+
end
|
24
|
+
|
25
|
+
Error = Class.new(StandardError)
|
26
|
+
ConfigurationError = Class.new(Error)
|
27
|
+
|
28
|
+
include HTTP
|
29
|
+
include Helper
|
30
|
+
|
31
|
+
attr_reader :event, :config, :payload
|
32
|
+
|
33
|
+
ALL_EVENTS = %w[test unit coverage quality vulnerability snapshot pull_request issue]
|
34
|
+
|
35
|
+
# Tracks the defined services.
|
36
|
+
def self.services
|
37
|
+
@services ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.inherited(svc)
|
41
|
+
Service.services << svc
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.by_slug(slug)
|
46
|
+
services.detect { |s| s.slug == slug }
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_writer :title
|
51
|
+
attr_accessor :description
|
52
|
+
attr_accessor :issue_tracker
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.title
|
56
|
+
@title ||= begin
|
57
|
+
hook = name.dup
|
58
|
+
hook.sub! /.*:/, ''
|
59
|
+
hook
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.slug
|
64
|
+
@slug ||= begin
|
65
|
+
hook = name.dup
|
66
|
+
hook.downcase!
|
67
|
+
hook.sub! /.*:/, ''
|
68
|
+
hook
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(config, payload)
|
73
|
+
@payload = payload.stringify_keys
|
74
|
+
@config = create_config(config)
|
75
|
+
@event = @payload["name"].to_s
|
76
|
+
|
77
|
+
load_helper
|
78
|
+
validate_event
|
79
|
+
end
|
80
|
+
|
81
|
+
def receive
|
82
|
+
methods = [:receive_event, :"receive_#{event}"]
|
83
|
+
|
84
|
+
methods.each do |method|
|
85
|
+
if respond_to?(method)
|
86
|
+
return public_send(method)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
{ ok: false, ignored: true, message: "No service handler found" }
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def load_helper
|
96
|
+
helper_name = "#{event.classify}Helper"
|
97
|
+
|
98
|
+
if Service.const_defined?(helper_name)
|
99
|
+
@helper = Service.const_get(helper_name)
|
100
|
+
extend @helper
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def validate_event
|
105
|
+
unless ALL_EVENTS.include?(event)
|
106
|
+
raise ArgumentError.new("Invalid event: #{event}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_config(config)
|
111
|
+
config_class.new(config).tap do |c|
|
112
|
+
unless c.valid?
|
113
|
+
raise ConfigurationError, "Invalid config: #{config.inspect}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def config_class
|
119
|
+
if defined?("#{self.class.name}::Config")
|
120
|
+
"#{self.class.name}::Config".constantize
|
121
|
+
else
|
122
|
+
Config
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class CC::Service::Asana < CC::Service
|
2
|
+
class Config < CC::Service::Config
|
3
|
+
attribute :api_key, String, label: "API key"
|
4
|
+
|
5
|
+
attribute :workspace_id, String, label: "Workspace ID"
|
6
|
+
|
7
|
+
attribute :project_id, String, label: "Project ID",
|
8
|
+
description: "(optional)"
|
9
|
+
|
10
|
+
attribute :assignee, String, label: "Assignee",
|
11
|
+
description: "Assignee email address (optional)"
|
12
|
+
|
13
|
+
validates :api_key, presence: true
|
14
|
+
validates :workspace_id, presence: true
|
15
|
+
end
|
16
|
+
|
17
|
+
ENDPOINT = "https://app.asana.com/api/1.0/tasks"
|
18
|
+
|
19
|
+
self.title = "Asana"
|
20
|
+
self.description = "Create tasks in Asana"
|
21
|
+
self.issue_tracker = true
|
22
|
+
|
23
|
+
def receive_test
|
24
|
+
result = create_task("Test task from Code Climate")
|
25
|
+
result.merge(
|
26
|
+
message: "Ticket <a href='#{result[:url]}'>#{result[:id]}</a> created."
|
27
|
+
)
|
28
|
+
rescue CC::Service::HTTPError => ex
|
29
|
+
body = JSON.parse(ex.response_body)
|
30
|
+
ex.user_message = body["errors"].map{|e| e["message"] }.join(" ")
|
31
|
+
raise ex
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive_issue
|
35
|
+
title = %{Fix "#{issue["check_name"]}" issue in #{constant_name}}
|
36
|
+
|
37
|
+
body = [issue["description"], details_url].join("\n\n")
|
38
|
+
|
39
|
+
create_task(title, body)
|
40
|
+
end
|
41
|
+
|
42
|
+
def receive_quality
|
43
|
+
title = "Refactor #{constant_name} from #{rating} on Code Climate"
|
44
|
+
|
45
|
+
create_task("#{title} - #{details_url}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive_vulnerability
|
49
|
+
formatter = CC::Formatters::TicketFormatter.new(self)
|
50
|
+
title = formatter.format_vulnerability_title
|
51
|
+
|
52
|
+
create_task("#{title} - #{details_url}")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def create_task(name, notes = nil)
|
58
|
+
params = generate_params(name, notes)
|
59
|
+
authenticate_http
|
60
|
+
http.headers["Content-Type"] = "application/json"
|
61
|
+
service_post(ENDPOINT, params.to_json) do |response|
|
62
|
+
body = JSON.parse(response.body)
|
63
|
+
id = body['data']['id']
|
64
|
+
url = "https://app.asana.com/0/#{config.workspace_id}/#{id}"
|
65
|
+
{ id: id, url: url }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate_params(name, notes = nil)
|
70
|
+
params = {
|
71
|
+
data: { workspace: config.workspace_id, name: name, notes: notes }
|
72
|
+
}
|
73
|
+
|
74
|
+
if config.project_id.present?
|
75
|
+
# Note this is undocumented, found via trial & error
|
76
|
+
params[:data][:projects] = [config.project_id]
|
77
|
+
end
|
78
|
+
|
79
|
+
if config.assignee.present?
|
80
|
+
params[:data][:assignee] = config.assignee
|
81
|
+
end
|
82
|
+
|
83
|
+
params
|
84
|
+
end
|
85
|
+
|
86
|
+
def authenticate_http
|
87
|
+
http.basic_auth(config.api_key, "")
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class CC::Service::Campfire < CC::Service
|
2
|
+
class Config < CC::Service::Config
|
3
|
+
attribute :subdomain, String,
|
4
|
+
description: "The Campfire subdomain for the account"
|
5
|
+
attribute :token, String,
|
6
|
+
description: "Your Campfire API auth token"
|
7
|
+
attribute :room_id, String,
|
8
|
+
description: "Check your campfire URL for a room ID. Usually 6 digits."
|
9
|
+
|
10
|
+
validates :subdomain, presence: true
|
11
|
+
validates :room_id, presence: true
|
12
|
+
validates :token, presence: true
|
13
|
+
end
|
14
|
+
|
15
|
+
self.description = "Send messages to a Campfire chat room"
|
16
|
+
|
17
|
+
def receive_test
|
18
|
+
speak(formatter.format_test).merge(
|
19
|
+
message: "Test message sent"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive_coverage
|
24
|
+
speak(formatter.format_coverage)
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive_quality
|
28
|
+
speak(formatter.format_quality)
|
29
|
+
end
|
30
|
+
|
31
|
+
def receive_vulnerability
|
32
|
+
speak(formatter.format_vulnerability)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def formatter
|
38
|
+
CC::Formatters::PlainFormatter.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def speak(line)
|
42
|
+
http.headers['Content-Type'] = 'application/json'
|
43
|
+
params = { message: { body: line } }
|
44
|
+
|
45
|
+
http.basic_auth(config.token, "X")
|
46
|
+
service_post(speak_uri, params.to_json)
|
47
|
+
end
|
48
|
+
|
49
|
+
def speak_uri
|
50
|
+
subdomain = config.subdomain
|
51
|
+
room_id = config.room_id
|
52
|
+
"https://#{subdomain}.campfirenow.com/room/#{room_id}/speak.json"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class CC::Service::Flowdock < CC::Service
|
2
|
+
class Config < CC::Service::Config
|
3
|
+
attribute :api_token, String,
|
4
|
+
label: "API Token",
|
5
|
+
description: "The API token of the Flow to send notifications to",
|
6
|
+
link: "https://www.flowdock.com/account/tokens"
|
7
|
+
validates :api_token, presence: true
|
8
|
+
end
|
9
|
+
|
10
|
+
BASE_URL = "https://api.flowdock.com/v1"
|
11
|
+
INVALID_PROJECT_CHARACTERS = /[^0-9a-z\-_ ]+/i
|
12
|
+
|
13
|
+
self.description = "Send messages to a Flowdock inbox"
|
14
|
+
|
15
|
+
def receive_test
|
16
|
+
notify("Test", repo_name, formatter.format_test).merge(
|
17
|
+
message: "Test message sent"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive_coverage
|
22
|
+
notify("Coverage", repo_name, formatter.format_coverage)
|
23
|
+
end
|
24
|
+
|
25
|
+
def receive_quality
|
26
|
+
notify("Quality", repo_name, formatter.format_quality)
|
27
|
+
end
|
28
|
+
|
29
|
+
def receive_vulnerability
|
30
|
+
notify("Vulnerability", repo_name, formatter.format_vulnerability)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def formatter
|
36
|
+
CC::Formatters::LinkedFormatter.new(
|
37
|
+
self,
|
38
|
+
prefix: "",
|
39
|
+
prefix_with_repo: false,
|
40
|
+
link_style: :html
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def notify(subject, project, content)
|
45
|
+
params = {
|
46
|
+
source: "Code Climate",
|
47
|
+
from_address: "hello@codeclimate.com",
|
48
|
+
from_name: "Code Climate",
|
49
|
+
format: "html",
|
50
|
+
subject: subject,
|
51
|
+
project: project.gsub(INVALID_PROJECT_CHARACTERS, ''),
|
52
|
+
content: content,
|
53
|
+
link: "https://codeclimate.com"
|
54
|
+
}
|
55
|
+
|
56
|
+
url = "#{BASE_URL}/messages/team_inbox/#{config.api_token}"
|
57
|
+
http.headers["User-Agent"] = "Code Climate"
|
58
|
+
|
59
|
+
service_post(url, params)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class CC::Service::GitHubIssues < CC::Service
|
2
|
+
class Config < CC::Service::Config
|
3
|
+
attribute :oauth_token, String,
|
4
|
+
label: "OAuth Token",
|
5
|
+
description: "A personal OAuth token with permissions for the repo"
|
6
|
+
attribute :project, String,
|
7
|
+
label: "Project",
|
8
|
+
description: "Project name on GitHub (e.g 'thoughtbot/paperclip')"
|
9
|
+
attribute :labels, String,
|
10
|
+
label: "Labels (comma separated)",
|
11
|
+
description: "Comma separated list of labels to apply to the issue"
|
12
|
+
|
13
|
+
validates :oauth_token, presence: true
|
14
|
+
end
|
15
|
+
|
16
|
+
self.title = "GitHub Issues"
|
17
|
+
self.description = "Open issues on GitHub"
|
18
|
+
self.issue_tracker = true
|
19
|
+
|
20
|
+
BASE_URL = "https://api.github.com"
|
21
|
+
|
22
|
+
def receive_test
|
23
|
+
result = create_issue("Test ticket from Code Climate", "")
|
24
|
+
result.merge(
|
25
|
+
message: "Issue <a href='#{result[:url]}'>##{result[:number]}</a> created."
|
26
|
+
)
|
27
|
+
rescue CC::Service::HTTPError => e
|
28
|
+
body = JSON.parse(e.response_body)
|
29
|
+
e.user_message = body["message"]
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
|
33
|
+
def receive_quality
|
34
|
+
title = "Refactor #{constant_name} from #{rating} on Code Climate"
|
35
|
+
|
36
|
+
create_issue(title, details_url)
|
37
|
+
end
|
38
|
+
|
39
|
+
def receive_vulnerability
|
40
|
+
formatter = CC::Formatters::TicketFormatter.new(self)
|
41
|
+
|
42
|
+
create_issue(
|
43
|
+
formatter.format_vulnerability_title,
|
44
|
+
formatter.format_vulnerability_body
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive_issue
|
49
|
+
title = %{Fix "#{issue["check_name"]}" issue in #{constant_name}}
|
50
|
+
|
51
|
+
body = [issue["description"], details_url].join("\n\n")
|
52
|
+
|
53
|
+
create_issue(title, body)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def create_issue(title, issue_body)
|
59
|
+
params = { title: title, body: issue_body }
|
60
|
+
|
61
|
+
if config.labels.present?
|
62
|
+
params[:labels] = config.labels.split(",").map(&:strip).reject(&:blank?).compact
|
63
|
+
end
|
64
|
+
|
65
|
+
http.headers["Content-Type"] = "application/json"
|
66
|
+
http.headers["Authorization"] = "token #{config.oauth_token}"
|
67
|
+
http.headers["User-Agent"] = "Code Climate"
|
68
|
+
|
69
|
+
url = "#{BASE_URL}/repos/#{config.project}/issues"
|
70
|
+
service_post(url, params.to_json) do |response|
|
71
|
+
body = JSON.parse(response.body)
|
72
|
+
{
|
73
|
+
id: body["id"],
|
74
|
+
number: body["number"],
|
75
|
+
url: body["html_url"]
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|