Class: Gitlab::Triage::NetworkAdapters::GraphqlAdapter
- Inherits:
-
BaseAdapter
- Object
- BaseAdapter
- Gitlab::Triage::NetworkAdapters::GraphqlAdapter
show all
- Defined in:
- lib/gitlab/triage/network_adapters/graphql_adapter.rb
Defined Under Namespace
Classes: RawDocument
Constant Summary
collapse
- Client =
GraphQL::Client
Constants inherited
from BaseAdapter
BaseAdapter::USER_AGENT
Instance Attribute Summary
Attributes inherited from BaseAdapter
#options, #tracker
Instance Method Summary
collapse
-
#build_graphql_response(node, headers) ⇒ Object
private
Shared response assembly for both #query and #mutate (parsed path) and #query_raw (raw path).
-
#client ⇒ Object
private
-
#http_client ⇒ Object
private
-
#mutate(graphql_mutation, variables: {}) ⇒ Object
-
#parse_response(response, resource_path) ⇒ Object
private
-
#plain_node(parsed_response) ⇒ Object
private
Converts a parsed GraphQL::Client response node into plain Ruby so the shared builder only ever deals with Hash/Array, never client objects.
-
#query(graphql_query, resource_path: [], variables: {}) ⇒ Object
-
#query_raw(query_string, resource_path: [], variables: {}) ⇒ Object
Executes a raw query string directly through the HTTP layer, skipping GraphQL::Client schema validation (client.parse).
-
#raise_on_error!(response) ⇒ Object
private
-
#rate_limit_remaining(headers) ⇒ Object
private
When the rate-limit headers are absent (e.g. a server that doesn't send them), treat the limit as not-a-concern rather than coercing a missing header to 0/1970, which would otherwise trip rate_limit_wait.
-
#rate_limit_reset_at(headers) ⇒ Object
private
-
#schema ⇒ Object
private
Methods inherited from BaseAdapter
#initialize
Instance Method Details
#build_graphql_response(node, headers) ⇒ Object
Shared response assembly for both #query and #mutate (parsed path) and
#query_raw (raw path). Takes a plain-Ruby node (Hash/Array/nil) and the
rate-limit headers, and produces the canonical response shape:
{ ratelimit_*, [more_pages, end_cursor], results }.
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 79
def build_graphql_response(node, )
response = {
ratelimit_remaining: rate_limit_remaining(),
ratelimit_reset_at: rate_limit_reset_at()
}
return response.merge(results: {}) if node.nil?
if node.is_a?(Hash) && node.key?('nodes')
page_info = node['pageInfo'] || {}
response.merge(
more_pages: page_info['hasNextPage'] || false,
end_cursor: page_info['endCursor'],
results: node['nodes']
)
else
response.merge(results: node)
end
end
|
#client ⇒ Object
176
177
178
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 176
def client
@client ||= Client.new(schema: schema, execute: http_client).tap { |client| client.allow_dynamic_queries = true }
end
|
#http_client ⇒ Object
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 140
def http_client
graphql_tracker = tracker
graphql_url = "#{options.host_url}/api/graphql"
Client::HTTP.new(graphql_url) do
define_method(:execute) do |document:, operation_name: nil, variables: {}, context: {}|
body = {}
body['query'] = document.to_query_string
body['variables'] = variables if variables.any?
body['operationName'] = operation_name if operation_name
response = HTTParty.post(
uri,
body: body.to_json,
headers: {
'User-Agent' => USER_AGENT,
'Content-type' => 'application/json',
'PRIVATE-TOKEN' => context[:token]
}
)
case response.code
when 200, 400
graphql_tracker&.record('POST', graphql_url)
JSON.parse(response.body).merge('extensions' => { 'headers' => response. })
else
{ 'errors' => [{ 'message' => "#{response.code} #{response.message}" }] }
end
end
end
end
|
#mutate(graphql_mutation, variables: {}) ⇒ Object
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 56
def mutate(graphql_mutation, variables: {})
response = client.query(graphql_mutation, variables: variables, context: { token: options.token })
raise_on_error!(response)
parsed_response = response.data
= response.extensions.fetch('headers', {})
{
ratelimit_remaining: ['ratelimit-remaining'].to_i,
ratelimit_reset_at: Time.at(['ratelimit-reset'].to_i),
results: parsed_response.to_h
}
end
|
#parse_response(response, resource_path) ⇒ Object
128
129
130
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 128
def parse_response(response, resource_path)
resource_path.reduce(response.data) { |data, resource| data&.send(resource) } end
|
#plain_node(parsed_response) ⇒ Object
Converts a parsed GraphQL::Client response node into plain Ruby so the
shared builder only ever deals with Hash/Array, never client objects.
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 114
def plain_node(parsed_response)
return if parsed_response.nil?
return parsed_response.map(&:to_h) if parsed_response.is_a?(Client::List)
return parsed_response.to_h unless parsed_response.nodes?
{
'nodes' => parsed_response.nodes.map(&:to_h),
'pageInfo' => {
'hasNextPage' => parsed_response.page_info.has_next_page,
'endCursor' => parsed_response.page_info.end_cursor
}
}
end
|
#query(graphql_query, resource_path: [], variables: {}) ⇒ Object
22
23
24
25
26
27
28
29
30
31
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 22
def query(graphql_query, resource_path: [], variables: {})
response = client.query(graphql_query, variables: variables, context: { token: options.token })
raise_on_error!(response)
= response.extensions.fetch('headers', {})
node = plain_node(parse_response(response, resource_path))
build_graphql_response(node, )
end
|
#query_raw(query_string, resource_path: [], variables: {}) ⇒ Object
Executes a raw query string directly through the HTTP layer, skipping
GraphQL::Client schema validation (client.parse). Required for queries
that reference experiment-tagged fields/arguments: GitLab hides those
from introspection but executes them at runtime, so the client-side
validator would otherwise reject a perfectly valid query.
Returns the same shape as #query so callers can be source-agnostic.
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 40
def query_raw(query_string, resource_path: [], variables: {})
raw = http_client.execute(
document: RawDocument.new(query_string),
variables: variables,
context: { token: options.token }
)
errors = raw['errors']
raise "There was an error: #{errors.to_json}" if errors.present?
= raw.dig('extensions', 'headers') || {}
node = resource_path.reduce(raw['data']) { |data, segment| data&.fetch(segment, nil) }
build_graphql_response(node, )
end
|
#raise_on_error!(response) ⇒ Object
132
133
134
135
136
137
138
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 132
def raise_on_error!(response)
return if response.errors.blank?
puts Gitlab::Triage::UI.debug response.inspect if options.debug
raise "There was an error: #{response.errors.messages.to_json}"
end
|
#rate_limit_remaining(headers) ⇒ Object
When the rate-limit headers are absent (e.g. a server that doesn't send
them), treat the limit as not-a-concern rather than coercing a missing
header to 0/1970, which would otherwise trip rate_limit_wait.
102
103
104
105
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 102
def rate_limit_remaining()
value = ['ratelimit-remaining']
value.present? ? value.to_i : Float::INFINITY
end
|
#rate_limit_reset_at(headers) ⇒ Object
107
108
109
110
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 107
def rate_limit_reset_at()
value = ['ratelimit-reset']
Time.at(value.to_i) if value.present?
end
|
#schema ⇒ Object
172
173
174
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 172
def schema
@schema ||= Client.load_schema(http_client)
end
|