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,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command_builders/text_content_builder'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module EntityBuilders
8
+ class SummaryBuilder
9
+ def initialize(
10
+ type:, action:, resources:, network:,
11
+ policy_spec: {}, separator: "\n")
12
+ @type = type
13
+ @policy_spec = policy_spec
14
+ @item_template = action[:item]
15
+ @title_template = action[:title]
16
+ @summary_template = action[:summary]
17
+ @summary_destination = action[:destination]
18
+ @redact_confidentials =
19
+ action[:redact_confidential_resources] != false
20
+ @resources = resources
21
+ @network = network
22
+ @separator = separator
23
+ end
24
+
25
+ def title
26
+ @title ||= build_text(title_resource, @title_template)
27
+ end
28
+
29
+ def description
30
+ @description ||= build_text(description_resource, @summary_template)
31
+ end
32
+
33
+ def destination
34
+ @summary_destination
35
+ end
36
+
37
+ def valid?
38
+ title =~ /\S+/ && any_resources?
39
+ end
40
+
41
+ def any_resources?
42
+ @resources.any?
43
+ end
44
+
45
+ private
46
+
47
+ def title_resource
48
+ { type: @type }
49
+ end
50
+
51
+ def description_resource
52
+ title_resource.merge(
53
+ title: title, items: items, resources: @resources)
54
+ end
55
+
56
+ def items
57
+ @items ||= @resources.map { |x| build_item(x) }.join(@separator)
58
+ end
59
+
60
+ def build_item(resource)
61
+ case resource
62
+ when SummaryBuilder
63
+ resource.description
64
+ else
65
+ build_text(resource, @item_template)
66
+ end
67
+ end
68
+
69
+ def build_text(resource, template)
70
+ return '' unless template
71
+
72
+ CommandBuilders::TextContentBuilder.new(
73
+ template,
74
+ resource: resource,
75
+ network: @network,
76
+ redact_confidentials: @redact_confidentials)
77
+ .build_command.chomp
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,11 @@
1
+ module Gitlab
2
+ module Triage
3
+ module Errors
4
+ module Network
5
+ InternalServerError = Class.new(StandardError)
6
+ TooManyRequests = Class.new(StandardError)
7
+ UnexpectedResponse = Class.new(StandardError)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ require_relative 'errors/network'
@@ -0,0 +1,203 @@
1
+ module Gitlab
2
+ module Triage
3
+ module ExpandCondition
4
+ class Expansion
5
+ # @pattern describes how we're looking for the pattern, and
6
+ # @compile is a block which should compile the scanned data
7
+ # into a list of results.
8
+ #
9
+ # Please see the comments for #perform to see actual example.
10
+ def initialize(pattern, &compile)
11
+ @pattern = pattern
12
+ @compile = compile
13
+ end
14
+
15
+ # This method will take a list of strings, which contains
16
+ # some kind of pattern, described by @pattern and expand them
17
+ # into each possible matches via @compile. For example,
18
+ # suppose @pattern is:
19
+ #
20
+ # /\{(\d+)\.\.(\d+)\}/
21
+ #
22
+ # And @compile is:
23
+ #
24
+ # do |(lower, upper)|
25
+ # Integer(lower)..Integer(upper)
26
+ # end
27
+ #
28
+ # And the input is:
29
+ #
30
+ # * a:{0..1}
31
+ # * b:{2..3}
32
+ # * c
33
+ #
34
+ # The result would be:
35
+ #
36
+ # * * a:0
37
+ # * b:2
38
+ # * c
39
+ # * * a:0
40
+ # * b:3
41
+ # * c
42
+ # * * a:1
43
+ # * b:2
44
+ # * c
45
+ # * * a:1
46
+ # * b:3
47
+ # * c
48
+ #
49
+ # We get this by picking the 1st number from the 1st string,
50
+ # which is 0 from "a:{0..1}", and the 1st number from the
51
+ # 2nd string, which is 2 from "b:{2..3}", and since the 3rd
52
+ # string is just a fixed string, we just pick it.
53
+ #
54
+ # This way we have the first possible match, that is:
55
+ #
56
+ # * a:0
57
+ # * b:2
58
+ # * c
59
+ #
60
+ # Then we repeat the process by picking the next number for the
61
+ # next possible match, starting from the least significant
62
+ # string, which is c, but there's nothing more to pick. Then we
63
+ # go to the next one, which will be the 2nd string: "b:{2..3}",
64
+ # and we pick the 2nd number for it: 3. Since we have a new pick,
65
+ # we have a new possible match:
66
+ #
67
+ # * a:0
68
+ # * b:3
69
+ # * c
70
+ #
71
+ # Again we repeat the process, and 2nd string doesn't have more
72
+ # choices therefore we need to go to the 1st string now. When
73
+ # this happens, we'll need to reset the picks from the previous
74
+ # string, thus 2nd string will go back to 2. The next number for
75
+ # the 1st string is 1, and then we form the new match:
76
+ #
77
+ # * a:1
78
+ # * b:2
79
+ # * c
80
+ #
81
+ # The next step will be the last match by picking the next number
82
+ # from the 2nd string again: 3, and we get:
83
+ #
84
+ # * a:1
85
+ # * b:3
86
+ # * c
87
+ #
88
+ # The method will stop here because it had walked through all the
89
+ # possible combinations. The total number of results is the product
90
+ # of numbers of sequences.
91
+ #
92
+ # Note that a string can contain multiple sequences, and it will
93
+ # also walk through them one by one. For example, given:
94
+ #
95
+ # * a:{0..1}:{2..3}
96
+ # * c
97
+ #
98
+ # We'll get:
99
+ #
100
+ # * * a:0:2
101
+ # * c
102
+ # * * a:0:3
103
+ # * c
104
+ # * * a:1:2
105
+ # * c
106
+ # * * a:1:3
107
+ # * c
108
+ def perform(strings)
109
+ expanded_strings =
110
+ strings.map(&:strip).map(&method(:expand_patterns))
111
+
112
+ product_of_all(expanded_strings)
113
+ end
114
+
115
+ # This method returns the product of list of lists. For example,
116
+ # giving it [%w[a:0 a:1], %w[b:2 b:3], %w[c]] will return:
117
+ #
118
+ # [%w[a:0 b:2 c], %w[a:0 b:3 c], %w[a:1 b:2 c], %w[a:1 b:3 c]]
119
+ def product_of_all(expanded_strings)
120
+ expanded_strings.first.product(*expanded_strings.drop(1))
121
+ end
122
+
123
+ # This method expands the string from the sequences. For example,
124
+ # giving it "a:{0..1}:{2..3}" will return:
125
+ #
126
+ # %w[
127
+ # a:0:2
128
+ # a:0:3
129
+ # a:1:2
130
+ # a:1:3
131
+ # ]
132
+ def expand_patterns(string)
133
+ expand(string, scan_patterns(string))
134
+ end
135
+
136
+ # This method extracts the sequences from the string. For example,
137
+ # giving it "a:{0..1}:{2..3}" will return:
138
+ #
139
+ # [0..1, 2..3]
140
+ def scan_patterns(string)
141
+ string.scan(@pattern).map(&@compile)
142
+ end
143
+
144
+ # This recursive method does the heavy lifting. It substitutes the
145
+ # sequence patterns in a string with a picked number from the
146
+ # sequence, and collect all the results. Here's an example:
147
+ #
148
+ # expand("a:{0..1}:{2..3}", [0..1, 2..3])
149
+ #
150
+ # This means that we want to pick the numbers from the sequences,
151
+ # and fill them back to the string containing the pattern in the
152
+ # respective order. We don't care which pattern it is because
153
+ # the order should have spoken for it. The result will be:
154
+ #
155
+ # %w[
156
+ # a:0:2
157
+ # a:0:3
158
+ # a:1:2
159
+ # a:1:3
160
+ # ]
161
+ #
162
+ # We start by picking the first sequence, which is 0..1 here. We
163
+ # want all the possible picks, thus we flat_map on it, substituting
164
+ # the first pattern with the picked number. This means we get:
165
+ #
166
+ # "a:0:{2..3}"
167
+ #
168
+ # For the first iteration. Before we jump to the next pick from the
169
+ # sequence, we recursively do this again on the current string,
170
+ # which only has one sequence pattern left. It will be called like:
171
+ #
172
+ # expand("a:0:{2..3}", [2..3])
173
+ #
174
+ # Because we also dropped the first sequence we have already used.
175
+ # On the next recursive call, we don't have any sequences left,
176
+ # therefore we just return the current string: "a:0:2".
177
+ #
178
+ # Flattening the recursion, it might look like this:
179
+ #
180
+ # (0..1).flat_map do |x|
181
+ # (2..3).flat_map do |y|
182
+ # "a:{0..1}:{2..3}".sub(PATTERN, x.to_s).sub(PATTERN, y.to_s)
183
+ # end
184
+ # end
185
+ #
186
+ # So here we could clearly see that we go deep first, substituting
187
+ # the least significant pattern first, and then go back to the
188
+ # previous one, until there's nothing more to pick.
189
+ def expand(string, items)
190
+ if items.empty?
191
+ [string]
192
+ else
193
+ remainings = items.drop(1)
194
+
195
+ items.first.flat_map do |item|
196
+ expand(string.sub(@pattern, item.to_s), remainings)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'expansion'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module ExpandCondition
6
+ module List
7
+ PATTERN = /\{.+?,.+?\}/m.freeze
8
+
9
+ def self.expand(conditions)
10
+ labels = conditions[:labels]
11
+
12
+ return conditions unless labels
13
+
14
+ expansion = Expansion.new(PATTERN) do |list|
15
+ list.gsub(/\{|\}/, '').split(',').map(&:strip)
16
+ end
17
+
18
+ expansion.perform(labels).map do |new_labels|
19
+ conditions.merge(labels: new_labels)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'expansion'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module ExpandCondition
6
+ module Sequence
7
+ PATTERN = /\{\s*(\d+)\s*\.\.\s*(\d+)\s*\}/.freeze
8
+
9
+ def self.expand(conditions)
10
+ labels = conditions[:labels]
11
+
12
+ return conditions unless labels
13
+
14
+ expansion = Expansion.new(PATTERN) do |(lower, upper)|
15
+ Integer(lower)..Integer(upper)
16
+ end
17
+
18
+ expansion.perform(labels).map do |new_labels|
19
+ conditions.merge(labels: new_labels)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'expand_condition/list'
2
+ require_relative 'expand_condition/sequence'
3
+
4
+ module Gitlab
5
+ module Triage
6
+ module ExpandCondition
7
+ PIPELINE = [
8
+ List,
9
+ Sequence
10
+ ].freeze
11
+
12
+ def self.perform(conditions, pipeline = PIPELINE, &block)
13
+ expand([conditions], pipeline).each(&block)
14
+ end
15
+
16
+ def self.expand(conditions, pipeline = PIPELINE)
17
+ pipeline.inject(conditions) do |result, job|
18
+ result.flat_map(&job.method(:expand))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'member_conditions_filter'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Filters
6
+ class AssigneeMemberConditionsFilter < MemberConditionsFilter
7
+ def member_field
8
+ :assignee
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'member_conditions_filter'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Filters
6
+ class AuthorMemberConditionsFilter < MemberConditionsFilter
7
+ def member_field
8
+ :author
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support/all'
2
+ require_relative '../validators/params_validator'
3
+
4
+ module Gitlab
5
+ module Triage
6
+ module Filters
7
+ class BaseConditionsFilter
8
+ def initialize(resource, condition)
9
+ @resource = resource
10
+ validate_condition(condition)
11
+ initialize_variables(condition)
12
+ end
13
+
14
+ def calculate
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def self.filter_parameters
19
+ []
20
+ end
21
+
22
+ def self.params_filter_names(params = nil)
23
+ params ||= filter_parameters
24
+
25
+ params.map do |param|
26
+ param[:name]
27
+ end
28
+ end
29
+
30
+ def self.all_params_filter_names
31
+ params_filter_names
32
+ end
33
+
34
+ def self.params_checking_condition_value
35
+ params_filter_names params_check_for_field(:values)
36
+ end
37
+
38
+ def self.params_checking_condition_type
39
+ params_filter_names params_check_for_field(:type)
40
+ end
41
+
42
+ def self.params_check_for_field(field)
43
+ filter_parameters.select do |param|
44
+ param[field].present?
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def validate_condition(condition)
51
+ ParamsValidator.new(self.class.filter_parameters, condition).validate!
52
+ end
53
+
54
+ def initialize_variables(condition); end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,73 @@
1
+ require_relative 'base_conditions_filter'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Filters
6
+ class BranchDateFilter < BaseConditionsFilter
7
+ ATTRIBUTES = %w[committed_date authored_date].freeze
8
+ CONDITIONS = %w[older_than newer_than].freeze
9
+ TIME_BASED_INTERVALS = %w[minutes hours].freeze
10
+ DATE_BASED_INTERVALS = %w[days weeks months years].freeze
11
+ INTERVAL_TYPES = TIME_BASED_INTERVALS + DATE_BASED_INTERVALS
12
+
13
+ def self.allowed_attributes
14
+ self::ATTRIBUTES
15
+ end
16
+
17
+ def self.filter_parameters
18
+ [
19
+ {
20
+ name: :attribute,
21
+ type: String,
22
+ values: allowed_attributes
23
+ },
24
+ {
25
+ name: :condition,
26
+ type: String,
27
+ values: CONDITIONS
28
+ },
29
+ {
30
+ name: :interval_type,
31
+ type: String,
32
+ values: INTERVAL_TYPES
33
+ },
34
+ {
35
+ name: :interval,
36
+ type: Numeric
37
+ }
38
+ ]
39
+ end
40
+
41
+ def initialize_variables(condition)
42
+ @attribute = condition[:attribute].to_sym
43
+ @condition = condition[:condition].to_sym
44
+ @interval_type = condition[:interval_type].to_sym
45
+ @interval = condition[:interval]
46
+ end
47
+
48
+ def resource_value
49
+ @resource[:commit][@attribute]&.to_date
50
+ end
51
+
52
+ def condition_value
53
+ if TIME_BASED_INTERVALS.include?(@interval_type.to_s)
54
+ @interval.public_send(@interval_type).ago.to_datetime # rubocop:disable GitlabSecurity/PublicSend
55
+ else
56
+ @interval.public_send(@interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
57
+ end
58
+ end
59
+
60
+ def calculate
61
+ return false unless resource_value
62
+
63
+ case @condition
64
+ when :older_than
65
+ resource_value < condition_value
66
+ when :newer_than
67
+ resource_value > condition_value
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'base_conditions_filter'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Filters
6
+ class BranchProtectedFilter < BaseConditionsFilter
7
+ def initialize_variables(config_value)
8
+ @attribute = :protected
9
+ @condition = config_value.nil? ? true : config_value
10
+ end
11
+
12
+ def resource_value
13
+ @resource[:protected]
14
+ end
15
+
16
+ def condition_value
17
+ @condition
18
+ end
19
+
20
+ def calculate
21
+ resource_value == condition_value
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,58 @@
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[:user_discussions_count]
40
+ end
41
+ end
42
+
43
+ def condition_value
44
+ @threshold
45
+ end
46
+
47
+ def calculate
48
+ case @condition
49
+ when :greater_than
50
+ resource_value.to_i > condition_value
51
+ when :less_than
52
+ resource_value.to_i < condition_value
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'base_conditions_filter'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Filters
6
+ class IssueDateConditionsFilter < BaseConditionsFilter
7
+ CONDITIONS = %w[older_than newer_than].freeze
8
+ TIME_BASED_INTERVALS = %w[minutes hours].freeze
9
+ DATE_BASED_INTERVALS = %w[days weeks months years].freeze
10
+ INTERVAL_TYPES = TIME_BASED_INTERVALS + DATE_BASED_INTERVALS
11
+
12
+ def self.generate_allowed_attributes
13
+ %w[updated_at created_at]
14
+ end
15
+
16
+ def self.allowed_attributes
17
+ @allowed_attributes ||= generate_allowed_attributes.freeze
18
+ end
19
+
20
+ def self.filter_parameters
21
+ [
22
+ {
23
+ name: :attribute,
24
+ type: String,
25
+ values: allowed_attributes
26
+ },
27
+ {
28
+ name: :condition,
29
+ type: String,
30
+ values: CONDITIONS
31
+ },
32
+ {
33
+ name: :interval_type,
34
+ type: String,
35
+ values: INTERVAL_TYPES
36
+ },
37
+ {
38
+ name: :interval,
39
+ type: Numeric
40
+ }
41
+ ]
42
+ end
43
+
44
+ def initialize_variables(condition)
45
+ @attribute = condition[:attribute].to_sym
46
+ @condition = condition[:condition].to_sym
47
+ @interval_type = condition[:interval_type].to_sym
48
+ @interval = condition[:interval]
49
+ end
50
+
51
+ # Guard against merge requests with no merged_at values
52
+ def resource_value
53
+ @resource[@attribute]&.to_date
54
+ end
55
+
56
+ def condition_value
57
+ if TIME_BASED_INTERVALS.include?(@interval_type.to_s)
58
+ @interval.public_send(@interval_type).ago.to_datetime # rubocop:disable GitlabSecurity/PublicSend
59
+ else
60
+ @interval.public_send(@interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
61
+ end
62
+ end
63
+
64
+ # Guard against merge requests with no merged_at values
65
+ def calculate
66
+ return false unless resource_value
67
+
68
+ case @condition
69
+ when :older_than
70
+ resource_value < condition_value
71
+ when :newer_than
72
+ resource_value > condition_value
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end