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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6668ca8a79611f5e39d317807cb604d7fb32320120283c9996a9784515523bf5
4
- data.tar.gz: d6211b78036d039e89a75da4f93bbfa07c44b73ffd16f331ae1bfb5cc901cc9d
3
+ metadata.gz: 6f594160679b71928b75f5bec0131c846d79b10cdc0cebb33388d1bc907b9769
4
+ data.tar.gz: 04a02e449720f03ab2820c3be0693c17486e366a7bcfe87a1c1f6e02ee6a02da
5
5
  SHA512:
6
- metadata.gz: bf0a6406e2a2b6f6cc6b7436831b3b95be04bbf1174c70d3fc5d4a0e24d4e8ccddcd1837864bc8e336fb1c5a0dc947c8a37716d74b31f35e025c4a21bcdb2fda
7
- data.tar.gz: 0aa64137299f30fe4f83db4b50475c2b9a15e9730eebbcf5b98d5ce17150a70379f5ae629a87b5d399ce4c74a1712063e222c224ef2e6da29d3016ca48faa35f
6
+ metadata.gz: 44cc6360a513c7ad126ee8dbc4e5151fafbb8b4994e5e0ea84a2fdb5a32ec997271bd66ee3b065cbbbfb3e92548a38d599c025a6f32f378339522f836001cadf
7
+ data.tar.gz: 1177f4e8471c3fbe6606e5e0eeaee384335b327774a94574e9942d1928b7dec03c50b8c90df082091b7873f6f871c70a8b162732fa3740a222c861c1751f3f7a
data/.gitlab/CODEOWNERS CHANGED
@@ -1,2 +1,2 @@
1
1
  # The official maintainers
2
- * @rymai @godfat-gitlab @markglenfletcher
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 | Required |
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` | yes |
195
- | `interval_type` | string | `days`, `weeks`, `months`, `years` | yes |
196
- | `interval` | integer | integer | yes |
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
- --all-projects Process all projects visible to `--token`
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(policy.actions[:comment], resource: resource, network: network).build_command,
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)
@@ -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
- assert_all!
59
- assert_project_id!
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.to_s.singularize}", char: '=')
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
- if resource_type == 'epics' && options.source != :groups
71
- raise(EpicsTriagingForProjectImpossibleError, "Epics can only be triaged at the group level. Please set the `--source groups` option.")
72
- end
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 assert_project_id!
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 project_id is needed (pass it with the `--source-id` option)!'
136
+ raise ArgumentError, 'A project or group ID is needed (pass it with the `--source-id` option)!'
101
137
  end
102
138
 
103
- def assert_all!
104
- raise ArgumentError, '--all-projects option cannot be used in conjunction with --source and --source-id option!' if
105
- options.all && (options.source || options.source_id)
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
- results << Filters::MergeRequestDateConditionsFilter.new(resource, conditions[:date]).calculate
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
- UrlBuilders::UrlBuilder.new(
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
- ).build
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
- INTERVAL_TYPES = %w[days weeks months years].freeze
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
- @interval.public_send(@interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
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 < BaseConditionsFilter
7
- ATTRIBUTES = %w[merged_at].freeze
8
- CONDITIONS = %w[older_than newer_than].freeze
9
- INTERVAL_TYPES = %w[days weeks months years].freeze
10
-
11
- def self.allowed_attributes
12
- self::ATTRIBUTES
13
- end
14
-
15
- def self.filter_parameters
16
- [
17
- {
18
- name: :attribute,
19
- type: String,
20
- values: allowed_attributes
21
- },
22
- {
23
- name: :condition,
24
- type: String,
25
- values: CONDITIONS
26
- },
27
- {
28
- name: :interval_type,
29
- type: String,
30
- values: INTERVAL_TYPES
31
- },
32
- {
33
- name: :interval,
34
- type: Numeric
35
- }
36
- ]
37
- end
38
-
39
- def initialize_variables(condition)
40
- @attribute = condition[:attribute].to_sym
41
- @condition = condition[:condition].to_sym
42
- @interval_type = condition[:interval_type].to_sym
43
- @interval = condition[:interval]
44
- end
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
- iids_declaration: graphql_only ? nil : ', $iids: [String!]',
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($source: ID!, $after: String%{iids_declaration}) {
44
+ query(%{resource_declarations}) {
42
45
  %{source_type}(fullPath: $source) {
43
46
  id
44
- %{resource_type}(after: $after%{iids_query}%{resource_query}) {
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
@@ -3,9 +3,11 @@ module Gitlab
3
3
  Options = Struct.new(
4
4
  :dry_run,
5
5
  :policies_files,
6
+ :resources,
6
7
  :all,
7
8
  :source,
8
9
  :source_id,
10
+ :resource_reference,
9
11
  :token,
10
12
  :debug,
11
13
  :host_url,
@@ -5,7 +5,9 @@ module Gitlab
5
5
  module ParamBuilders
6
6
  class DateParamBuilder
7
7
  CONDITIONS = %w[older_than newer_than].freeze
8
- INTERVAL_TYPES = %w[days weeks months years].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
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
- interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
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
@@ -14,6 +14,10 @@ module Gitlab
14
14
  def self.warn(text)
15
15
  "[WARNING] #{text}"
16
16
  end
17
+
18
+ def self.error(text)
19
+ "[ERROR] #{text}"
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Triage
5
- VERSION = '1.27.0'
5
+ VERSION = '1.29.0'
6
6
  end
7
7
  end
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.27.0
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: 2022-11-28 00:00:00.000000000 Z
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