gitlab-triage 1.28.0 → 1.29.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/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
|