gitlab-triage 1.11.0 → 1.14.2
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/CODEOWNERS +1 -1
- data/.gitlab/merge_request_templates/Release.md +1 -1
- data/README.md +40 -0
- data/gitlab-triage.gemspec +2 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +11 -2
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +10 -2
- data/lib/gitlab/triage/engine.rb +53 -2
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +60 -0
- data/lib/gitlab/triage/graphql_network.rb +54 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +47 -0
- data/lib/gitlab/triage/graphql_queries/threads_query.rb +31 -0
- data/lib/gitlab/triage/graphql_queries/user_notes_query.rb +23 -0
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +69 -0
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +2 -11
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +2 -11
- data/lib/gitlab/triage/url_builders/url_builder.rb +7 -3
- data/lib/gitlab/triage/validators/params_validator.rb +1 -1
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +37 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c40a53d7edad2e8472cb3a7a503af2d45d31dfb4b7192e12b170016cd47b0b23
|
4
|
+
data.tar.gz: 35c4cfc534faee10e6d1d02b6108d3300ad4cee20883e6e1299d97c5315a0cde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27424e4cab78096d186c11b9508340b50f70b04fdf9ee942bc1063dd3e4a0b14e4f10ebe7651947f3e32bd891cff98eb33db2315cd021aaef86e1f38e3dc9552
|
7
|
+
data.tar.gz: 3c4e60ac07b46c9c755d8b85c39fed6addc39370b268885090e7cf822456877e1f3bf241adf7288c5f5df6cda802284ca05cfa8c91d79d92dc04086c36d8a182
|
data/.gitlab/CODEOWNERS
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# The official maintainers
|
2
|
-
* @rymai @godfat @markglenfletcher
|
2
|
+
* @rymai @godfat-gitlab @markglenfletcher
|
@@ -32,4 +32,4 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-triage/commits/
|
|
32
32
|
- Checklist after merging:
|
33
33
|
- [ ] [Update the release notes for the newly created tag](docs/release_process.md#how-to).
|
34
34
|
|
35
|
-
/label ~"Engineering Productivity" ~"ep::triage" ~"tooling::workflow"
|
35
|
+
/label ~"Engineering Productivity" ~"ep::triage" ~tooling ~"tooling::workflow"
|
data/README.md
CHANGED
@@ -141,6 +141,8 @@ Available condition types:
|
|
141
141
|
- [`assignee_member` condition](#assignee-member-condition)
|
142
142
|
- [`source_branch` condition](#source-branch-condition)
|
143
143
|
- [`target_branch` condition](#target-branch-condition)
|
144
|
+
- [`weight` condition](#weight-condition)
|
145
|
+
- [`discussions` condition](#discussions-condition)
|
144
146
|
- [`ruby` condition](#ruby-condition)
|
145
147
|
|
146
148
|
##### Date condition
|
@@ -467,6 +469,44 @@ conditions:
|
|
467
469
|
target_branch: 'master'
|
468
470
|
```
|
469
471
|
|
472
|
+
##### Weight condition
|
473
|
+
|
474
|
+
Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues).
|
475
|
+
This condition is only applicable for issues (not merge requests).
|
476
|
+
|
477
|
+
| State | Type | Value |
|
478
|
+
| --------- | ---- | ------ |
|
479
|
+
| Any weight | string | `Any` |
|
480
|
+
| No weight | string | `None` |
|
481
|
+
| Specific weight | integer | integer |
|
482
|
+
|
483
|
+
Example:
|
484
|
+
|
485
|
+
```yml
|
486
|
+
conditions:
|
487
|
+
weight: Any
|
488
|
+
```
|
489
|
+
|
490
|
+
##### Discussions condition
|
491
|
+
|
492
|
+
Accepts a hash of fields.
|
493
|
+
|
494
|
+
| Field | Type | Values | Required |
|
495
|
+
| --------- | ---- | ---- | -------- |
|
496
|
+
| `attribute` | string | `threads`, `notes` | yes |
|
497
|
+
| `condition` | string | `less_than`, `greater_than` | yes |
|
498
|
+
| `threshold` | integer | integer | yes |
|
499
|
+
|
500
|
+
Example:
|
501
|
+
|
502
|
+
```yml
|
503
|
+
conditions:
|
504
|
+
discussions:
|
505
|
+
attribute: threads
|
506
|
+
condition: greater_than
|
507
|
+
threshold: 15
|
508
|
+
```
|
509
|
+
|
470
510
|
##### Ruby condition
|
471
511
|
|
472
512
|
This condition allows users to write a Ruby expression to be evaluated for
|
data/gitlab-triage.gemspec
CHANGED
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
22
|
spec.add_dependency 'activesupport', '~> 5.1'
|
23
|
+
spec.add_dependency 'globalid', '~> 0.4'
|
24
|
+
spec.add_dependency 'graphql-client', '~> 0.16'
|
23
25
|
spec.add_dependency 'httparty', '~> 0.17'
|
24
26
|
|
25
27
|
spec.add_development_dependency 'bundler'
|
@@ -2,16 +2,25 @@ module Gitlab
|
|
2
2
|
module Triage
|
3
3
|
module APIQueryBuilders
|
4
4
|
class BaseQueryParamBuilder
|
5
|
-
attr_reader :param_name, :param_contents
|
5
|
+
attr_reader :param_name, :param_contents, :allowed_values
|
6
6
|
|
7
|
-
def initialize(param_name, param_contents)
|
7
|
+
def initialize(param_name, param_contents, allowed_values: nil)
|
8
8
|
@param_name = param_name
|
9
9
|
@param_contents = param_contents
|
10
|
+
@allowed_values = allowed_values
|
11
|
+
|
12
|
+
validate_allowed_values! if allowed_values
|
10
13
|
end
|
11
14
|
|
12
15
|
def build_param
|
13
16
|
"&#{param_name}=#{param_content.strip}"
|
14
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_allowed_values!
|
22
|
+
ParamsValidator.new([{ name: param_name, type: String, values: allowed_values }], { param_name => param_contents }).validate!
|
23
|
+
end
|
15
24
|
end
|
16
25
|
end
|
17
26
|
end
|
@@ -6,14 +6,22 @@ module Gitlab
|
|
6
6
|
class MultiQueryParamBuilder < BaseQueryParamBuilder
|
7
7
|
attr_reader :separator
|
8
8
|
|
9
|
-
def initialize(param_name, param_contents, separator)
|
9
|
+
def initialize(param_name, param_contents, separator, allowed_values: nil)
|
10
10
|
@separator = separator
|
11
|
-
super(param_name, param_contents)
|
11
|
+
super(param_name, Array(param_contents), allowed_values: allowed_values)
|
12
12
|
end
|
13
13
|
|
14
14
|
def param_content
|
15
15
|
param_contents.map(&:strip).join(separator)
|
16
16
|
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_allowed_values!
|
21
|
+
param_contents.each do |param|
|
22
|
+
ParamsValidator.new([{ name: param_name, type: String, values: allowed_values }], { param_name => param }).validate!
|
23
|
+
end
|
24
|
+
end
|
17
25
|
end
|
18
26
|
end
|
19
27
|
end
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -7,6 +7,7 @@ require_relative 'filters/votes_conditions_filter'
|
|
7
7
|
require_relative 'filters/no_additional_labels_conditions_filter'
|
8
8
|
require_relative 'filters/author_member_conditions_filter'
|
9
9
|
require_relative 'filters/assignee_member_conditions_filter'
|
10
|
+
require_relative 'filters/discussions_conditions_filter'
|
10
11
|
require_relative 'filters/ruby_conditions_filter'
|
11
12
|
require_relative 'limiters/date_field_limiter'
|
12
13
|
require_relative 'action'
|
@@ -19,7 +20,10 @@ require_relative 'api_query_builders/single_query_param_builder'
|
|
19
20
|
require_relative 'api_query_builders/multi_query_param_builder'
|
20
21
|
require_relative 'url_builders/url_builder'
|
21
22
|
require_relative 'network'
|
23
|
+
require_relative 'graphql_network'
|
22
24
|
require_relative 'network_adapters/httparty_adapter'
|
25
|
+
require_relative 'network_adapters/graphql_adapter'
|
26
|
+
require_relative 'graphql_queries/query_builder'
|
23
27
|
require_relative 'ui'
|
24
28
|
|
25
29
|
module Gitlab
|
@@ -27,7 +31,14 @@ module Gitlab
|
|
27
31
|
class Engine
|
28
32
|
attr_reader :per_page, :policies, :options
|
29
33
|
|
30
|
-
|
34
|
+
DEFAULT_NETWORK_ADAPTER = Gitlab::Triage::NetworkAdapters::HttpartyAdapter
|
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
|
40
|
+
|
41
|
+
def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
|
31
42
|
options.host_url = policies.delete(:host_url) { options.host_url }
|
32
43
|
options.api_version = policies.delete(:api_version) { 'v4' }
|
33
44
|
options.dry_run = ENV['TEST'] == 'true' if options.dry_run.nil?
|
@@ -36,6 +47,7 @@ module Gitlab
|
|
36
47
|
@policies = policies
|
37
48
|
@options = options
|
38
49
|
@network_adapter_class = network_adapter_class
|
50
|
+
@graphql_network_adapter_class = graphql_network_adapter_class
|
39
51
|
|
40
52
|
assert_all!
|
41
53
|
assert_project_id!
|
@@ -62,6 +74,10 @@ module Gitlab
|
|
62
74
|
@network ||= Network.new(network_adapter)
|
63
75
|
end
|
64
76
|
|
77
|
+
def graphql_network
|
78
|
+
@graphql_network ||= GraphqlNetwork.new(graphql_network_adapter)
|
79
|
+
end
|
80
|
+
|
65
81
|
private
|
66
82
|
|
67
83
|
def assert_project_id!
|
@@ -94,6 +110,10 @@ module Gitlab
|
|
94
110
|
@network_adapter ||= @network_adapter_class.new(options)
|
95
111
|
end
|
96
112
|
|
113
|
+
def graphql_network_adapter
|
114
|
+
@graphql_network_adapter ||= @graphql_network_adapter_class.new(options)
|
115
|
+
end
|
116
|
+
|
97
117
|
def rule_conditions(rule)
|
98
118
|
rule.fetch(:conditions) { {} }
|
99
119
|
end
|
@@ -158,8 +178,13 @@ module Gitlab
|
|
158
178
|
# retrieving the resources for every rule is inefficient
|
159
179
|
# however, previous rules may affect those upcoming
|
160
180
|
resources = network.query_api(build_get_url(resource_type, conditions))
|
181
|
+
iids = resources.pluck('iid').map(&:to_s)
|
182
|
+
|
183
|
+
graphql_query = build_graphql_query(resource_type, conditions)
|
184
|
+
graphql_resources = graphql_network.query(graphql_query, source: options.source_id, iids: iids) if graphql_query.present?
|
161
185
|
# In some filters/actions we want to know which resource type it is
|
162
186
|
attach_resource_type(resources, resource_type)
|
187
|
+
decorate_resources_with_graphql_data(resources, graphql_resources)
|
163
188
|
|
164
189
|
puts "\n\n* Found #{resources.count} resources..."
|
165
190
|
print "* Filtering resources..."
|
@@ -180,6 +205,13 @@ module Gitlab
|
|
180
205
|
resources.each { |resource| resource[:type] ||= resource_type }
|
181
206
|
end
|
182
207
|
|
208
|
+
def decorate_resources_with_graphql_data(resources, graphql_resources)
|
209
|
+
return if graphql_resources.nil?
|
210
|
+
|
211
|
+
graphql_resources_by_id = graphql_resources.to_h { |resource| [resource[:id], resource] }
|
212
|
+
resources.each { |resource| resource.merge!(graphql_resources_by_id[resource[:id]].to_h) }
|
213
|
+
end
|
214
|
+
|
183
215
|
def process_action(policy)
|
184
216
|
Action.process(
|
185
217
|
policy: policy,
|
@@ -213,6 +245,10 @@ module Gitlab
|
|
213
245
|
results << Filters::AssigneeMemberConditionsFilter.new(resource, conditions[:assignee_member], network).calculate
|
214
246
|
end
|
215
247
|
|
248
|
+
if conditions[:discussions]
|
249
|
+
results << Filters::DiscussionsConditionsFilter.new(resource, conditions[:discussions]).calculate
|
250
|
+
end
|
251
|
+
|
216
252
|
if conditions[:ruby]
|
217
253
|
results << Filters::RubyConditionsFilter.new(resource, conditions, network).calculate
|
218
254
|
end
|
@@ -244,7 +280,13 @@ module Gitlab
|
|
244
280
|
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('not[labels]', conditions[:forbidden_labels], ',')
|
245
281
|
end
|
246
282
|
|
247
|
-
|
283
|
+
if conditions[:state]
|
284
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new(
|
285
|
+
'state',
|
286
|
+
conditions[:state],
|
287
|
+
allowed_values: ALLOWED_STATE_VALUES[resource_type])
|
288
|
+
end
|
289
|
+
|
248
290
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('milestone', Array(conditions[:milestone])[0]) if conditions[:milestone]
|
249
291
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('source_branch', conditions[:source_branch]) if conditions[:source_branch]
|
250
292
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('target_branch', conditions[:target_branch]) if conditions[:target_branch]
|
@@ -253,6 +295,10 @@ module Gitlab
|
|
253
295
|
condition_builders << APIQueryBuilders::DateQueryParamBuilder.new(conditions.delete(:date))
|
254
296
|
end
|
255
297
|
|
298
|
+
if conditions[:weight] && resource_type.to_sym == :issues
|
299
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('weight', conditions[:weight])
|
300
|
+
end
|
301
|
+
|
256
302
|
condition_builders.each do |condition_builder|
|
257
303
|
params[condition_builder.param_name] = condition_builder.param_content
|
258
304
|
end
|
@@ -266,6 +312,11 @@ module Gitlab
|
|
266
312
|
params: params
|
267
313
|
).build
|
268
314
|
end
|
315
|
+
|
316
|
+
def build_graphql_query(resource_type, conditions)
|
317
|
+
Gitlab::Triage::GraphqlQueries::QueryBuilder
|
318
|
+
.new(options.source, resource_type, conditions)
|
319
|
+
end
|
269
320
|
end
|
270
321
|
end
|
271
322
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative 'base_conditions_filter'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module Filters
|
6
|
+
class DiscussionsConditionsFilter < BaseConditionsFilter
|
7
|
+
ATTRIBUTES = %w[notes threads].freeze
|
8
|
+
CONDITIONS = %w[greater_than less_than].freeze
|
9
|
+
|
10
|
+
def self.filter_parameters
|
11
|
+
[
|
12
|
+
{
|
13
|
+
name: :attribute,
|
14
|
+
type: String,
|
15
|
+
values: ATTRIBUTES
|
16
|
+
},
|
17
|
+
{
|
18
|
+
name: :condition,
|
19
|
+
type: String,
|
20
|
+
values: CONDITIONS
|
21
|
+
},
|
22
|
+
{
|
23
|
+
name: :threshold,
|
24
|
+
type: Numeric
|
25
|
+
}
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize_variables(condition)
|
30
|
+
@attribute = condition[:attribute].to_sym
|
31
|
+
@condition = condition[:condition].to_sym
|
32
|
+
@threshold = condition[:threshold]
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource_value
|
36
|
+
if @attribute == :notes
|
37
|
+
@resource[:user_notes_count]
|
38
|
+
else
|
39
|
+
@resource.dig(:discussions, :nodes)&.count do |node|
|
40
|
+
!node&.dig(:notes, :nodes, 0, :system)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def condition_value
|
46
|
+
@threshold
|
47
|
+
end
|
48
|
+
|
49
|
+
def calculate
|
50
|
+
case @condition
|
51
|
+
when :greater_than
|
52
|
+
resource_value.to_i > condition_value
|
53
|
+
when :less_than
|
54
|
+
resource_value.to_i < condition_value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'net/protocol'
|
3
|
+
require 'globalid'
|
4
|
+
|
5
|
+
require_relative 'retryable'
|
6
|
+
require_relative 'ui'
|
7
|
+
require_relative 'errors'
|
8
|
+
|
9
|
+
module Gitlab
|
10
|
+
module Triage
|
11
|
+
class GraphqlNetwork
|
12
|
+
attr_reader :options, :adapter
|
13
|
+
|
14
|
+
def initialize(adapter)
|
15
|
+
@adapter = adapter
|
16
|
+
@options = adapter.options
|
17
|
+
end
|
18
|
+
|
19
|
+
def query(graphql_query, variables = {})
|
20
|
+
return if graphql_query.blank?
|
21
|
+
|
22
|
+
response = {}
|
23
|
+
resources = []
|
24
|
+
|
25
|
+
parsed_graphql_query = adapter.parse(graphql_query.query)
|
26
|
+
|
27
|
+
begin
|
28
|
+
print '.'
|
29
|
+
|
30
|
+
response = adapter.query(
|
31
|
+
parsed_graphql_query,
|
32
|
+
resource_path: graphql_query.resource_path,
|
33
|
+
variables: variables.merge(after: response.delete(:end_cursor))
|
34
|
+
)
|
35
|
+
|
36
|
+
resources.concat(Array.wrap(response.delete(:results)))
|
37
|
+
end while response.delete(:more_pages)
|
38
|
+
|
39
|
+
resources
|
40
|
+
.map { |resource| resource.deep_transform_keys(&:underscore) }
|
41
|
+
.map(&:with_indifferent_access)
|
42
|
+
.map { |resource| resource.merge(id: extract_id_from_global_id(resource[:id])) }
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def extract_id_from_global_id(global_id)
|
48
|
+
return if global_id.blank?
|
49
|
+
|
50
|
+
GlobalID.parse(global_id).model_id.to_i
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'user_notes_query'
|
2
|
+
require_relative 'threads_query'
|
3
|
+
|
4
|
+
module Gitlab
|
5
|
+
module Triage
|
6
|
+
module GraphqlQueries
|
7
|
+
class QueryBuilder
|
8
|
+
def initialize(source_type, resource_type, conditions)
|
9
|
+
@source_type = source_type.to_s.singularize
|
10
|
+
@resource_type = resource_type
|
11
|
+
@conditions = conditions
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource_path
|
15
|
+
[source_type, resource_type]
|
16
|
+
end
|
17
|
+
|
18
|
+
def query
|
19
|
+
return if query_template.nil?
|
20
|
+
|
21
|
+
format(query_template, source_type: source_type, resource_type: resource_type.to_s.camelize(:lower), group_query: group_query)
|
22
|
+
end
|
23
|
+
|
24
|
+
delegate :present?, to: :query_template
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :source_type, :resource_type, :conditions
|
29
|
+
|
30
|
+
def query_template
|
31
|
+
case conditions.dig(:discussions, :attribute).to_s
|
32
|
+
when 'notes'
|
33
|
+
UserNotesQuery
|
34
|
+
when 'threads'
|
35
|
+
ThreadsQuery
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def group_query
|
40
|
+
return if source_type != 'group'
|
41
|
+
|
42
|
+
', includeSubgroups: true'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Triage
|
3
|
+
module GraphqlQueries
|
4
|
+
ThreadsQuery = <<-GRAPHQL.freeze # rubocop:disable Naming/ConstantName
|
5
|
+
query($source: ID!, $after: String, $iids: [String!]) {
|
6
|
+
%{source_type}(fullPath: $source) {
|
7
|
+
id
|
8
|
+
%{resource_type}(after: $after, iids: $iids%{group_query}) {
|
9
|
+
pageInfo {
|
10
|
+
hasNextPage
|
11
|
+
endCursor
|
12
|
+
}
|
13
|
+
nodes {
|
14
|
+
id
|
15
|
+
discussions {
|
16
|
+
nodes {
|
17
|
+
notes {
|
18
|
+
nodes {
|
19
|
+
system
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
GRAPHQL
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Triage
|
3
|
+
module GraphqlQueries
|
4
|
+
UserNotesQuery = <<-GRAPHQL.freeze # rubocop:disable Naming/ConstantName
|
5
|
+
query($source: ID!, $after: String, $iids: [String!]) {
|
6
|
+
%{source_type}(fullPath: $source) {
|
7
|
+
id
|
8
|
+
%{resource_type}(after: $after, iids: $iids%{group_query}) {
|
9
|
+
pageInfo {
|
10
|
+
hasNextPage
|
11
|
+
endCursor
|
12
|
+
}
|
13
|
+
nodes {
|
14
|
+
id
|
15
|
+
userNotesCount
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
GRAPHQL
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'graphql/client'
|
2
|
+
require 'graphql/client/http'
|
3
|
+
|
4
|
+
require_relative 'base_adapter'
|
5
|
+
require_relative '../ui'
|
6
|
+
require_relative '../errors'
|
7
|
+
|
8
|
+
module Gitlab
|
9
|
+
module Triage
|
10
|
+
module NetworkAdapters
|
11
|
+
class GraphqlAdapter < BaseAdapter
|
12
|
+
Client = GraphQL::Client
|
13
|
+
|
14
|
+
def query(graphql_query, resource_path: [], variables: {})
|
15
|
+
response = client.query(graphql_query, variables: variables, context: { token: options.token })
|
16
|
+
|
17
|
+
raise_on_error!(response)
|
18
|
+
|
19
|
+
parsed_response = parse_response(response, resource_path)
|
20
|
+
|
21
|
+
return { results: {} } if parsed_response.nil?
|
22
|
+
return { results: parsed_response.map(&:to_h) } if parsed_response.is_a?(Client::List)
|
23
|
+
return { results: parsed_response.to_h } unless parsed_response.nodes?
|
24
|
+
|
25
|
+
{
|
26
|
+
more_pages: parsed_response.page_info.has_next_page,
|
27
|
+
end_cursor: parsed_response.page_info.end_cursor,
|
28
|
+
results: parsed_response.nodes.map(&:to_h)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
delegate :parse, to: :client
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parse_response(response, resource_path)
|
37
|
+
resource_path.reduce(response.data) { |data, resource| data&.send(resource) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def raise_on_error!(response)
|
41
|
+
return if response.errors.blank?
|
42
|
+
|
43
|
+
puts Gitlab::Triage::UI.debug response.inspect if options.debug
|
44
|
+
|
45
|
+
raise "There was an error: #{response.errors.messages.to_json}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def http_client
|
49
|
+
Client::HTTP.new("#{options.host_url}/api/graphql") do
|
50
|
+
def headers(context) # rubocop:disable Lint/NestedMethodDefinition
|
51
|
+
{
|
52
|
+
'Content-type' => 'application/json',
|
53
|
+
'PRIVATE-TOKEN' => context[:token]
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def schema
|
60
|
+
@schema ||= Client.load_schema(http_client)
|
61
|
+
end
|
62
|
+
|
63
|
+
def client
|
64
|
+
@client ||= Client.new(schema: schema, execute: http_client).tap { |client| client.allow_dynamic_queries = true }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,20 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'delegate'
|
4
4
|
|
5
5
|
module Gitlab
|
6
6
|
module Triage
|
7
7
|
module PoliciesResources
|
8
|
-
|
9
|
-
include Enumerable
|
10
|
-
extend Forwardable
|
11
|
-
|
12
|
-
def initialize(new_resources)
|
13
|
-
@resources = new_resources
|
14
|
-
end
|
15
|
-
|
16
|
-
def_delegator :@resources, :each
|
17
|
-
end
|
8
|
+
RuleResources = Class.new(SimpleDelegator)
|
18
9
|
end
|
19
10
|
end
|
20
11
|
end
|
@@ -1,20 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'delegate'
|
4
4
|
|
5
5
|
module Gitlab
|
6
6
|
module Triage
|
7
7
|
module PoliciesResources
|
8
|
-
|
9
|
-
include Enumerable
|
10
|
-
extend Forwardable
|
11
|
-
|
12
|
-
def initialize(new_rule_to_resources)
|
13
|
-
@rule_to_resources = new_rule_to_resources
|
14
|
-
end
|
15
|
-
|
16
|
-
def_delegator :@rule_to_resources, :each
|
17
|
-
end
|
8
|
+
SummaryResources = Class.new(SimpleDelegator)
|
18
9
|
end
|
19
10
|
end
|
20
11
|
end
|
@@ -17,7 +17,7 @@ module Gitlab
|
|
17
17
|
|
18
18
|
def build
|
19
19
|
url = base_url
|
20
|
-
url << "/#{
|
20
|
+
url << "/#{percent_encode(@resource_id.to_s)}" if @resource_id
|
21
21
|
url << "/#{@sub_resource_type}" if @sub_resource_type
|
22
22
|
url << params_string if @params
|
23
23
|
url
|
@@ -31,16 +31,20 @@ module Gitlab
|
|
31
31
|
|
32
32
|
def base_url
|
33
33
|
url = host_with_api_url
|
34
|
-
url << "/#{@source}/#{
|
34
|
+
url << "/#{@source}/#{percent_encode(@source_id.to_s)}" unless @all
|
35
35
|
url << "/#{@resource_type}" if @resource_type
|
36
36
|
url
|
37
37
|
end
|
38
38
|
|
39
39
|
def params_string
|
40
40
|
"?" << @params.map do |k, v|
|
41
|
-
"#{k}=#{v}"
|
41
|
+
"#{percent_encode(k.to_s)}=#{percent_encode(v.to_s)}"
|
42
42
|
end.join("&")
|
43
43
|
end
|
44
|
+
|
45
|
+
def percent_encode(str)
|
46
|
+
CGI.escape(str).gsub('+', '%20')
|
47
|
+
end
|
44
48
|
end
|
45
49
|
end
|
46
50
|
end
|
@@ -34,7 +34,7 @@ module Gitlab
|
|
34
34
|
def validate_parameter_content(value)
|
35
35
|
@parameter_definitions.each do |param|
|
36
36
|
if param[:values]
|
37
|
-
raise InvalidParameter, "#{param[:name]} must be
|
37
|
+
raise InvalidParameter, "#{param[:name]} must be one of #{param[:values].join(',')}" unless param[:values].include?(value[param[:name]])
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-triage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: globalid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: graphql-client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.16'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.16'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: httparty
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,16 +186,22 @@ files:
|
|
158
186
|
- lib/gitlab/triage/filters/assignee_member_conditions_filter.rb
|
159
187
|
- lib/gitlab/triage/filters/author_member_conditions_filter.rb
|
160
188
|
- lib/gitlab/triage/filters/base_conditions_filter.rb
|
189
|
+
- lib/gitlab/triage/filters/discussions_conditions_filter.rb
|
161
190
|
- lib/gitlab/triage/filters/member_conditions_filter.rb
|
162
191
|
- lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb
|
163
192
|
- lib/gitlab/triage/filters/name_conditions_filter.rb
|
164
193
|
- lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb
|
165
194
|
- lib/gitlab/triage/filters/ruby_conditions_filter.rb
|
166
195
|
- lib/gitlab/triage/filters/votes_conditions_filter.rb
|
196
|
+
- lib/gitlab/triage/graphql_network.rb
|
197
|
+
- lib/gitlab/triage/graphql_queries/query_builder.rb
|
198
|
+
- lib/gitlab/triage/graphql_queries/threads_query.rb
|
199
|
+
- lib/gitlab/triage/graphql_queries/user_notes_query.rb
|
167
200
|
- lib/gitlab/triage/limiters/base_limiter.rb
|
168
201
|
- lib/gitlab/triage/limiters/date_field_limiter.rb
|
169
202
|
- lib/gitlab/triage/network.rb
|
170
203
|
- lib/gitlab/triage/network_adapters/base_adapter.rb
|
204
|
+
- lib/gitlab/triage/network_adapters/graphql_adapter.rb
|
171
205
|
- lib/gitlab/triage/network_adapters/httparty_adapter.rb
|
172
206
|
- lib/gitlab/triage/network_adapters/test_adapter.rb
|
173
207
|
- lib/gitlab/triage/option_parser.rb
|
@@ -213,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
213
247
|
- !ruby/object:Gem::Version
|
214
248
|
version: '0'
|
215
249
|
requirements: []
|
216
|
-
rubygems_version: 3.1.
|
250
|
+
rubygems_version: 3.1.4
|
217
251
|
signing_key:
|
218
252
|
specification_version: 4
|
219
253
|
summary: GitLab triage automation project.
|