gitlab-triage 1.28.0 → 1.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -5
- data/lib/gitlab/triage/action/comment.rb +6 -1
- data/lib/gitlab/triage/engine.rb +126 -27
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +13 -5
- data/lib/gitlab/triage/option_parser.rb +4 -0
- data/lib/gitlab/triage/options.rb +2 -0
- data/lib/gitlab/triage/ui.rb +4 -0
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f594160679b71928b75f5bec0131c846d79b10cdc0cebb33388d1bc907b9769
|
4
|
+
data.tar.gz: 04a02e449720f03ab2820c3be0693c17486e366a7bcfe87a1c1f6e02ee6a02da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44cc6360a513c7ad126ee8dbc4e5151fafbb8b4994e5e0ea84a2fdb5a32ec997271bd66ee3b065cbbbfb3e92548a38d599c025a6f32f378339522f836001cadf
|
7
|
+
data.tar.gz: 1177f4e8471c3fbe6606e5e0eeaee384335b327774a94574e9942d1928b7dec03c50b8c90df082091b7873f6f871c70a8b162732fa3740a222c861c1751f3f7a
|
data/README.md
CHANGED
@@ -586,6 +586,7 @@ Example:
|
|
586
586
|
```yml
|
587
587
|
conditions:
|
588
588
|
health_status: Any
|
589
|
+
```
|
589
590
|
|
590
591
|
> **Note:** This query is not supported using GraphQL yet.
|
591
592
|
|
@@ -628,9 +629,9 @@ conditions:
|
|
628
629
|
threshold: 15
|
629
630
|
```
|
630
631
|
|
631
|
-
##### Protected condition
|
632
|
+
##### Protected condition
|
632
633
|
|
633
|
-
** This condition is only applicable for branches**
|
634
|
+
** This condition is only applicable for branches**
|
634
635
|
|
635
636
|
Accept a boolean.
|
636
637
|
If not specified, default to `false` to filter out protected branches.
|
@@ -731,6 +732,7 @@ Available action types:
|
|
731
732
|
- [`mention` action](#mention-action)
|
732
733
|
- [`move` action](#move-action)
|
733
734
|
- [`comment` action](#comment-action)
|
735
|
+
- [`redact_confidential_resources` option](#redact-confidential-resources-option)
|
734
736
|
- [`comment_type` action option](#comment-type-action-option)
|
735
737
|
- [`summarize` action](#summarize-action)
|
736
738
|
- [`comment_on_summary` action](#comment-on-summary-action)
|
@@ -868,6 +870,22 @@ actions:
|
|
868
870
|
{{author}} Are you still interested in finishing this merge request?
|
869
871
|
```
|
870
872
|
|
873
|
+
###### Redact confidential resources option
|
874
|
+
|
875
|
+
Determines if the data of confidential resources is redacted.
|
876
|
+
|
877
|
+
If the option is set to `true` or not set, data from confidential will appear as `(confidential)`. \
|
878
|
+
When it is set to `false`, everything will be revealed and visible.
|
879
|
+
|
880
|
+
Example:
|
881
|
+
|
882
|
+
```yml
|
883
|
+
actions:
|
884
|
+
redact_confidential_resources: false
|
885
|
+
comment: |
|
886
|
+
{{author}} Are you still interested in finishing this merge request?
|
887
|
+
```
|
888
|
+
|
871
889
|
##### Comment type action option
|
872
890
|
|
873
891
|
Determines the type of comment to be added to the resource.
|
@@ -1111,11 +1129,11 @@ resource_rules:
|
|
1111
1129
|
|
1112
1130
|
**This action is only applicable for branches.**
|
1113
1131
|
|
1114
|
-
Delete the resource.
|
1132
|
+
Delete the resource.
|
1115
1133
|
|
1116
1134
|
Accept a boolean. Set to `true` to enable.
|
1117
1135
|
|
1118
|
-
Example :
|
1136
|
+
Example :
|
1119
1137
|
```yaml
|
1120
1138
|
resource_rules:
|
1121
1139
|
branches:
|
@@ -1327,15 +1345,18 @@ Usage: gitlab-triage [options]
|
|
1327
1345
|
|
1328
1346
|
-n, --dry-run Don't actually update anything, just print
|
1329
1347
|
-f, --policies-file [string] A valid policies YML file
|
1348
|
+
--all-projects Process all projects the token has access to
|
1330
1349
|
-s, --source [type] The source type between [ projects or groups ], default value: projects
|
1331
1350
|
-i, --source-id [string] Source ID or path
|
1332
1351
|
-p, --project-id [string] [Deprecated] A project ID or path, please use `--source-id`
|
1352
|
+
--resource-reference [string]
|
1353
|
+
Resource short-reference, e.g. #42, !33, or &99
|
1333
1354
|
-t, --token [string] A valid API token
|
1334
1355
|
-H, --host-url [string] A valid host url
|
1335
1356
|
-r, --require [string] Require a file before performing
|
1336
1357
|
-d, --debug Print debug information
|
1337
1358
|
-h, --help Print help message
|
1338
|
-
|
1359
|
+
-v, --version Print version
|
1339
1360
|
--init Initialize the project with a policy file
|
1340
1361
|
--init-ci Initialize the project with a .gitlab-ci.yml file
|
1341
1362
|
```
|
@@ -1372,6 +1393,14 @@ For example- after cloning this project, from the root `gitlab-triage` directory
|
|
1372
1393
|
bundle exec bin/gitlab-triage --dry-run --token $GITLAB_API_TOKEN --source-id gitlab-org/triage
|
1373
1394
|
```
|
1374
1395
|
|
1396
|
+
Triaging against specific resource:
|
1397
|
+
|
1398
|
+
```
|
1399
|
+
gitlab-triage --dry-run --token $API_TOKEN --source-id gitlab-org/triage --resource-reference '#42'
|
1400
|
+
gitlab-triage --dry-run --token $API_TOKEN --source-id gitlab-org/triage --resource-reference '!33'
|
1401
|
+
gitlab-triage --dry-run --token $API_TOKEN --source groups --source-id gitlab-org --resource-reference '&99'
|
1402
|
+
```
|
1403
|
+
|
1375
1404
|
#### Running on GitLab CI pipeline
|
1376
1405
|
|
1377
1406
|
You can enforce policies using a scheduled pipeline:
|
@@ -45,7 +45,12 @@ module Gitlab
|
|
45
45
|
def build_comment(resource)
|
46
46
|
CommandBuilders::CommentCommandBuilder.new(
|
47
47
|
[
|
48
|
-
CommandBuilders::TextContentBuilder.new(
|
48
|
+
CommandBuilders::TextContentBuilder.new(
|
49
|
+
policy.actions[:comment],
|
50
|
+
resource: resource,
|
51
|
+
network: network,
|
52
|
+
redact_confidentials: policy.actions.fetch(:redact_confidential_resources, true)
|
53
|
+
).build_command,
|
49
54
|
CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels], resource: resource, network: network).build_command,
|
50
55
|
CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels], resource: resource, network: network).build_command,
|
51
56
|
CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command,
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -44,6 +44,7 @@ module Gitlab
|
|
44
44
|
MILESTONE_TIMEBOX_VALUES = %w[none any upcoming started].freeze
|
45
45
|
ITERATION_SELECTION_VALUES = %w[none any].freeze
|
46
46
|
EpicsTriagingForProjectImpossibleError = Class.new(StandardError)
|
47
|
+
MultiPolicyInInjectionModeError = Class.new(StandardError)
|
47
48
|
|
48
49
|
def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
|
49
50
|
options.host_url = policies.delete(:host_url) { options.host_url }
|
@@ -56,21 +57,23 @@ module Gitlab
|
|
56
57
|
@network_adapter_class = network_adapter_class
|
57
58
|
@graphql_network_adapter_class = graphql_network_adapter_class
|
58
59
|
|
59
|
-
|
60
|
-
|
60
|
+
assert_options!
|
61
|
+
|
62
|
+
@options.source = @options.source.to_s
|
63
|
+
|
61
64
|
require_ruby_files
|
62
65
|
end
|
63
66
|
|
64
67
|
def perform
|
65
68
|
puts "Performing a dry run.\n\n" if options.dry_run
|
66
69
|
|
67
|
-
puts Gitlab::Triage::UI.header("Triaging the `#{options.source_id}` #{options.source.
|
70
|
+
puts Gitlab::Triage::UI.header("Triaging the `#{options.source_id}` #{options.source.singularize}", char: '=')
|
68
71
|
puts
|
69
72
|
|
70
73
|
resource_rules.each do |resource_type, policy_definition|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
next unless right_resource_type_for_resource_option?(resource_type)
|
75
|
+
|
76
|
+
assert_epic_rule!(resource_type)
|
74
77
|
|
75
78
|
puts Gitlab::Triage::UI.header("Processing summaries & rules for #{resource_type}", char: '-')
|
76
79
|
puts
|
@@ -94,22 +97,86 @@ module Gitlab
|
|
94
97
|
|
95
98
|
private
|
96
99
|
|
97
|
-
def
|
100
|
+
def assert_options!
|
101
|
+
assert_all!
|
102
|
+
assert_source!
|
103
|
+
assert_source_id!
|
104
|
+
assert_resource_reference!
|
105
|
+
end
|
106
|
+
|
107
|
+
# rubocop:disable Style/IfUnlessModifier
|
108
|
+
def assert_all!
|
109
|
+
return unless options.all
|
110
|
+
|
111
|
+
if options.source
|
112
|
+
raise ArgumentError, '--all-projects option cannot be used in conjunction with --source option!'
|
113
|
+
end
|
114
|
+
|
115
|
+
if options.source_id
|
116
|
+
raise ArgumentError, '--all-projects option cannot be used in conjunction with --source-id option!'
|
117
|
+
end
|
118
|
+
|
119
|
+
if options.resource_reference # rubocop:disable Style/GuardClause
|
120
|
+
raise ArgumentError, '--all-projects option cannot be used in conjunction with --resource-reference option!'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
# rubocop:enable Style/IfUnlessModifier
|
124
|
+
|
125
|
+
def assert_source!
|
126
|
+
return if options.source
|
127
|
+
return if options.all
|
128
|
+
|
129
|
+
raise ArgumentError, 'A source is needed (pass it with the `--source` option)!'
|
130
|
+
end
|
131
|
+
|
132
|
+
def assert_source_id!
|
98
133
|
return if options.source_id
|
99
134
|
return if options.all
|
100
135
|
|
101
|
-
raise ArgumentError, 'A
|
136
|
+
raise ArgumentError, 'A project or group ID is needed (pass it with the `--source-id` option)!'
|
102
137
|
end
|
103
138
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
139
|
+
def assert_resource_reference!
|
140
|
+
return unless options.resource_reference
|
141
|
+
|
142
|
+
if options.source == 'groups' && !options.resource_reference.start_with?('&')
|
143
|
+
raise ArgumentError, "--resource-reference can only start with '&' when --source=groups is passed ('#{options.resource_reference}' passed)!"
|
144
|
+
end
|
145
|
+
|
146
|
+
if options.source == 'projects' && !options.resource_reference.start_with?('#', '!') # rubocop:disable Style/GuardClause
|
147
|
+
raise(
|
148
|
+
ArgumentError,
|
149
|
+
"--resource-reference can only start with '#' or '!' when --source=projects is passed " \
|
150
|
+
"('#{options.resource_reference}' passed)!"
|
151
|
+
)
|
152
|
+
end
|
107
153
|
end
|
108
154
|
|
109
155
|
def require_ruby_files
|
110
156
|
options.require_files.each(&method(:require))
|
111
157
|
end
|
112
158
|
|
159
|
+
def right_resource_type_for_resource_option?(resource_type)
|
160
|
+
return true unless options.resource_reference
|
161
|
+
|
162
|
+
resource_reference = options.resource_reference
|
163
|
+
|
164
|
+
case resource_type
|
165
|
+
when 'issues'
|
166
|
+
resource_reference.start_with?('#')
|
167
|
+
when 'merge_requests'
|
168
|
+
resource_reference.start_with?('!')
|
169
|
+
when 'epics'
|
170
|
+
resource_reference.start_with?('&')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_epic_rule!(resource_type)
|
175
|
+
return if resource_type != 'epics' || options.source == 'groups'
|
176
|
+
|
177
|
+
raise EpicsTriagingForProjectImpossibleError, "Epics can only be triaged at the group level. Please set the `--source groups` option."
|
178
|
+
end
|
179
|
+
|
113
180
|
def resource_rules
|
114
181
|
@resource_rules ||= policies.delete(:resource_rules) { {} }
|
115
182
|
end
|
@@ -279,20 +346,8 @@ module Gitlab
|
|
279
346
|
ExpandCondition.perform(rule_conditions(rule_definition)) do |expanded_conditions|
|
280
347
|
# retrieving the resources for every rule is inefficient
|
281
348
|
# however, previous rules may affect those upcoming
|
282
|
-
resources =
|
283
|
-
|
284
|
-
if rule_definition[:api] == 'graphql'
|
285
|
-
graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
|
286
|
-
resources = graphql_network.query(graphql_query, source: source_full_path)
|
287
|
-
else
|
288
|
-
resources = network.query_api(build_get_url(resource_type, expanded_conditions))
|
289
|
-
iids = resources.pluck('iid').map(&:to_s)
|
290
|
-
|
291
|
-
graphql_query = build_graphql_query(resource_type, expanded_conditions)
|
292
|
-
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
|
293
|
-
|
294
|
-
decorate_resources_with_graphql_data(resources, graphql_resources)
|
295
|
-
end
|
349
|
+
resources = options.resources ||
|
350
|
+
fetch_resources(resource_type, expanded_conditions, rule_definition)
|
296
351
|
|
297
352
|
# In some filters/actions we want to know which resource type it is
|
298
353
|
attach_resource_type(resources, resource_type)
|
@@ -310,6 +365,38 @@ module Gitlab
|
|
310
365
|
end
|
311
366
|
end
|
312
367
|
|
368
|
+
def fetch_resources(resource_type, expanded_conditions, rule_definition)
|
369
|
+
resources = []
|
370
|
+
|
371
|
+
if rule_definition[:api] == 'graphql'
|
372
|
+
graphql_query_options = { source: source_full_path }
|
373
|
+
|
374
|
+
if options.resource_reference
|
375
|
+
expanded_conditions[:iids] = options.resource_reference[1..]
|
376
|
+
graphql_query_options[:iids] = [expanded_conditions[:iids]]
|
377
|
+
end
|
378
|
+
|
379
|
+
graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
|
380
|
+
|
381
|
+
resources = graphql_network.query(graphql_query, **graphql_query_options)
|
382
|
+
else
|
383
|
+
# FIXME: Epics listing endpoint doesn't support filtering by `iids`, so instead we
|
384
|
+
# get a single epic when `--resource-reference` is given for epics.
|
385
|
+
# Because of that, the query could return a single epic, so we make sure we get an array.
|
386
|
+
resources = Array(network.query_api(build_get_url(resource_type, expanded_conditions)))
|
387
|
+
|
388
|
+
iids = resources.pluck('iid').map(&:to_s)
|
389
|
+
expanded_conditions[:iids] = iids
|
390
|
+
|
391
|
+
graphql_query = build_graphql_query(resource_type, expanded_conditions)
|
392
|
+
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
|
393
|
+
|
394
|
+
decorate_resources_with_graphql_data(resources, graphql_resources)
|
395
|
+
end
|
396
|
+
|
397
|
+
resources
|
398
|
+
end
|
399
|
+
|
313
400
|
def attach_resource_type(resources, resource_type)
|
314
401
|
resources.each { |resource| resource[:type] = resource_type }
|
315
402
|
end
|
@@ -394,6 +481,8 @@ module Gitlab
|
|
394
481
|
end
|
395
482
|
end
|
396
483
|
|
484
|
+
# rubocop:disable Metrics/AbcSize
|
485
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
397
486
|
def build_get_url(resource_type, conditions)
|
398
487
|
# Example issues query with state and labels
|
399
488
|
# https://gitlab.com/api/v4/projects/test-triage%2Fissue-project/issues?state=open&labels=project%20label%20with%20spaces,group_label_no_spaces
|
@@ -402,6 +491,8 @@ module Gitlab
|
|
402
491
|
}
|
403
492
|
|
404
493
|
condition_builders = []
|
494
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('iids', options.resource_reference[1..]) if options.resource_reference
|
495
|
+
|
405
496
|
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels]
|
406
497
|
|
407
498
|
if conditions[:forbidden_labels]
|
@@ -434,15 +525,23 @@ module Gitlab
|
|
434
525
|
params[condition_builder.param_name] = condition_builder.param_content
|
435
526
|
end
|
436
527
|
|
437
|
-
|
528
|
+
url_builder_options = {
|
438
529
|
network_options: options,
|
439
530
|
all: options.all,
|
440
531
|
source: options.source,
|
441
532
|
source_id: options.source_id,
|
442
533
|
resource_type: resource_type,
|
443
534
|
params: params
|
444
|
-
|
535
|
+
}
|
536
|
+
|
537
|
+
# FIXME: Epics listing endpoint doesn't support filtering by `iids`, so instead we
|
538
|
+
# get a single epic when `--resource-reference` is given for epics.
|
539
|
+
url_builder_options[:resource_id] = options.resource_reference[1..] if options.resource_reference && resource_type == 'epics'
|
540
|
+
|
541
|
+
UrlBuilders::UrlBuilder.new(url_builder_options).build
|
445
542
|
end
|
543
|
+
# rubocop:enable Metrics/AbcSize
|
544
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
446
545
|
|
447
546
|
def milestone_condition_builder(resource_type, milestone_condition)
|
448
547
|
milestone_value = Array(milestone_condition)[0].to_s # back-compatibility
|
@@ -11,6 +11,10 @@ module Gitlab
|
|
11
11
|
@resource_type = resource_type
|
12
12
|
@conditions = conditions
|
13
13
|
@graphql_only = graphql_only
|
14
|
+
@resource_declarations = [
|
15
|
+
'$source: ID!',
|
16
|
+
'$after: String'
|
17
|
+
]
|
14
18
|
end
|
15
19
|
|
16
20
|
def resource_path
|
@@ -26,8 +30,7 @@ module Gitlab
|
|
26
30
|
resource_type: resource_type.to_s.camelize(:lower),
|
27
31
|
resource_fields: resource_fields.join(' '),
|
28
32
|
resource_query: resource_query,
|
29
|
-
|
30
|
-
iids_query: graphql_only ? nil : ', iids: $iids'
|
33
|
+
resource_declarations: resource_declarations.join(', ')
|
31
34
|
)
|
32
35
|
end
|
33
36
|
|
@@ -35,13 +38,13 @@ module Gitlab
|
|
35
38
|
|
36
39
|
private
|
37
40
|
|
38
|
-
attr_reader :source_type, :resource_type, :conditions, :graphql_only
|
41
|
+
attr_reader :source_type, :resource_type, :conditions, :graphql_only, :resource_declarations
|
39
42
|
|
40
43
|
BASE_QUERY = <<~GRAPHQL.freeze
|
41
|
-
query(
|
44
|
+
query(%{resource_declarations}) {
|
42
45
|
%{source_type}(fullPath: $source) {
|
43
46
|
id
|
44
|
-
%{resource_type}(after: $after%{
|
47
|
+
%{resource_type}(after: $after%{resource_query}) {
|
45
48
|
pageInfo {
|
46
49
|
hasNextPage
|
47
50
|
endCursor
|
@@ -86,6 +89,11 @@ module Gitlab
|
|
86
89
|
condition_queries << QueryParamBuilders::BaseParamBuilder.new('milestoneTitle', condition_params) if condition.to_s == 'milestone'
|
87
90
|
condition_queries << QueryParamBuilders::BaseParamBuilder.new('state', condition_params, with_quotes: false) if condition.to_s == 'state'
|
88
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
|
+
|
89
97
|
case resource_type
|
90
98
|
when 'issues'
|
91
99
|
condition_queries << issues_label_query(condition, condition_params)
|
@@ -43,6 +43,10 @@ module Gitlab
|
|
43
43
|
options.source_id = value
|
44
44
|
end
|
45
45
|
|
46
|
+
opts.on('--resource-reference [string]', String, 'Resource short-reference, e.g. #42, !33, or &99') do |value|
|
47
|
+
options.resource_reference = value
|
48
|
+
end
|
49
|
+
|
46
50
|
opts.on('-t', '--token [string]', String, 'A valid API token') do |value|
|
47
51
|
options.token = value
|
48
52
|
end
|
data/lib/gitlab/triage/ui.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-triage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.29.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|