gitlab-triage 1.27.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/.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
|