gitlab-triage 1.14.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab/merge_request_templates/Release.md +1 -1
- data/README.md +75 -4
- data/bin/gitlab-triage +5 -2
- data/lib/gitlab/triage/action.rb +8 -4
- data/lib/gitlab/triage/action/comment.rb +7 -4
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/summarize.rb +7 -1
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +11 -2
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +13 -50
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +10 -2
- data/lib/gitlab/triage/engine.rb +52 -11
- data/lib/gitlab/triage/errors/network.rb +2 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +1 -3
- data/lib/gitlab/triage/graphql_network.rb +28 -1
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +72 -16
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +25 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
- data/lib/gitlab/triage/network.rb +9 -3
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +33 -10
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +10 -0
- data/lib/gitlab/triage/option_parser.rb +2 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +58 -0
- data/lib/gitlab/triage/policies/base_policy.rb +30 -1
- data/lib/gitlab/triage/resource/context.rb +1 -0
- data/lib/gitlab/triage/resource/epic.rb +24 -0
- data/lib/gitlab/triage/retryable.rb +7 -5
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/params_validator.rb +1 -1
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +10 -5
- data/lib/gitlab/triage/graphql_queries/threads_query.rb +0 -31
- data/lib/gitlab/triage/graphql_queries/user_notes_query.rb +0 -23
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -33,6 +33,10 @@ module Gitlab
|
|
33
33
|
|
34
34
|
DEFAULT_NETWORK_ADAPTER = Gitlab::Triage::NetworkAdapters::HttpartyAdapter
|
35
35
|
DEFAULT_GRAPHQL_ADAPTER = Gitlab::Triage::NetworkAdapters::GraphqlAdapter
|
36
|
+
ALLOWED_STATE_VALUES = {
|
37
|
+
issues: %w[opened closed],
|
38
|
+
merge_requests: %w[opened closed merged]
|
39
|
+
}.with_indifferent_access.freeze
|
36
40
|
|
37
41
|
def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
|
38
42
|
options.host_url = policies.delete(:host_url) { options.host_url }
|
@@ -173,14 +177,23 @@ module Gitlab
|
|
173
177
|
ExpandCondition.perform(rule_conditions(rule)) do |conditions|
|
174
178
|
# retrieving the resources for every rule is inefficient
|
175
179
|
# however, previous rules may affect those upcoming
|
176
|
-
resources =
|
177
|
-
|
180
|
+
resources = []
|
181
|
+
|
182
|
+
if rule[:api] == 'graphql'
|
183
|
+
graphql_query = build_graphql_query(resource_type, conditions, true)
|
184
|
+
resources = graphql_network.query(graphql_query, source: source_full_path)
|
185
|
+
else
|
186
|
+
resources = network.query_api(build_get_url(resource_type, conditions))
|
187
|
+
iids = resources.pluck('iid').map(&:to_s)
|
188
|
+
|
189
|
+
graphql_query = build_graphql_query(resource_type, conditions)
|
190
|
+
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
|
191
|
+
|
192
|
+
decorate_resources_with_graphql_data(resources, graphql_resources)
|
193
|
+
end
|
178
194
|
|
179
|
-
graphql_query = build_graphql_query(resource_type, conditions)
|
180
|
-
graphql_resources = graphql_network.query(graphql_query, source: options.source_id, iids: iids) if graphql_query.present?
|
181
195
|
# In some filters/actions we want to know which resource type it is
|
182
196
|
attach_resource_type(resources, resource_type)
|
183
|
-
decorate_resources_with_graphql_data(resources, graphql_resources)
|
184
197
|
|
185
198
|
puts "\n\n* Found #{resources.count} resources..."
|
186
199
|
print "* Filtering resources..."
|
@@ -195,10 +208,17 @@ module Gitlab
|
|
195
208
|
end
|
196
209
|
end
|
197
210
|
|
198
|
-
# We don't have to do this once the response will contain the type
|
199
|
-
# of the resource. For now let's just attach it.
|
200
211
|
def attach_resource_type(resources, resource_type)
|
201
|
-
resources.each { |resource| resource[:type]
|
212
|
+
resources.each { |resource| resource[:type] = resource_type }
|
213
|
+
# TODO: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
214
|
+
# We should not overwrite the attribute here, but we need to
|
215
|
+
# fix it first. We should instead use something like
|
216
|
+
# gitlab_triage_resource_type so it won't conflict with the
|
217
|
+
# existing fields.
|
218
|
+
# And we need to retain the backward compatibility that using
|
219
|
+
# {{type}} will give us this value, rather than from the REST API,
|
220
|
+
# which will give us ISSUE from:
|
221
|
+
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59648
|
202
222
|
end
|
203
223
|
|
204
224
|
def decorate_resources_with_graphql_data(resources, graphql_resources)
|
@@ -276,7 +296,13 @@ module Gitlab
|
|
276
296
|
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('not[labels]', conditions[:forbidden_labels], ',')
|
277
297
|
end
|
278
298
|
|
279
|
-
|
299
|
+
if conditions[:state]
|
300
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new(
|
301
|
+
'state',
|
302
|
+
conditions[:state],
|
303
|
+
allowed_values: ALLOWED_STATE_VALUES[resource_type])
|
304
|
+
end
|
305
|
+
|
280
306
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('milestone', Array(conditions[:milestone])[0]) if conditions[:milestone]
|
281
307
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('source_branch', conditions[:source_branch]) if conditions[:source_branch]
|
282
308
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('target_branch', conditions[:target_branch]) if conditions[:target_branch]
|
@@ -303,9 +329,24 @@ module Gitlab
|
|
303
329
|
).build
|
304
330
|
end
|
305
331
|
|
306
|
-
def build_graphql_query(resource_type, conditions)
|
332
|
+
def build_graphql_query(resource_type, conditions, graphql_only = false)
|
307
333
|
Gitlab::Triage::GraphqlQueries::QueryBuilder
|
308
|
-
.new(options.source, resource_type, conditions)
|
334
|
+
.new(options.source, resource_type, conditions, graphql_only: graphql_only)
|
335
|
+
end
|
336
|
+
|
337
|
+
def source_full_path
|
338
|
+
@source_full_path ||= fetch_source_full_path
|
339
|
+
end
|
340
|
+
|
341
|
+
def fetch_source_full_path
|
342
|
+
return options.source_id unless /\A\d+\z/.match?(options.source_id)
|
343
|
+
|
344
|
+
source_details = network.query_api(build_get_url(nil, {})).first
|
345
|
+
full_path = source_details['full_path'] || source_details['path_with_namespace']
|
346
|
+
|
347
|
+
raise ArgumentError, 'A source with given source_id was not found!' if full_path.blank?
|
348
|
+
|
349
|
+
full_path
|
309
350
|
end
|
310
351
|
end
|
311
352
|
end
|
@@ -11,6 +11,8 @@ module Gitlab
|
|
11
11
|
class GraphqlNetwork
|
12
12
|
attr_reader :options, :adapter
|
13
13
|
|
14
|
+
MINIMUM_RATE_LIMIT = 25
|
15
|
+
|
14
16
|
def initialize(adapter)
|
15
17
|
@adapter = adapter
|
16
18
|
@options = adapter.options
|
@@ -33,22 +35,47 @@ module Gitlab
|
|
33
35
|
variables: variables.merge(after: response.delete(:end_cursor))
|
34
36
|
)
|
35
37
|
|
38
|
+
rate_limit_debug(response) if options.debug
|
39
|
+
rate_limit_wait(response)
|
40
|
+
|
36
41
|
resources.concat(Array.wrap(response.delete(:results)))
|
37
42
|
end while response.delete(:more_pages)
|
38
43
|
|
39
44
|
resources
|
40
45
|
.map { |resource| resource.deep_transform_keys(&:underscore) }
|
41
46
|
.map(&:with_indifferent_access)
|
42
|
-
.map { |resource|
|
47
|
+
.map { |resource| normalize(resource) }
|
43
48
|
end
|
44
49
|
|
45
50
|
private
|
46
51
|
|
52
|
+
def normalize(resource)
|
53
|
+
resource
|
54
|
+
.slice(:iid, :state, :author, :merged_at, :user_notes_count, :user_discussions_count, :upvotes, :downvotes, :project_id, :web_url)
|
55
|
+
.merge(
|
56
|
+
id: extract_id_from_global_id(resource[:id]),
|
57
|
+
labels: [*resource.dig(:labels, :nodes)].pluck(:title),
|
58
|
+
assignees: [*resource.dig(:assignees, :nodes)]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
47
62
|
def extract_id_from_global_id(global_id)
|
48
63
|
return if global_id.blank?
|
49
64
|
|
50
65
|
GlobalID.parse(global_id).model_id.to_i
|
51
66
|
end
|
67
|
+
|
68
|
+
def rate_limit_debug(response)
|
69
|
+
rate_limit_infos = "Rate limit remaining: #{response[:ratelimit_remaining]} (reset at #{response[:ratelimit_reset_at]})"
|
70
|
+
puts Gitlab::Triage::UI.debug "rate_limit_infos: #{rate_limit_infos}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def rate_limit_wait(response)
|
74
|
+
return unless response.delete(:ratelimit_remaining) < MINIMUM_RATE_LIMIT
|
75
|
+
|
76
|
+
puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{response[:ratelimit_reset_at] - Time.now} seconds" if options.debug
|
77
|
+
sleep(1) until Time.now >= response[:ratelimit_reset_at]
|
78
|
+
end
|
52
79
|
end
|
53
80
|
end
|
54
81
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
1
|
+
require_relative 'query_param_builders/base_param_builder'
|
2
|
+
require_relative 'query_param_builders/date_param_builder'
|
3
|
+
require_relative 'query_param_builders/labels_param_builder'
|
3
4
|
|
4
5
|
module Gitlab
|
5
6
|
module Triage
|
6
7
|
module GraphqlQueries
|
7
8
|
class QueryBuilder
|
8
|
-
def initialize(source_type, resource_type, conditions)
|
9
|
+
def initialize(source_type, resource_type, conditions, graphql_only: false)
|
9
10
|
@source_type = source_type.to_s.singularize
|
10
11
|
@resource_type = resource_type
|
11
12
|
@conditions = conditions
|
13
|
+
@graphql_only = graphql_only
|
12
14
|
end
|
13
15
|
|
14
16
|
def resource_path
|
@@ -16,30 +18,84 @@ module Gitlab
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def query
|
19
|
-
return if
|
21
|
+
return if resource_fields.empty?
|
20
22
|
|
21
|
-
format(
|
23
|
+
format(
|
24
|
+
BASE_QUERY,
|
25
|
+
source_type: source_type,
|
26
|
+
resource_type: resource_type.to_s.camelize(:lower),
|
27
|
+
resource_fields: resource_fields.join(' '),
|
28
|
+
resource_query: resource_query,
|
29
|
+
iids_declaration: graphql_only ? nil : ', $iids: [String!]',
|
30
|
+
iids_query: graphql_only ? nil : ', iids: $iids'
|
31
|
+
)
|
22
32
|
end
|
23
33
|
|
24
|
-
delegate :
|
34
|
+
delegate :any?, to: :resource_fields
|
25
35
|
|
26
36
|
private
|
27
37
|
|
28
|
-
attr_reader :source_type, :resource_type, :conditions
|
38
|
+
attr_reader :source_type, :resource_type, :conditions, :graphql_only
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
BASE_QUERY = <<~GRAPHQL.freeze
|
41
|
+
query($source: ID!, $after: String%{iids_declaration}) {
|
42
|
+
%{source_type}(fullPath: $source) {
|
43
|
+
id
|
44
|
+
%{resource_type}(after: $after%{iids_query}%{resource_query}) {
|
45
|
+
pageInfo {
|
46
|
+
hasNextPage
|
47
|
+
endCursor
|
48
|
+
}
|
49
|
+
nodes {
|
50
|
+
id iid title updatedAt createdAt webUrl projectId %{resource_fields}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
GRAPHQL
|
56
|
+
|
57
|
+
def resource_fields
|
58
|
+
fields = []
|
59
|
+
|
60
|
+
fields << 'userNotesCount' if conditions.dig(:discussions, :attribute).to_s == 'notes'
|
61
|
+
fields << 'userDiscussionsCount' if conditions.dig(:discussions, :attribute).to_s == 'threads'
|
62
|
+
|
63
|
+
if graphql_only
|
64
|
+
fields << 'labels { nodes { title } }'
|
65
|
+
fields << 'author { id name username }'
|
66
|
+
fields << 'assignees { nodes { id name username } }' if conditions.key?(:assignee_member)
|
67
|
+
fields << 'upvotes' if conditions.dig(:upvotes, :attribute).to_s == 'upvotes'
|
68
|
+
fields << 'downvotes' if conditions.dig(:upvotes, :attribute).to_s == 'downvotes'
|
69
|
+
fields << 'mergedAt' if resource_type == 'merge_requests'
|
36
70
|
end
|
71
|
+
|
72
|
+
fields
|
37
73
|
end
|
38
74
|
|
39
|
-
def
|
40
|
-
|
75
|
+
def resource_query
|
76
|
+
condition_queries = []
|
77
|
+
|
78
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('includeSubgroups', true, with_quotes: false) if source_type == 'group'
|
79
|
+
|
80
|
+
conditions.each do |condition, condition_params|
|
81
|
+
condition_queries << QueryParamBuilders::DateParamBuilder.new(condition_params) if condition.to_s == 'date'
|
82
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('milestoneTitle', condition_params) if condition.to_s == 'milestone'
|
83
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('state', condition_params, with_quotes: false) if condition.to_s == 'state'
|
84
|
+
|
85
|
+
if resource_type == 'merge_requests'
|
86
|
+
condition_queries << QueryParamBuilders::LabelsParamBuilder.new('labels', condition_params) if condition.to_s == 'labels'
|
87
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('sourceBranch', condition_params) if condition.to_s == 'source_branch'
|
88
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('targetBranch', condition_params) if condition.to_s == 'target_branch'
|
89
|
+
end
|
90
|
+
|
91
|
+
if resource_type == 'issues'
|
92
|
+
condition_queries << QueryParamBuilders::LabelsParamBuilder.new('labelName', condition_params) if condition.to_s == 'labels'
|
93
|
+
end
|
94
|
+
end
|
41
95
|
|
42
|
-
|
96
|
+
condition_queries
|
97
|
+
.map(&:build_param)
|
98
|
+
.join
|
43
99
|
end
|
44
100
|
end
|
45
101
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../../utils'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module GraphqlQueries
|
6
|
+
module QueryParamBuilders
|
7
|
+
class BaseParamBuilder
|
8
|
+
attr_reader :param_name, :param_contents, :with_quotes
|
9
|
+
|
10
|
+
def initialize(param_name, param_contents, with_quotes: true)
|
11
|
+
@param_name = param_name
|
12
|
+
@param_contents = param_contents.to_s.strip
|
13
|
+
@with_quotes = with_quotes
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_param
|
17
|
+
contents = with_quotes ? Utils.graphql_quote(param_contents) : param_contents
|
18
|
+
|
19
|
+
", #{param_name}: #{contents}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../../param_builders/date_param_builder'
|
2
|
+
require_relative 'base_param_builder'
|
3
|
+
|
4
|
+
module Gitlab
|
5
|
+
module Triage
|
6
|
+
module GraphqlQueries
|
7
|
+
module QueryParamBuilders
|
8
|
+
class DateParamBuilder < BaseParamBuilder
|
9
|
+
ATTRIBUTES = %w[updated_at created_at merged_at].freeze
|
10
|
+
|
11
|
+
def initialize(condition_hash)
|
12
|
+
date_param_builder = ParamBuilders::DateParamBuilder.new(ATTRIBUTES, condition_hash)
|
13
|
+
|
14
|
+
super(build_param_name(condition_hash), date_param_builder.param_content)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_param_name(condition_hash)
|
20
|
+
prefix = condition_hash[:attribute].to_s.sub(/_at\z/, '')
|
21
|
+
suffix =
|
22
|
+
case condition_hash[:condition].to_sym
|
23
|
+
when :older_than
|
24
|
+
'Before'
|
25
|
+
when :newer_than
|
26
|
+
'After'
|
27
|
+
end
|
28
|
+
|
29
|
+
"#{prefix}#{suffix}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../../utils'
|
2
|
+
require_relative 'base_param_builder'
|
3
|
+
|
4
|
+
module Gitlab
|
5
|
+
module Triage
|
6
|
+
module GraphqlQueries
|
7
|
+
module QueryParamBuilders
|
8
|
+
class LabelsParamBuilder < BaseParamBuilder
|
9
|
+
def initialize(param_name, labels)
|
10
|
+
label_param_content = labels.map { |label| Utils.graphql_quote(label) }.join(', ').then { |content| "[#{content}]" }
|
11
|
+
|
12
|
+
super(param_name, label_param_content, with_quotes: false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -33,7 +33,9 @@ module Gitlab
|
|
33
33
|
print '.'
|
34
34
|
url = response.fetch(:next_page_url) { url }
|
35
35
|
|
36
|
-
response = execute_with_retry(
|
36
|
+
response = execute_with_retry(
|
37
|
+
exception_types: [Net::ReadTimeout, Errors::Network::InternalServerError],
|
38
|
+
backoff_exceptions: Errors::Network::TooManyRequests) do
|
37
39
|
puts Gitlab::Triage::UI.debug "query_api: #{url}" if options.debug
|
38
40
|
|
39
41
|
@adapter.get(token, url)
|
@@ -44,8 +46,10 @@ module Gitlab
|
|
44
46
|
case results
|
45
47
|
when Array
|
46
48
|
resources.concat(results)
|
47
|
-
|
49
|
+
when Hash
|
48
50
|
resources << results
|
51
|
+
else
|
52
|
+
raise Errors::Network::UnexpectedResponse, "Unexpected response: #{results.inspect}"
|
49
53
|
end
|
50
54
|
|
51
55
|
rate_limit_debug(response) if options.debug
|
@@ -58,7 +62,9 @@ module Gitlab
|
|
58
62
|
end
|
59
63
|
|
60
64
|
def post_api(url, body)
|
61
|
-
response = execute_with_retry(
|
65
|
+
response = execute_with_retry(
|
66
|
+
exception_types: Net::ReadTimeout,
|
67
|
+
backoff_exceptions: Errors::Network::TooManyRequests) do
|
62
68
|
puts Gitlab::Triage::UI.debug "post_api: #{url}" if options.debug
|
63
69
|
|
64
70
|
@adapter.post(token, url, body)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'httparty'
|
1
2
|
require 'graphql/client'
|
2
3
|
require 'graphql/client/http'
|
3
4
|
|
@@ -17,16 +18,22 @@ module Gitlab
|
|
17
18
|
raise_on_error!(response)
|
18
19
|
|
19
20
|
parsed_response = parse_response(response, resource_path)
|
21
|
+
headers = response.extensions.fetch('headers', {})
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
graphql_response = {
|
24
|
+
ratelimit_remaining: headers['ratelimit-remaining'].to_i,
|
25
|
+
ratelimit_reset_at: Time.at(headers['ratelimit-reset'].to_i)
|
26
|
+
}
|
27
|
+
|
28
|
+
return graphql_response.merge(results: {}) if parsed_response.nil?
|
29
|
+
return graphql_response.merge(results: parsed_response.map(&:to_h)) if parsed_response.is_a?(Client::List)
|
30
|
+
return graphql_response.merge(results: parsed_response.to_h) unless parsed_response.nodes?
|
24
31
|
|
25
|
-
|
32
|
+
graphql_response.merge(
|
26
33
|
more_pages: parsed_response.page_info.has_next_page,
|
27
34
|
end_cursor: parsed_response.page_info.end_cursor,
|
28
35
|
results: parsed_response.nodes.map(&:to_h)
|
29
|
-
|
36
|
+
)
|
30
37
|
end
|
31
38
|
|
32
39
|
delegate :parse, to: :client
|
@@ -47,11 +54,27 @@ module Gitlab
|
|
47
54
|
|
48
55
|
def http_client
|
49
56
|
Client::HTTP.new("#{options.host_url}/api/graphql") do
|
50
|
-
def
|
51
|
-
{
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def execute(document:, operation_name: nil, variables: {}, context: {}) # rubocop:disable Lint/NestedMethodDefinition
|
58
|
+
body = {}
|
59
|
+
body['query'] = document.to_query_string
|
60
|
+
body['variables'] = variables if variables.any?
|
61
|
+
body['operationName'] = operation_name if operation_name
|
62
|
+
|
63
|
+
response = HTTParty.post(
|
64
|
+
uri,
|
65
|
+
body: body.to_json,
|
66
|
+
headers: {
|
67
|
+
'Content-type' => 'application/json',
|
68
|
+
'PRIVATE-TOKEN' => context[:token]
|
69
|
+
}
|
70
|
+
)
|
71
|
+
|
72
|
+
case response.code
|
73
|
+
when 200, 400
|
74
|
+
JSON.parse(response.body).merge('extensions' => { 'headers' => response.headers })
|
75
|
+
else
|
76
|
+
{ 'errors' => [{ 'message' => "#{response.code} #{response.message}" }] }
|
77
|
+
end
|
55
78
|
end
|
56
79
|
end
|
57
80
|
end
|