gitlab-triage 1.7.1 → 1.11.0
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/.gitignore +0 -1
- data/.gitlab-ci.yml +57 -41
- data/.gitlab/merge_request_templates/Release.md +35 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +1 -1
- data/Guardfile +1 -1
- data/README.md +105 -8
- data/gitlab-triage.gemspec +1 -1
- data/lib/gitlab/triage/action/comment.rb +16 -3
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +78 -0
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +7 -3
- data/lib/gitlab/triage/command_builders/label_command_builder.rb +17 -0
- data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +18 -6
- data/lib/gitlab/triage/engine.rb +42 -17
- data/lib/gitlab/triage/errors.rb +1 -1
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +51 -5
- data/lib/gitlab/triage/option_parser.rb +10 -0
- data/lib/gitlab/triage/options.rb +2 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +1 -1
- data/lib/gitlab/triage/policies/summary_policy.rb +1 -1
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +5 -6
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +5 -6
- data/lib/gitlab/triage/resource/base.rb +10 -1
- data/lib/gitlab/triage/resource/label.rb +15 -0
- data/lib/gitlab/triage/resource/merge_request.rb +9 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +10 -9
- data/lib/gitlab/triage/validators/limiter_validator.rb +3 -1
- data/lib/gitlab/triage/validators/params_validator.rb +5 -3
- data/lib/gitlab/triage/version.rb +3 -1
- data/support/.gitlab-ci.example.yml +2 -2
- data/support/.triage-policies.example.yml +2 -2
- metadata +6 -5
- data/lib/gitlab/triage/filters/forbidden_labels_conditions_filter.rb +0 -32
- data/lib/gitlab/triage/filters/issuable_date_conditions_filter.rb +0 -65
data/gitlab-triage.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.license = 'MIT'
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
-
f.match(%r{^(test|spec|features)/})
|
16
|
+
f.match(%r{^(docs|test|spec|features)/})
|
17
17
|
end
|
18
18
|
spec.bindir = 'bin'
|
19
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -7,6 +7,7 @@ require_relative '../command_builders/label_command_builder'
|
|
7
7
|
require_relative '../command_builders/remove_label_command_builder'
|
8
8
|
require_relative '../command_builders/cc_command_builder'
|
9
9
|
require_relative '../command_builders/status_command_builder'
|
10
|
+
require_relative '../command_builders/move_command_builder'
|
10
11
|
|
11
12
|
module Gitlab
|
12
13
|
module Triage
|
@@ -40,9 +41,10 @@ module Gitlab
|
|
40
41
|
CommandBuilders::CommentCommandBuilder.new(
|
41
42
|
[
|
42
43
|
CommandBuilders::TextContentBuilder.new(policy.actions[:comment], resource: resource, network: network).build_command,
|
43
|
-
CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels]).build_command,
|
44
|
-
CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels]).build_command,
|
44
|
+
CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels], resource: resource, network: network).build_command,
|
45
|
+
CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels], resource: resource, network: network).build_command,
|
45
46
|
CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command,
|
47
|
+
CommandBuilders::MoveCommandBuilder.new(policy.actions[:move]).build_command,
|
46
48
|
CommandBuilders::StatusCommandBuilder.new(policy.actions[:status]).build_command
|
47
49
|
]
|
48
50
|
).build_command
|
@@ -61,13 +63,24 @@ module Gitlab
|
|
61
63
|
source_id: resource[:project_id],
|
62
64
|
resource_type: policy.type,
|
63
65
|
resource_id: resource['iid'],
|
64
|
-
sub_resource_type:
|
66
|
+
sub_resource_type: sub_resource_type
|
65
67
|
).build
|
66
68
|
|
67
69
|
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if network.options.debug
|
68
70
|
|
69
71
|
post_url
|
70
72
|
end
|
73
|
+
|
74
|
+
def sub_resource_type
|
75
|
+
case type = policy.actions[:comment_type]
|
76
|
+
when 'comment', nil # nil is default
|
77
|
+
'notes'
|
78
|
+
when 'thread'
|
79
|
+
'discussions'
|
80
|
+
else
|
81
|
+
raise ArgumentError, "Unknown comment type: #{type}"
|
82
|
+
end
|
83
|
+
end
|
71
84
|
end
|
72
85
|
end
|
73
86
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative '../validators/params_validator'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module APIQueryBuilders
|
6
|
+
class DateQueryParamBuilder
|
7
|
+
ATTRIBUTES = %w[updated_at created_at].freeze
|
8
|
+
CONDITIONS = %w[older_than newer_than].freeze
|
9
|
+
INTERVAL_TYPES = %w[days weeks months years].freeze
|
10
|
+
|
11
|
+
def self.filter_parameters
|
12
|
+
[
|
13
|
+
{
|
14
|
+
name: :attribute,
|
15
|
+
type: String,
|
16
|
+
values: ATTRIBUTES
|
17
|
+
},
|
18
|
+
{
|
19
|
+
name: :condition,
|
20
|
+
type: String,
|
21
|
+
values: CONDITIONS
|
22
|
+
},
|
23
|
+
{
|
24
|
+
name: :interval_type,
|
25
|
+
type: String,
|
26
|
+
values: INTERVAL_TYPES
|
27
|
+
},
|
28
|
+
{
|
29
|
+
name: :interval,
|
30
|
+
type: Numeric
|
31
|
+
}
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.applicable?(condition)
|
36
|
+
ATTRIBUTES.include?(condition[:attribute].to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(condition_hash)
|
40
|
+
@attribute = condition_hash[:attribute].to_s
|
41
|
+
@interval_condition = condition_hash[:condition].to_sym
|
42
|
+
@interval_type = condition_hash[:interval_type]
|
43
|
+
@interval = condition_hash[:interval]
|
44
|
+
validate_condition(condition_hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_condition(condition)
|
48
|
+
ParamsValidator.new(self.class.filter_parameters, condition).validate!
|
49
|
+
end
|
50
|
+
|
51
|
+
def param_name
|
52
|
+
prefix = attribute.sub(/_at\z/, '')
|
53
|
+
suffix =
|
54
|
+
case interval_condition
|
55
|
+
when :older_than
|
56
|
+
'before'
|
57
|
+
when :newer_than
|
58
|
+
'after'
|
59
|
+
end
|
60
|
+
|
61
|
+
"#{prefix}_#{suffix}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def param_content
|
65
|
+
interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_param
|
69
|
+
"&#{param_name}=#{param_content.strip}"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
attr_reader :condition_hash, :attribute, :interval_condition, :interval_type, :interval
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -2,13 +2,15 @@ module Gitlab
|
|
2
2
|
module Triage
|
3
3
|
module CommandBuilders
|
4
4
|
class BaseCommandBuilder
|
5
|
-
def initialize(items)
|
5
|
+
def initialize(items, resource: nil, network: nil)
|
6
6
|
@items = Array.wrap(items)
|
7
7
|
@items.delete('')
|
8
|
+
@resource = resource&.with_indifferent_access
|
9
|
+
@network = network
|
8
10
|
end
|
9
11
|
|
10
12
|
def build_command
|
11
|
-
if
|
13
|
+
if items.any?
|
12
14
|
[slash_command_string, content_string].compact.join(separator)
|
13
15
|
else
|
14
16
|
""
|
@@ -17,6 +19,8 @@ module Gitlab
|
|
17
19
|
|
18
20
|
private
|
19
21
|
|
22
|
+
attr_reader :items, :resource, :network
|
23
|
+
|
20
24
|
def separator
|
21
25
|
' '
|
22
26
|
end
|
@@ -26,7 +30,7 @@ module Gitlab
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def content_string
|
29
|
-
|
33
|
+
items.map do |item|
|
30
34
|
format_item(item)
|
31
35
|
end.join(separator)
|
32
36
|
end
|
@@ -4,8 +4,25 @@ module Gitlab
|
|
4
4
|
module Triage
|
5
5
|
module CommandBuilders
|
6
6
|
class LabelCommandBuilder < BaseCommandBuilder
|
7
|
+
def build_command
|
8
|
+
ensure_labels_exist!
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
7
13
|
private
|
8
14
|
|
15
|
+
def ensure_labels_exist!
|
16
|
+
items.each do |label|
|
17
|
+
label_opts = { project_id: resource[:project_id], name: label }
|
18
|
+
|
19
|
+
unless Resource::Label.new(label_opts, network: network).exist?
|
20
|
+
raise Resource::Label::LabelDoesntExistError,
|
21
|
+
"Label `#{label}` doesn't exist!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
9
26
|
def slash_command_string
|
10
27
|
"/label"
|
11
28
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'base_command_builder'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module CommandBuilders
|
6
|
+
class MoveCommandBuilder < BaseCommandBuilder
|
7
|
+
private
|
8
|
+
|
9
|
+
def slash_command_string
|
10
|
+
"/move"
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_item(item)
|
14
|
+
item
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_support/core_ext/array/wrap'
|
4
|
+
require 'cgi'
|
4
5
|
|
5
6
|
require_relative 'base_command_builder'
|
6
7
|
require_relative '../resource/context'
|
@@ -33,13 +34,9 @@ module Gitlab
|
|
33
34
|
}.freeze
|
34
35
|
PLACEHOLDER_REGEX = /{{([\w\.]+)}}/.freeze
|
35
36
|
|
36
|
-
attr_reader :resource, :network
|
37
|
-
|
38
37
|
def initialize(
|
39
38
|
items, resource: nil, network: nil, redact_confidentials: true)
|
40
|
-
super(items)
|
41
|
-
@resource = resource&.with_indifferent_access
|
42
|
-
@network = network
|
39
|
+
super(items, resource: resource, network: network)
|
43
40
|
@redact_confidentials = redact_confidentials
|
44
41
|
end
|
45
42
|
|
@@ -76,7 +73,22 @@ module Gitlab
|
|
76
73
|
template.sub(PLACEHOLDER_REGEX, attribute.to_s)
|
77
74
|
end.join(', ')
|
78
75
|
|
79
|
-
|
76
|
+
escaped_text =
|
77
|
+
case placeholder
|
78
|
+
when :items
|
79
|
+
# We don't need to escape it because it's recursive,
|
80
|
+
# which the contents should all be escaped already.
|
81
|
+
# Or put it another way, items isn't an attribute
|
82
|
+
# retrieved externally. It's a generated value which
|
83
|
+
# should be safe to begin with. At some point we
|
84
|
+
# may want to make this more distinguishable,
|
85
|
+
# separating values from API and values generated.
|
86
|
+
formatted_text
|
87
|
+
else
|
88
|
+
CGI.escape_html(formatted_text)
|
89
|
+
end
|
90
|
+
|
91
|
+
comment.gsub("{{#{placeholder}}}", escaped_text)
|
80
92
|
end
|
81
93
|
end
|
82
94
|
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -2,10 +2,8 @@ require 'active_support/all'
|
|
2
2
|
require 'active_support/inflector'
|
3
3
|
|
4
4
|
require_relative 'expand_condition'
|
5
|
-
require_relative 'filters/issuable_date_conditions_filter'
|
6
5
|
require_relative 'filters/merge_request_date_conditions_filter'
|
7
6
|
require_relative 'filters/votes_conditions_filter'
|
8
|
-
require_relative 'filters/forbidden_labels_conditions_filter'
|
9
7
|
require_relative 'filters/no_additional_labels_conditions_filter'
|
10
8
|
require_relative 'filters/author_member_conditions_filter'
|
11
9
|
require_relative 'filters/assignee_member_conditions_filter'
|
@@ -16,6 +14,7 @@ require_relative 'policies/rule_policy'
|
|
16
14
|
require_relative 'policies/summary_policy'
|
17
15
|
require_relative 'policies_resources/rule_resources'
|
18
16
|
require_relative 'policies_resources/summary_resources'
|
17
|
+
require_relative 'api_query_builders/date_query_param_builder'
|
19
18
|
require_relative 'api_query_builders/single_query_param_builder'
|
20
19
|
require_relative 'api_query_builders/multi_query_param_builder'
|
21
20
|
require_relative 'url_builders/url_builder'
|
@@ -38,6 +37,7 @@ module Gitlab
|
|
38
37
|
@options = options
|
39
38
|
@network_adapter_class = network_adapter_class
|
40
39
|
|
40
|
+
assert_all!
|
41
41
|
assert_project_id!
|
42
42
|
assert_token!
|
43
43
|
require_ruby_files
|
@@ -66,6 +66,7 @@ module Gitlab
|
|
66
66
|
|
67
67
|
def assert_project_id!
|
68
68
|
return if options.source_id
|
69
|
+
return if options.all
|
69
70
|
|
70
71
|
raise ArgumentError, 'A project_id is needed (pass it with the `--source-id` option)!'
|
71
72
|
end
|
@@ -76,6 +77,11 @@ module Gitlab
|
|
76
77
|
raise ArgumentError, 'A token is needed (pass it with the `--token` option)!'
|
77
78
|
end
|
78
79
|
|
80
|
+
def assert_all!
|
81
|
+
raise ArgumentError, '--all-projects option cannot be used in conjunction with --source and --source-id option!' if
|
82
|
+
options.all && (options.source || options.source_id)
|
83
|
+
end
|
84
|
+
|
79
85
|
def require_ruby_files
|
80
86
|
options.require_files.each(&method(:require))
|
81
87
|
end
|
@@ -146,7 +152,7 @@ module Gitlab
|
|
146
152
|
end
|
147
153
|
|
148
154
|
def resources_for_rule(resource_type, rule)
|
149
|
-
puts Gitlab::Triage::UI.header("
|
155
|
+
puts Gitlab::Triage::UI.header("Gathering resources for rule: **#{rule[:name]}**", char: '-')
|
150
156
|
|
151
157
|
ExpandCondition.perform(rule_conditions(rule)) do |conditions|
|
152
158
|
# retrieving the resources for every rule is inefficient
|
@@ -186,22 +192,31 @@ module Gitlab
|
|
186
192
|
resources.select do |resource|
|
187
193
|
results = []
|
188
194
|
|
195
|
+
# rubocop:disable Style/IfUnlessModifier
|
189
196
|
if conditions[:date]
|
190
|
-
results <<
|
191
|
-
when 'issues'
|
192
|
-
Filters::IssuableDateConditionsFilter.new(resource, conditions[:date]).calculate
|
193
|
-
when 'merge_requests'
|
194
|
-
Filters::MergeRequestDateConditionsFilter.new(resource, conditions[:date]).calculate
|
195
|
-
else
|
196
|
-
raise "Unknown resource type: #{resource[:type]}"
|
197
|
-
end
|
197
|
+
results << Filters::MergeRequestDateConditionsFilter.new(resource, conditions[:date]).calculate
|
198
198
|
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
199
|
+
|
200
|
+
if conditions[:upvotes]
|
201
|
+
results << Filters::VotesConditionsFilter.new(resource, conditions[:upvotes]).calculate
|
202
|
+
end
|
203
|
+
|
204
|
+
if conditions[:no_additional_labels]
|
205
|
+
results << Filters::NoAdditionalLabelsConditionsFilter.new(resource, conditions.fetch(:labels) { [] }).calculate
|
206
|
+
end
|
207
|
+
|
208
|
+
if conditions[:author_member]
|
209
|
+
results << Filters::AuthorMemberConditionsFilter.new(resource, conditions[:author_member], network).calculate
|
210
|
+
end
|
211
|
+
|
212
|
+
if conditions[:assignee_member]
|
213
|
+
results << Filters::AssigneeMemberConditionsFilter.new(resource, conditions[:assignee_member], network).calculate
|
214
|
+
end
|
215
|
+
|
216
|
+
if conditions[:ruby]
|
217
|
+
results << Filters::RubyConditionsFilter.new(resource, conditions, network).calculate
|
218
|
+
end
|
219
|
+
# rubocop:enable Style/IfUnlessModifier
|
205
220
|
|
206
221
|
results.all?
|
207
222
|
end
|
@@ -224,17 +239,27 @@ module Gitlab
|
|
224
239
|
|
225
240
|
condition_builders = []
|
226
241
|
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels]
|
242
|
+
|
243
|
+
if conditions[:forbidden_labels]
|
244
|
+
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('not[labels]', conditions[:forbidden_labels], ',')
|
245
|
+
end
|
246
|
+
|
227
247
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('state', conditions[:state]) if conditions[:state]
|
228
248
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('milestone', Array(conditions[:milestone])[0]) if conditions[:milestone]
|
229
249
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('source_branch', conditions[:source_branch]) if conditions[:source_branch]
|
230
250
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('target_branch', conditions[:target_branch]) if conditions[:target_branch]
|
231
251
|
|
252
|
+
if conditions[:date] && APIQueryBuilders::DateQueryParamBuilder.applicable?(conditions[:date])
|
253
|
+
condition_builders << APIQueryBuilders::DateQueryParamBuilder.new(conditions.delete(:date))
|
254
|
+
end
|
255
|
+
|
232
256
|
condition_builders.each do |condition_builder|
|
233
257
|
params[condition_builder.param_name] = condition_builder.param_content
|
234
258
|
end
|
235
259
|
|
236
260
|
UrlBuilders::UrlBuilder.new(
|
237
261
|
network_options: options,
|
262
|
+
all: options.all,
|
238
263
|
source: options.source,
|
239
264
|
source_id: options.source_id,
|
240
265
|
resource_type: resource_type,
|
data/lib/gitlab/triage/errors.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
require_relative 'errors/network'
|
@@ -1,21 +1,67 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
5
|
module Filters
|
6
|
-
class MergeRequestDateConditionsFilter <
|
7
|
-
ATTRIBUTES = %w[
|
6
|
+
class MergeRequestDateConditionsFilter < BaseConditionsFilter
|
7
|
+
ATTRIBUTES = %w[merged_at].freeze
|
8
|
+
CONDITIONS = %w[older_than newer_than].freeze
|
9
|
+
INTERVAL_TYPES = %w[days weeks months years].freeze
|
10
|
+
|
11
|
+
def self.allowed_attributes
|
12
|
+
self::ATTRIBUTES
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.filter_parameters
|
16
|
+
[
|
17
|
+
{
|
18
|
+
name: :attribute,
|
19
|
+
type: String,
|
20
|
+
values: allowed_attributes
|
21
|
+
},
|
22
|
+
{
|
23
|
+
name: :condition,
|
24
|
+
type: String,
|
25
|
+
values: CONDITIONS
|
26
|
+
},
|
27
|
+
{
|
28
|
+
name: :interval_type,
|
29
|
+
type: String,
|
30
|
+
values: INTERVAL_TYPES
|
31
|
+
},
|
32
|
+
{
|
33
|
+
name: :interval,
|
34
|
+
type: Numeric
|
35
|
+
}
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize_variables(condition)
|
40
|
+
@attribute = condition[:attribute].to_sym
|
41
|
+
@condition = condition[:condition].to_sym
|
42
|
+
@interval_type = condition[:interval_type].to_sym
|
43
|
+
@interval = condition[:interval]
|
44
|
+
end
|
8
45
|
|
9
46
|
# Guard against merge requests with no merged_at values
|
10
47
|
def resource_value
|
11
|
-
|
48
|
+
@resource[@attribute]&.to_date
|
49
|
+
end
|
50
|
+
|
51
|
+
def condition_value
|
52
|
+
@interval.public_send(@interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
|
12
53
|
end
|
13
54
|
|
14
55
|
# Guard against merge requests with no merged_at values
|
15
56
|
def calculate
|
16
57
|
return false unless resource_value
|
17
58
|
|
18
|
-
|
59
|
+
case @condition
|
60
|
+
when :older_than
|
61
|
+
resource_value < condition_value
|
62
|
+
when :newer_than
|
63
|
+
resource_value > condition_value
|
64
|
+
end
|
19
65
|
end
|
20
66
|
end
|
21
67
|
end
|