gitlab-triage 1.27.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/.gitlab/CODEOWNERS +1 -1
- data/README.md +75 -10
- data/lib/gitlab/triage/action/comment.rb +6 -1
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +2 -1
- data/lib/gitlab/triage/engine.rb +139 -29
- data/lib/gitlab/triage/filters/branch_date_filter.rb +8 -2
- data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +3 -59
- 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/param_builders/date_param_builder.rb +8 -2
- data/lib/gitlab/triage/ui.rb +4 -0
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +3 -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/.gitlab/CODEOWNERS
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# The official maintainers
|
2
|
-
* @
|
2
|
+
* @gitlab-org/quality/engineering-productivity
|
data/README.md
CHANGED
@@ -178,6 +178,7 @@ Available condition types:
|
|
178
178
|
- [`draft` condition](#draft-condition)
|
179
179
|
- [`source_branch` condition](#source-branch-condition)
|
180
180
|
- [`target_branch` condition](#target-branch-condition)
|
181
|
+
- [`health_status` condition](#health-status-condition)
|
181
182
|
- [`weight` condition](#weight-condition)
|
182
183
|
- [`discussions` condition](#discussions-condition)
|
183
184
|
- [`protected` condition](#protected-condition)
|
@@ -188,12 +189,12 @@ Available condition types:
|
|
188
189
|
|
189
190
|
Accepts a hash of fields.
|
190
191
|
|
191
|
-
| Field | Type | Values
|
192
|
-
| --------- | ----
|
192
|
+
| Field | Type | Values | Required |
|
193
|
+
| --------- | ---- |----------------------------------------------------------------------------| -------- |
|
193
194
|
| `attribute` | string | `created_at`, `updated_at`, `merged_at`, `authored_date`, `committed_date` | yes |
|
194
|
-
| `condition` | string | `older_than`, `newer_than`
|
195
|
-
| `interval_type` | string | `days`, `weeks`, `months`, `years`
|
196
|
-
| `interval` | integer | integer
|
195
|
+
| `condition` | string | `older_than`, `newer_than` | yes |
|
196
|
+
| `interval_type` | string | `minutes`, `hours`, `days`, `weeks`, `months`, `years` | yes |
|
197
|
+
| `interval` | integer | integer | yes |
|
197
198
|
> **Note:**
|
198
199
|
> - `merged_at` only works on merge requests.
|
199
200
|
> - `closed_at` is not supported in the GitLab API, but can be used in a [`ruby` condition](#ruby-condition).
|
@@ -210,6 +211,21 @@ conditions:
|
|
210
211
|
interval: 12
|
211
212
|
```
|
212
213
|
|
214
|
+
> **Note:** If the GitLab server is giving 500 error with this option, it
|
215
|
+
> can mean that it's taking too much time to query this, and it's timing out.
|
216
|
+
> A workaround for this is that we can filter in Ruby. If you need this
|
217
|
+
> workaround, specify this with `filter_in_ruby: true`
|
218
|
+
>
|
219
|
+
> ```yaml
|
220
|
+
> conditions:
|
221
|
+
> date:
|
222
|
+
> attribute: updated_at
|
223
|
+
> condition: older_than
|
224
|
+
> interval_type: months
|
225
|
+
> interval: 12
|
226
|
+
> filter_in_ruby: true
|
227
|
+
> ```
|
228
|
+
|
213
229
|
##### Milestone condition
|
214
230
|
|
215
231
|
Accepts the name of a milestone to filter upon. Also accepts the following timebox values:
|
@@ -553,6 +569,27 @@ conditions:
|
|
553
569
|
target_branch: 'master'
|
554
570
|
```
|
555
571
|
|
572
|
+
##### Health Status condition
|
573
|
+
|
574
|
+
**This condition is only applicable for issues.**
|
575
|
+
|
576
|
+
Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues).
|
577
|
+
|
578
|
+
| State | Type | Value |
|
579
|
+
| --------- | ---- | ------ |
|
580
|
+
| Any health status | string | `Any` |
|
581
|
+
| No health status | string | `None` |
|
582
|
+
| Specific health status | string | One of `on_track`, `needs_attention` or `at_risk` |
|
583
|
+
|
584
|
+
Example:
|
585
|
+
|
586
|
+
```yml
|
587
|
+
conditions:
|
588
|
+
health_status: Any
|
589
|
+
```
|
590
|
+
|
591
|
+
> **Note:** This query is not supported using GraphQL yet.
|
592
|
+
|
556
593
|
##### Weight condition
|
557
594
|
|
558
595
|
**This condition is only applicable for issues.**
|
@@ -592,9 +629,9 @@ conditions:
|
|
592
629
|
threshold: 15
|
593
630
|
```
|
594
631
|
|
595
|
-
##### Protected condition
|
632
|
+
##### Protected condition
|
596
633
|
|
597
|
-
** This condition is only applicable for branches**
|
634
|
+
** This condition is only applicable for branches**
|
598
635
|
|
599
636
|
Accept a boolean.
|
600
637
|
If not specified, default to `false` to filter out protected branches.
|
@@ -695,6 +732,7 @@ Available action types:
|
|
695
732
|
- [`mention` action](#mention-action)
|
696
733
|
- [`move` action](#move-action)
|
697
734
|
- [`comment` action](#comment-action)
|
735
|
+
- [`redact_confidential_resources` option](#redact-confidential-resources-option)
|
698
736
|
- [`comment_type` action option](#comment-type-action-option)
|
699
737
|
- [`summarize` action](#summarize-action)
|
700
738
|
- [`comment_on_summary` action](#comment-on-summary-action)
|
@@ -832,6 +870,22 @@ actions:
|
|
832
870
|
{{author}} Are you still interested in finishing this merge request?
|
833
871
|
```
|
834
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
|
+
|
835
889
|
##### Comment type action option
|
836
890
|
|
837
891
|
Determines the type of comment to be added to the resource.
|
@@ -1075,11 +1129,11 @@ resource_rules:
|
|
1075
1129
|
|
1076
1130
|
**This action is only applicable for branches.**
|
1077
1131
|
|
1078
|
-
Delete the resource.
|
1132
|
+
Delete the resource.
|
1079
1133
|
|
1080
1134
|
Accept a boolean. Set to `true` to enable.
|
1081
1135
|
|
1082
|
-
Example :
|
1136
|
+
Example :
|
1083
1137
|
```yaml
|
1084
1138
|
resource_rules:
|
1085
1139
|
branches:
|
@@ -1291,15 +1345,18 @@ Usage: gitlab-triage [options]
|
|
1291
1345
|
|
1292
1346
|
-n, --dry-run Don't actually update anything, just print
|
1293
1347
|
-f, --policies-file [string] A valid policies YML file
|
1348
|
+
--all-projects Process all projects the token has access to
|
1294
1349
|
-s, --source [type] The source type between [ projects or groups ], default value: projects
|
1295
1350
|
-i, --source-id [string] Source ID or path
|
1296
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
|
1297
1354
|
-t, --token [string] A valid API token
|
1298
1355
|
-H, --host-url [string] A valid host url
|
1299
1356
|
-r, --require [string] Require a file before performing
|
1300
1357
|
-d, --debug Print debug information
|
1301
1358
|
-h, --help Print help message
|
1302
|
-
|
1359
|
+
-v, --version Print version
|
1303
1360
|
--init Initialize the project with a policy file
|
1304
1361
|
--init-ci Initialize the project with a .gitlab-ci.yml file
|
1305
1362
|
```
|
@@ -1336,6 +1393,14 @@ For example- after cloning this project, from the root `gitlab-triage` directory
|
|
1336
1393
|
bundle exec bin/gitlab-triage --dry-run --token $GITLAB_API_TOKEN --source-id gitlab-org/triage
|
1337
1394
|
```
|
1338
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
|
+
|
1339
1404
|
#### Running on GitLab CI pipeline
|
1340
1405
|
|
1341
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,
|
@@ -8,7 +8,8 @@ module Gitlab
|
|
8
8
|
ATTRIBUTES = %w[updated_at created_at].freeze
|
9
9
|
|
10
10
|
def self.applicable?(condition)
|
11
|
-
ATTRIBUTES.include?(condition[:attribute].to_s)
|
11
|
+
ATTRIBUTES.include?(condition[:attribute].to_s) &&
|
12
|
+
condition[:filter_in_ruby] != true
|
12
13
|
end
|
13
14
|
|
14
15
|
def initialize(condition_hash)
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -2,6 +2,7 @@ require 'active_support/all'
|
|
2
2
|
require 'active_support/inflector'
|
3
3
|
|
4
4
|
require_relative 'expand_condition'
|
5
|
+
require_relative 'filters/issue_date_conditions_filter'
|
5
6
|
require_relative 'filters/merge_request_date_conditions_filter'
|
6
7
|
require_relative 'filters/branch_date_filter'
|
7
8
|
require_relative 'filters/branch_protected_filter'
|
@@ -43,6 +44,7 @@ module Gitlab
|
|
43
44
|
MILESTONE_TIMEBOX_VALUES = %w[none any upcoming started].freeze
|
44
45
|
ITERATION_SELECTION_VALUES = %w[none any].freeze
|
45
46
|
EpicsTriagingForProjectImpossibleError = Class.new(StandardError)
|
47
|
+
MultiPolicyInInjectionModeError = Class.new(StandardError)
|
46
48
|
|
47
49
|
def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
|
48
50
|
options.host_url = policies.delete(:host_url) { options.host_url }
|
@@ -55,21 +57,23 @@ module Gitlab
|
|
55
57
|
@network_adapter_class = network_adapter_class
|
56
58
|
@graphql_network_adapter_class = graphql_network_adapter_class
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
+
assert_options!
|
61
|
+
|
62
|
+
@options.source = @options.source.to_s
|
63
|
+
|
60
64
|
require_ruby_files
|
61
65
|
end
|
62
66
|
|
63
67
|
def perform
|
64
68
|
puts "Performing a dry run.\n\n" if options.dry_run
|
65
69
|
|
66
|
-
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: '=')
|
67
71
|
puts
|
68
72
|
|
69
73
|
resource_rules.each do |resource_type, policy_definition|
|
70
|
-
|
71
|
-
|
72
|
-
|
74
|
+
next unless right_resource_type_for_resource_option?(resource_type)
|
75
|
+
|
76
|
+
assert_epic_rule!(resource_type)
|
73
77
|
|
74
78
|
puts Gitlab::Triage::UI.header("Processing summaries & rules for #{resource_type}", char: '-')
|
75
79
|
puts
|
@@ -93,22 +97,86 @@ module Gitlab
|
|
93
97
|
|
94
98
|
private
|
95
99
|
|
96
|
-
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!
|
97
133
|
return if options.source_id
|
98
134
|
return if options.all
|
99
135
|
|
100
|
-
raise ArgumentError, 'A
|
136
|
+
raise ArgumentError, 'A project or group ID is needed (pass it with the `--source-id` option)!'
|
101
137
|
end
|
102
138
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
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
|
106
153
|
end
|
107
154
|
|
108
155
|
def require_ruby_files
|
109
156
|
options.require_files.each(&method(:require))
|
110
157
|
end
|
111
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
|
+
|
112
180
|
def resource_rules
|
113
181
|
@resource_rules ||= policies.delete(:resource_rules) { {} }
|
114
182
|
end
|
@@ -278,20 +346,8 @@ module Gitlab
|
|
278
346
|
ExpandCondition.perform(rule_conditions(rule_definition)) do |expanded_conditions|
|
279
347
|
# retrieving the resources for every rule is inefficient
|
280
348
|
# however, previous rules may affect those upcoming
|
281
|
-
resources =
|
282
|
-
|
283
|
-
if rule_definition[:api] == 'graphql'
|
284
|
-
graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
|
285
|
-
resources = graphql_network.query(graphql_query, source: source_full_path)
|
286
|
-
else
|
287
|
-
resources = network.query_api(build_get_url(resource_type, expanded_conditions))
|
288
|
-
iids = resources.pluck('iid').map(&:to_s)
|
289
|
-
|
290
|
-
graphql_query = build_graphql_query(resource_type, expanded_conditions)
|
291
|
-
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
|
292
|
-
|
293
|
-
decorate_resources_with_graphql_data(resources, graphql_resources)
|
294
|
-
end
|
349
|
+
resources = options.resources ||
|
350
|
+
fetch_resources(resource_type, expanded_conditions, rule_definition)
|
295
351
|
|
296
352
|
# In some filters/actions we want to know which resource type it is
|
297
353
|
attach_resource_type(resources, resource_type)
|
@@ -309,6 +365,38 @@ module Gitlab
|
|
309
365
|
end
|
310
366
|
end
|
311
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
|
+
|
312
400
|
def attach_resource_type(resources, resource_type)
|
313
401
|
resources.each { |resource| resource[:type] = resource_type }
|
314
402
|
end
|
@@ -328,7 +416,7 @@ module Gitlab
|
|
328
416
|
puts
|
329
417
|
end
|
330
418
|
|
331
|
-
def filter_resources(resources, conditions) # rubocop:disable Metrics/CyclomaticComplexity
|
419
|
+
def filter_resources(resources, conditions) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
332
420
|
resources.select do |resource|
|
333
421
|
results = []
|
334
422
|
|
@@ -337,8 +425,17 @@ module Gitlab
|
|
337
425
|
case resource[:type]
|
338
426
|
when 'branches'
|
339
427
|
results << Filters::BranchDateFilter.new(resource, conditions[:date]).calculate
|
428
|
+
when 'issues'
|
429
|
+
if conditions.dig(:date, :filter_in_ruby)
|
430
|
+
results << Filters::IssueDateConditionsFilter.new(resource, conditions[:date]).calculate
|
431
|
+
end
|
340
432
|
when 'merge_requests'
|
341
|
-
|
433
|
+
if conditions.dig(:date, :filter_in_ruby) ||
|
434
|
+
# REST API does not support filtering with merged_at,
|
435
|
+
# so we have to filter it in Ruby
|
436
|
+
conditions.dig(:date, :attribute) == 'merged_at'
|
437
|
+
results << Filters::MergeRequestDateConditionsFilter.new(resource, conditions[:date]).calculate
|
438
|
+
end
|
342
439
|
end
|
343
440
|
end
|
344
441
|
|
@@ -384,6 +481,8 @@ module Gitlab
|
|
384
481
|
end
|
385
482
|
end
|
386
483
|
|
484
|
+
# rubocop:disable Metrics/AbcSize
|
485
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
387
486
|
def build_get_url(resource_type, conditions)
|
388
487
|
# Example issues query with state and labels
|
389
488
|
# https://gitlab.com/api/v4/projects/test-triage%2Fissue-project/issues?state=open&labels=project%20label%20with%20spaces,group_label_no_spaces
|
@@ -392,6 +491,8 @@ module Gitlab
|
|
392
491
|
}
|
393
492
|
|
394
493
|
condition_builders = []
|
494
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('iids', options.resource_reference[1..]) if options.resource_reference
|
495
|
+
|
395
496
|
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels]
|
396
497
|
|
397
498
|
if conditions[:forbidden_labels]
|
@@ -424,15 +525,23 @@ module Gitlab
|
|
424
525
|
params[condition_builder.param_name] = condition_builder.param_content
|
425
526
|
end
|
426
527
|
|
427
|
-
|
528
|
+
url_builder_options = {
|
428
529
|
network_options: options,
|
429
530
|
all: options.all,
|
430
531
|
source: options.source,
|
431
532
|
source_id: options.source_id,
|
432
533
|
resource_type: resource_type,
|
433
534
|
params: params
|
434
|
-
|
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
|
435
542
|
end
|
543
|
+
# rubocop:enable Metrics/AbcSize
|
544
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
436
545
|
|
437
546
|
def milestone_condition_builder(resource_type, milestone_condition)
|
438
547
|
milestone_value = Array(milestone_condition)[0].to_s # back-compatibility
|
@@ -464,6 +573,7 @@ module Gitlab
|
|
464
573
|
[].tap do |condition_builders|
|
465
574
|
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('weight', conditions[:weight]) if conditions[:weight]
|
466
575
|
condition_builders << iteration_condition_builder(conditions[:iteration]) if conditions[:iteration]
|
576
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('health_status', conditions[:health_status]) if conditions[:health_status]
|
467
577
|
end
|
468
578
|
end
|
469
579
|
|
@@ -6,7 +6,9 @@ module Gitlab
|
|
6
6
|
class BranchDateFilter < BaseConditionsFilter
|
7
7
|
ATTRIBUTES = %w[committed_date authored_date].freeze
|
8
8
|
CONDITIONS = %w[older_than newer_than].freeze
|
9
|
-
|
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
|
10
12
|
|
11
13
|
def self.allowed_attributes
|
12
14
|
self::ATTRIBUTES
|
@@ -48,7 +50,11 @@ module Gitlab
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def condition_value
|
51
|
-
|
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
|
52
58
|
end
|
53
59
|
|
54
60
|
def calculate
|
@@ -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
|
@@ -3,65 +3,9 @@ require_relative 'base_conditions_filter'
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
5
|
module Filters
|
6
|
-
class MergeRequestDateConditionsFilter <
|
7
|
-
|
8
|
-
|
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
|
45
|
-
|
46
|
-
# Guard against merge requests with no merged_at values
|
47
|
-
def resource_value
|
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
|
53
|
-
end
|
54
|
-
|
55
|
-
# Guard against merge requests with no merged_at values
|
56
|
-
def calculate
|
57
|
-
return false unless resource_value
|
58
|
-
|
59
|
-
case @condition
|
60
|
-
when :older_than
|
61
|
-
resource_value < condition_value
|
62
|
-
when :newer_than
|
63
|
-
resource_value > condition_value
|
64
|
-
end
|
6
|
+
class MergeRequestDateConditionsFilter < IssueDateConditionsFilter
|
7
|
+
def self.generate_allowed_attributes
|
8
|
+
super << 'merged_at'
|
65
9
|
end
|
66
10
|
end
|
67
11
|
end
|
@@ -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
|
@@ -5,7 +5,9 @@ module Gitlab
|
|
5
5
|
module ParamBuilders
|
6
6
|
class DateParamBuilder
|
7
7
|
CONDITIONS = %w[older_than newer_than].freeze
|
8
|
-
|
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
|
9
11
|
|
10
12
|
def initialize(allowed_attributes, condition_hash)
|
11
13
|
@allowed_attributes = allowed_attributes
|
@@ -18,7 +20,11 @@ module Gitlab
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def param_content
|
21
|
-
|
23
|
+
if TIME_BASED_INTERVALS.include?(interval_type)
|
24
|
+
interval.public_send(interval_type).ago.to_datetime # rubocop:disable GitlabSecurity/PublicSend
|
25
|
+
else
|
26
|
+
interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
private
|
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
|
@@ -201,6 +201,7 @@ files:
|
|
201
201
|
- lib/gitlab/triage/filters/branch_date_filter.rb
|
202
202
|
- lib/gitlab/triage/filters/branch_protected_filter.rb
|
203
203
|
- lib/gitlab/triage/filters/discussions_conditions_filter.rb
|
204
|
+
- lib/gitlab/triage/filters/issue_date_conditions_filter.rb
|
204
205
|
- lib/gitlab/triage/filters/member_conditions_filter.rb
|
205
206
|
- lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb
|
206
207
|
- lib/gitlab/triage/filters/name_conditions_filter.rb
|