payping-gitlab-triage 0.1.1
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 +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +15 -0
- data/.gitlab/CODEOWNERS +2 -0
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/issue_templates/Default.md +13 -0
- data/.gitlab/merge_request_templates/Default.md +11 -0
- data/.gitlab/merge_request_templates/Release.md +13 -0
- data/.gitlab-ci.yml +146 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +145 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +31 -0
- data/Dangerfile +5 -0
- data/Gemfile +15 -0
- data/Guardfile +70 -0
- data/LICENSE.md +25 -0
- data/README.md +1480 -0
- data/Rakefile +6 -0
- data/bin/gitlab-triage +19 -0
- data/gitlab-triage.gemspec +41 -0
- data/lib/gitlab/triage/action/base.rb +14 -0
- data/lib/gitlab/triage/action/comment.rb +104 -0
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/delete.rb +56 -0
- data/lib/gitlab/triage/action/issue.rb +64 -0
- data/lib/gitlab/triage/action/summarize.rb +82 -0
- data/lib/gitlab/triage/action.rb +36 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
- data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
- data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
- data/lib/gitlab/triage/engine.rb +635 -0
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
- data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
- data/lib/gitlab/triage/errors/network.rb +11 -0
- data/lib/gitlab/triage/errors.rb +1 -0
- data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
- data/lib/gitlab/triage/expand_condition/list.rb +25 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
- data/lib/gitlab/triage/expand_condition.rb +23 -0
- data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
- data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
- data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
- data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
- data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
- data/lib/gitlab/triage/graphql_network.rb +81 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -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/limiters/base_limiter.rb +35 -0
- data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
- data/lib/gitlab/triage/network.rb +39 -0
- data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
- data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
- data/lib/gitlab/triage/option_parser.rb +105 -0
- data/lib/gitlab/triage/options.rb +30 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
- data/lib/gitlab/triage/policies/base_policy.rb +80 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
- data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
- data/lib/gitlab/triage/resource/base.rb +102 -0
- data/lib/gitlab/triage/resource/branch.rb +13 -0
- data/lib/gitlab/triage/resource/context.rb +47 -0
- data/lib/gitlab/triage/resource/epic.rb +20 -0
- data/lib/gitlab/triage/resource/instance_version.rb +35 -0
- data/lib/gitlab/triage/resource/issue.rb +52 -0
- data/lib/gitlab/triage/resource/label.rb +56 -0
- data/lib/gitlab/triage/resource/label_event.rb +48 -0
- data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
- data/lib/gitlab/triage/resource/merge_request.rb +23 -0
- data/lib/gitlab/triage/resource/milestone.rb +98 -0
- data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
- data/lib/gitlab/triage/rest_api_network.rb +125 -0
- data/lib/gitlab/triage/retryable.rb +33 -0
- data/lib/gitlab/triage/ui.rb +23 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
- data/lib/gitlab/triage/validators/params_validator.rb +43 -0
- data/lib/gitlab/triage/version.rb +7 -0
- data/lib/gitlab/triage.rb +6 -0
- data/support/.gitlab-ci.example.yml +22 -0
- data/support/.triage-policies.example.yml +51 -0
- metadata +280 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
require_relative '../url_builders/url_builder'
|
|
3
|
+
|
|
4
|
+
module Gitlab
|
|
5
|
+
module Triage
|
|
6
|
+
module Filters
|
|
7
|
+
class MemberConditionsFilter < BaseConditionsFilter
|
|
8
|
+
SOURCES = %w[project group].freeze
|
|
9
|
+
CONDITIONS = %w[member_of not_member_of].freeze
|
|
10
|
+
|
|
11
|
+
def initialize(resource, condition, network = nil)
|
|
12
|
+
@network = network
|
|
13
|
+
super(resource, condition)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.filter_parameters
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
name: :source,
|
|
20
|
+
type: String,
|
|
21
|
+
values: SOURCES
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: :condition,
|
|
25
|
+
type: String,
|
|
26
|
+
values: CONDITIONS
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: :source_id,
|
|
30
|
+
type: [Numeric, String]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize_variables(condition)
|
|
36
|
+
@source = condition[:source].to_sym
|
|
37
|
+
@condition = condition[:condition].to_sym
|
|
38
|
+
@source_id = condition[:source_id]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resource_value
|
|
42
|
+
@resource[member_field][:username] if @resource[member_field]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def condition_value
|
|
46
|
+
members.map do |member|
|
|
47
|
+
member[:username]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calculate
|
|
52
|
+
return false unless resource_value
|
|
53
|
+
|
|
54
|
+
case @condition
|
|
55
|
+
when :member_of
|
|
56
|
+
condition_value.include?(resource_value)
|
|
57
|
+
when :not_member_of
|
|
58
|
+
!condition_value.include?(resource_value)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def members
|
|
63
|
+
@members ||= @network.query_api_cached(member_url)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def member_url
|
|
67
|
+
UrlBuilders::UrlBuilder.new(url_opts).build
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def url_opts
|
|
73
|
+
{
|
|
74
|
+
network_options: @network.options,
|
|
75
|
+
resource_type: 'members',
|
|
76
|
+
source: @source == :group ? 'groups' : 'projects',
|
|
77
|
+
source_id: @source_id,
|
|
78
|
+
params: { per_page: 100 }
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Filters
|
|
6
|
+
class MergeRequestDateConditionsFilter < IssueDateConditionsFilter
|
|
7
|
+
def self.generate_allowed_attributes
|
|
8
|
+
super << 'merged_at'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Filters
|
|
6
|
+
class NameConditionsFilter < BaseConditionsFilter
|
|
7
|
+
def initialize_variables(matching_name)
|
|
8
|
+
@attribute = :name
|
|
9
|
+
@matching_name = matching_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def resource_value
|
|
13
|
+
@resource[@attribute]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def condition_value
|
|
17
|
+
@matching_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def calculate
|
|
21
|
+
resource_value == condition_value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Filters
|
|
6
|
+
class NoAdditionalLabelsConditionsFilter < BaseConditionsFilter
|
|
7
|
+
def self.filter_parameters
|
|
8
|
+
[]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate_condition(condition)
|
|
12
|
+
raise ArgumentError, 'condition must be an array containing the only label values allowed' unless condition.is_a?(Array)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize_variables(expected_labels)
|
|
16
|
+
@attribute = :labels
|
|
17
|
+
@expected_labels = expected_labels
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resource_value
|
|
21
|
+
@resource[@attribute]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def calculate
|
|
25
|
+
(resource_value - @expected_labels).empty?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
require_relative '../resource/context'
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module Gitlab
|
|
6
|
+
module Triage
|
|
7
|
+
module Filters
|
|
8
|
+
class RubyConditionsFilter < BaseConditionsFilter
|
|
9
|
+
def self.limiter_parameters
|
|
10
|
+
[{ name: :ruby, type: String }]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(resource, condition, network = nil)
|
|
14
|
+
super(resource, condition)
|
|
15
|
+
|
|
16
|
+
@network = network
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def calculate
|
|
20
|
+
context = Resource::Context.build(@resource, network: @network, redact_confidentials: false)
|
|
21
|
+
|
|
22
|
+
!!context.eval(@expression)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def initialize_variables(condition)
|
|
28
|
+
@expression = condition[:ruby]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require_relative 'base_conditions_filter'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Filters
|
|
6
|
+
class VotesConditionsFilter < BaseConditionsFilter
|
|
7
|
+
ATTRIBUTES = %w[upvotes downvotes].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
|
+
@resource[@attribute]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def condition_value
|
|
40
|
+
@threshold
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def calculate
|
|
44
|
+
case @condition
|
|
45
|
+
when :greater_than
|
|
46
|
+
resource_value > condition_value
|
|
47
|
+
when :less_than
|
|
48
|
+
resource_value < condition_value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
MINIMUM_RATE_LIMIT = 25
|
|
15
|
+
|
|
16
|
+
def initialize(adapter)
|
|
17
|
+
@adapter = adapter
|
|
18
|
+
@options = adapter.options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def query(graphql_query, variables = {})
|
|
22
|
+
return if graphql_query.blank?
|
|
23
|
+
|
|
24
|
+
response = {}
|
|
25
|
+
resources = []
|
|
26
|
+
|
|
27
|
+
parsed_graphql_query = adapter.parse(graphql_query.query)
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
print '.'
|
|
31
|
+
|
|
32
|
+
response = adapter.query(
|
|
33
|
+
parsed_graphql_query,
|
|
34
|
+
resource_path: graphql_query.resource_path,
|
|
35
|
+
variables: variables.merge(after: response.delete(:end_cursor))
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
rate_limit_debug(response) if options.debug
|
|
39
|
+
rate_limit_wait(response)
|
|
40
|
+
|
|
41
|
+
resources.concat(Array.wrap(response.delete(:results)))
|
|
42
|
+
end while response.delete(:more_pages)
|
|
43
|
+
|
|
44
|
+
resources
|
|
45
|
+
.map { |resource| resource.deep_transform_keys(&:underscore) }
|
|
46
|
+
.map(&:with_indifferent_access)
|
|
47
|
+
.map { |resource| normalize(resource) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def normalize(resource)
|
|
53
|
+
resource
|
|
54
|
+
.slice(:iid, :title, :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
|
+
|
|
62
|
+
def extract_id_from_global_id(global_id)
|
|
63
|
+
return if global_id.blank?
|
|
64
|
+
|
|
65
|
+
GlobalID.parse(global_id).model_id.to_i
|
|
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
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
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'
|
|
4
|
+
|
|
5
|
+
module Gitlab
|
|
6
|
+
module Triage
|
|
7
|
+
module GraphqlQueries
|
|
8
|
+
class QueryBuilder
|
|
9
|
+
def initialize(source_type, resource_type, conditions, graphql_only: false)
|
|
10
|
+
@source_type = source_type.to_s.singularize
|
|
11
|
+
@resource_type = resource_type
|
|
12
|
+
@conditions = conditions
|
|
13
|
+
@graphql_only = graphql_only
|
|
14
|
+
@resource_declarations = [
|
|
15
|
+
'$source: ID!',
|
|
16
|
+
'$after: String'
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resource_path
|
|
21
|
+
[source_type, resource_type]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def query
|
|
25
|
+
return if resource_fields.empty?
|
|
26
|
+
|
|
27
|
+
format(
|
|
28
|
+
BASE_QUERY,
|
|
29
|
+
source_type: source_type,
|
|
30
|
+
resource_type: resource_type.to_s.camelize(:lower),
|
|
31
|
+
resource_fields: resource_fields.join(' '),
|
|
32
|
+
resource_query: resource_query,
|
|
33
|
+
resource_declarations: resource_declarations.join(', ')
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
delegate :any?, to: :resource_fields
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :source_type, :resource_type, :conditions, :graphql_only, :resource_declarations
|
|
42
|
+
|
|
43
|
+
BASE_QUERY = <<~GRAPHQL.freeze
|
|
44
|
+
query(%{resource_declarations}) {
|
|
45
|
+
%{source_type}(fullPath: $source) {
|
|
46
|
+
id
|
|
47
|
+
%{resource_type}(after: $after%{resource_query}) {
|
|
48
|
+
pageInfo {
|
|
49
|
+
hasNextPage
|
|
50
|
+
endCursor
|
|
51
|
+
}
|
|
52
|
+
nodes {
|
|
53
|
+
id iid title updatedAt createdAt webUrl projectId %{resource_fields}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
GRAPHQL
|
|
59
|
+
|
|
60
|
+
def vote_attribute
|
|
61
|
+
@vote_attribute ||= (conditions.dig(:votes, :attribute) || conditions.dig(:upvotes, :attribute)).to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def resource_fields
|
|
65
|
+
fields = []
|
|
66
|
+
|
|
67
|
+
fields << 'userNotesCount' if conditions.dig(:discussions, :attribute).to_s == 'notes'
|
|
68
|
+
fields << 'userDiscussionsCount' if conditions.dig(:discussions, :attribute).to_s == 'threads'
|
|
69
|
+
|
|
70
|
+
if graphql_only
|
|
71
|
+
fields << 'labels { nodes { title } }'
|
|
72
|
+
fields << 'author { id name username }'
|
|
73
|
+
fields << 'assignees { nodes { id name username } }' if conditions.key?(:assignee_member)
|
|
74
|
+
fields << 'upvotes' if vote_attribute == 'upvotes'
|
|
75
|
+
fields << 'downvotes' if vote_attribute == 'downvotes'
|
|
76
|
+
fields.push('draft', 'mergedAt') if resource_type == 'merge_requests'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
fields
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def resource_query
|
|
83
|
+
condition_queries = []
|
|
84
|
+
|
|
85
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('includeSubgroups', true, with_quotes: false) if source_type == 'group'
|
|
86
|
+
|
|
87
|
+
conditions.each do |condition, condition_params|
|
|
88
|
+
condition_queries << QueryParamBuilders::DateParamBuilder.new(condition_params) if condition.to_s == 'date'
|
|
89
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('milestoneTitle', condition_params) if condition.to_s == 'milestone'
|
|
90
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('state', condition_params, with_quotes: false) if condition.to_s == 'state'
|
|
91
|
+
|
|
92
|
+
if condition.to_s == 'iids'
|
|
93
|
+
@resource_declarations << '$iids: [String!]'
|
|
94
|
+
condition_queries << QueryParamBuilders::BaseParamBuilder.new('iids', '$iids', with_quotes: false)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
case resource_type
|
|
98
|
+
when 'issues'
|
|
99
|
+
condition_queries << issues_label_query(condition, condition_params)
|
|
100
|
+
when 'merge_requests'
|
|
101
|
+
condition_queries << merge_requests_label_query(condition, condition_params)
|
|
102
|
+
condition_queries << merge_requests_resource_query(condition, condition_params)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
condition_queries
|
|
107
|
+
.compact
|
|
108
|
+
.map(&:build_param)
|
|
109
|
+
.join
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def issues_label_query(condition, condition_params)
|
|
113
|
+
args =
|
|
114
|
+
case condition.to_s
|
|
115
|
+
when 'forbidden_labels'
|
|
116
|
+
['labelName', condition_params, { negated: true }]
|
|
117
|
+
when 'labels'
|
|
118
|
+
['labelName', condition_params, {}]
|
|
119
|
+
else
|
|
120
|
+
return nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
QueryParamBuilders::LabelsParamBuilder.new(*args[0...-1], **args.last)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def merge_requests_resource_query(condition, condition_params)
|
|
127
|
+
args =
|
|
128
|
+
case condition.to_s
|
|
129
|
+
when 'source_branch'
|
|
130
|
+
['sourceBranches', condition_params, {}]
|
|
131
|
+
when 'target_branch'
|
|
132
|
+
['targetBranches', condition_params, {}]
|
|
133
|
+
when 'draft'
|
|
134
|
+
['draft', condition_params, { with_quotes: false }]
|
|
135
|
+
else
|
|
136
|
+
return nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
QueryParamBuilders::BaseParamBuilder.new(*args[0...-1], **args.last)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def merge_requests_label_query(condition, condition_params)
|
|
143
|
+
args =
|
|
144
|
+
case condition.to_s
|
|
145
|
+
when 'forbidden_labels'
|
|
146
|
+
['labels', condition_params, { negated: true }]
|
|
147
|
+
when 'labels'
|
|
148
|
+
['labels', condition_params, {}]
|
|
149
|
+
else
|
|
150
|
+
return nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
QueryParamBuilders::LabelsParamBuilder.new(*args[0...-1], **args.last)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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, :negated
|
|
9
|
+
|
|
10
|
+
def initialize(param_name, param_contents, with_quotes: true, negated: false)
|
|
11
|
+
@param_name = param_name
|
|
12
|
+
@param_contents = param_contents.to_s.strip
|
|
13
|
+
@with_quotes = with_quotes
|
|
14
|
+
@negated = negated
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_param
|
|
18
|
+
contents = with_quotes ? Utils.graphql_quote(param_contents) : param_contents
|
|
19
|
+
|
|
20
|
+
if negated
|
|
21
|
+
", not: { #{param_name}: #{contents} }"
|
|
22
|
+
else
|
|
23
|
+
", #{param_name}: #{contents}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
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, negated: false)
|
|
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, negated: negated)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'active_support/all'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Limiters
|
|
6
|
+
class BaseLimiter
|
|
7
|
+
def initialize(resources, limit)
|
|
8
|
+
@resources = initialize_resources(resources)
|
|
9
|
+
validate_limit(limit)
|
|
10
|
+
initialize_variables(limit)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def limit
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.limiter_parameters
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def initialize_variables(limit); end
|
|
24
|
+
|
|
25
|
+
def initialize_resources(resources)
|
|
26
|
+
resources
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_limit(limit)
|
|
30
|
+
LimiterValidator.new(self.class.limiter_parameters, limit).validate!
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require_relative 'base_limiter'
|
|
2
|
+
require_relative '../validators/limiter_validator'
|
|
3
|
+
|
|
4
|
+
module Gitlab
|
|
5
|
+
module Triage
|
|
6
|
+
module Limiters
|
|
7
|
+
class DateFieldLimiter < BaseLimiter
|
|
8
|
+
LIMITS = %i[most_recent oldest].freeze
|
|
9
|
+
|
|
10
|
+
def self.limiter_parameters
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
name: :most_recent,
|
|
14
|
+
type: Integer
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: :oldest,
|
|
18
|
+
type: Integer
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize_variables(limit)
|
|
24
|
+
@criterion = LIMITS.find(&limit.method(:[]))
|
|
25
|
+
@threshold = limit[@criterion]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def limit
|
|
29
|
+
case @criterion
|
|
30
|
+
when :oldest
|
|
31
|
+
@resources.first(@threshold)
|
|
32
|
+
when :most_recent
|
|
33
|
+
@resources.last(@threshold).reverse
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def initialize_resources(resources)
|
|
40
|
+
resources.sort_by { |res| res[:created_at] }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative 'graphql_network'
|
|
2
|
+
require_relative 'rest_api_network'
|
|
3
|
+
|
|
4
|
+
module Gitlab
|
|
5
|
+
module Triage
|
|
6
|
+
Network = Struct.new(:restapi, :graphql, keyword_init: true) do
|
|
7
|
+
def query_api(url)
|
|
8
|
+
restapi.query_api(url)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def query_graphql(...)
|
|
12
|
+
graphql.query(...)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query_api_cached(url)
|
|
16
|
+
restapi.query_api_cached(url)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def restapi_options
|
|
20
|
+
restapi.options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# FIXME: Remove the alias method
|
|
24
|
+
alias_method :options, :restapi_options
|
|
25
|
+
|
|
26
|
+
def graphql_options
|
|
27
|
+
graphql.options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def post_api(...)
|
|
31
|
+
restapi.post_api(...)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete_api(...)
|
|
35
|
+
restapi.delete_api(...)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|