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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +19 -0
  3. data/.gitignore +15 -0
  4. data/.gitlab/CODEOWNERS +2 -0
  5. data/.gitlab/changelog_config.yml +13 -0
  6. data/.gitlab/issue_templates/Default.md +13 -0
  7. data/.gitlab/merge_request_templates/Default.md +11 -0
  8. data/.gitlab/merge_request_templates/Release.md +13 -0
  9. data/.gitlab-ci.yml +146 -0
  10. data/.rubocop.yml +21 -0
  11. data/.rubocop_todo.yml +145 -0
  12. data/.ruby-version +1 -0
  13. data/.tool-versions +1 -0
  14. data/.yardopts +4 -0
  15. data/CONTRIBUTING.md +31 -0
  16. data/Dangerfile +5 -0
  17. data/Gemfile +15 -0
  18. data/Guardfile +70 -0
  19. data/LICENSE.md +25 -0
  20. data/README.md +1480 -0
  21. data/Rakefile +6 -0
  22. data/bin/gitlab-triage +19 -0
  23. data/gitlab-triage.gemspec +41 -0
  24. data/lib/gitlab/triage/action/base.rb +14 -0
  25. data/lib/gitlab/triage/action/comment.rb +104 -0
  26. data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
  27. data/lib/gitlab/triage/action/delete.rb +56 -0
  28. data/lib/gitlab/triage/action/issue.rb +64 -0
  29. data/lib/gitlab/triage/action/summarize.rb +82 -0
  30. data/lib/gitlab/triage/action.rb +36 -0
  31. data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
  32. data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
  33. data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
  34. data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
  35. data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
  36. data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
  37. data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
  38. data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
  39. data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
  40. data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
  41. data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
  42. data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
  43. data/lib/gitlab/triage/engine.rb +635 -0
  44. data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
  45. data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
  46. data/lib/gitlab/triage/errors/network.rb +11 -0
  47. data/lib/gitlab/triage/errors.rb +1 -0
  48. data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
  49. data/lib/gitlab/triage/expand_condition/list.rb +25 -0
  50. data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
  51. data/lib/gitlab/triage/expand_condition.rb +23 -0
  52. data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
  53. data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
  54. data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
  55. data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
  56. data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
  57. data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
  58. data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
  59. data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
  60. data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
  61. data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
  62. data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
  63. data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
  64. data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
  65. data/lib/gitlab/triage/graphql_network.rb +81 -0
  66. data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
  67. data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -0
  68. data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
  69. data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
  70. data/lib/gitlab/triage/limiters/base_limiter.rb +35 -0
  71. data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
  72. data/lib/gitlab/triage/network.rb +39 -0
  73. data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
  74. data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
  75. data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
  76. data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
  77. data/lib/gitlab/triage/option_parser.rb +105 -0
  78. data/lib/gitlab/triage/options.rb +30 -0
  79. data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
  80. data/lib/gitlab/triage/policies/base_policy.rb +80 -0
  81. data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
  82. data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
  83. data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
  84. data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
  85. data/lib/gitlab/triage/resource/base.rb +102 -0
  86. data/lib/gitlab/triage/resource/branch.rb +13 -0
  87. data/lib/gitlab/triage/resource/context.rb +47 -0
  88. data/lib/gitlab/triage/resource/epic.rb +20 -0
  89. data/lib/gitlab/triage/resource/instance_version.rb +35 -0
  90. data/lib/gitlab/triage/resource/issue.rb +52 -0
  91. data/lib/gitlab/triage/resource/label.rb +56 -0
  92. data/lib/gitlab/triage/resource/label_event.rb +48 -0
  93. data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
  94. data/lib/gitlab/triage/resource/merge_request.rb +23 -0
  95. data/lib/gitlab/triage/resource/milestone.rb +98 -0
  96. data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
  97. data/lib/gitlab/triage/rest_api_network.rb +125 -0
  98. data/lib/gitlab/triage/retryable.rb +33 -0
  99. data/lib/gitlab/triage/ui.rb +23 -0
  100. data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
  101. data/lib/gitlab/triage/utils.rb +13 -0
  102. data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
  103. data/lib/gitlab/triage/validators/params_validator.rb +43 -0
  104. data/lib/gitlab/triage/version.rb +7 -0
  105. data/lib/gitlab/triage.rb +6 -0
  106. data/support/.gitlab-ci.example.yml +22 -0
  107. data/support/.triage-policies.example.yml +51 -0
  108. 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