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 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