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
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
172
173
174
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 172
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
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 140
def http_client
Client::HTTP.new("#{options.host_url}/api/graphql") do
def execute(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
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
168
169
170
|
# File 'lib/gitlab/triage/network_adapters/graphql_adapter.rb', line 168
def schema
@schema ||= Client.load_schema(http_client)
end
|