gitlab-triage 1.56.1 → 1.57.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: 9420b956227ae89432c05b78279b5c8267bf98e721c4feb2cde5fee21a7d611a
4
- data.tar.gz: ffde105e64820dca1e873e4c368bbe65d4039080aba864e114e67e7c6d3efaa3
3
+ metadata.gz: 624c4d1977031933b3f310f7ae5c82778ac62bcda0f86a0c2c4a375504b6e715
4
+ data.tar.gz: 9545979a57eea0ff733995ac553271ad310c2c2710ac4fe78645daff962449b5
5
5
  SHA512:
6
- metadata.gz: 4475319216eee8897599eef75260c58e3d677043d3a8b86cbe1ef44b6146faafb967786d3004731879b8a9c4996a21620a50956196f978aa94c704f9e2c2ef96
7
- data.tar.gz: 42b71bf2233e9daeeec8834b2bb3d651f620f758f83cb3e4e17701208aa40fbf55473532d5eff574dde327afc4c3d97029c370ee5c34198d22bf784a51af932d
6
+ metadata.gz: 1721670e0285a0d0fffd8cb076becb239e859267d1da55f4819526f39128dec62e884251f6ae28e9d1550eeca2eb29f8c5b08c1c8d6e16b480f22254bf20016c
7
+ data.tar.gz: 7b233404c63d41de48dfadf6e7fdacadb6412c47fe00562d417626534ef21b5dd8c4c14340b1d46f27bad1c5c2e58378d55e80b6aac5c8e0f25f12d2075c3515
data/.gitlab/CODEOWNERS CHANGED
@@ -1,2 +1 @@
1
- # The official maintainers
2
- * @gl-quality/eng-prod
1
+ * @gl-dx
data/.gitlab-ci.yml CHANGED
@@ -107,7 +107,6 @@ dry-run:custom:
107
107
  allow_failure: true
108
108
 
109
109
  include:
110
- - component: gitlab.com/components/code-quality/code-quality@~latest
111
110
  - component: gitlab.com/components/sast/sast@~latest
112
111
  - component: gitlab.com/components/secret-detection/secret-detection@~latest
113
112
  - component: gitlab.com/gitlab-org/components/danger-review/danger-review@~latest
@@ -119,18 +118,6 @@ include:
119
118
  - if: '$CI_MERGE_REQUEST_IID'
120
119
  - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
121
120
 
122
- code_quality:
123
- # We explicitly set dependencies here as the component sets them to []
124
- # This allows us to get the cache from the setup job that includes the Gemfile.lock
125
- dependencies: ["setup-test-env"]
126
- tags:
127
- - gitlab-org-docker
128
- # We explicitly override allow_failure here as the component sets it to true
129
- # Can be moved to a component input after: https://gitlab.com/components/code-quality/-/merge_requests/8
130
- allow_failure: false
131
- <<: *ruby-cache
132
- <<: *component-general-rules
133
-
134
121
  semgrep-sast:
135
122
  <<: *component-general-rules
136
123
 
data/.rubocop.yml CHANGED
@@ -51,3 +51,8 @@ RSpec/IndexedLet:
51
51
  # rationale: Maybe we should? For now, let's keep it disabled and revisit at a later point.
52
52
  Rails/Output:
53
53
  Enabled: false
54
+
55
+ # rationale: gitlab-triage has no database / ActiveRecord models
56
+ # context: https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/work_items/399
57
+ CodeReuse/ActiveRecord:
58
+ Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -6,27 +6,6 @@
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 81
10
- CodeReuse/ActiveRecord:
11
- Exclude:
12
- - 'lib/gitlab/triage/action/work_item.rb'
13
- - 'lib/gitlab/triage/engine.rb'
14
- - 'lib/gitlab/triage/filters/base_conditions_filter.rb'
15
- - 'lib/gitlab/triage/filters/member_conditions_filter.rb'
16
- - 'lib/gitlab/triage/graphql_network.rb'
17
- - 'lib/gitlab/triage/normalizers/work_item_normalizer.rb'
18
- - 'lib/gitlab/triage/validators/limiter_validator.rb'
19
- - 'spec/gitlab/triage/action_spec.rb'
20
- - 'spec/gitlab/triage/engine_spec.rb'
21
- - 'spec/gitlab/triage/graphql_network_spec.rb'
22
- - 'spec/gitlab/triage/network_adapters/graphql_adapter_spec.rb'
23
- - 'spec/gitlab/triage/policies/rule_policy_spec.rb'
24
- - 'spec/gitlab/triage/resource/label_spec.rb'
25
- - 'spec/gitlab/triage/rest_api_network_spec.rb'
26
- - 'spec/support/shared_examples/issuable_shared_examples.rb'
27
- - 'spec/support/shared_examples/label_command_shared_examples.rb'
28
- - 'spec/support/stub_api.rb'
29
-
30
9
  # Offense count: 1
31
10
  Lint/ToEnumArguments:
32
11
  Exclude:
data/Gemfile.lock CHANGED
@@ -1,31 +1,35 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-triage (1.56.1)
4
+ gitlab-triage (1.57.0)
5
5
  activesupport (>= 5.1)
6
6
  globalid (~> 1.0, >= 1.0.1)
7
- graphql (< 2.1.0)
8
- graphql-client (~> 0.16)
7
+ graphql (>= 2.4.13, < 2.7.0)
8
+ graphql-client (~> 0.23)
9
9
  httparty (>= 0.21.0, < 1.0)
10
10
 
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- activesupport (7.1.3.4)
14
+ activesupport (7.1.6)
15
15
  base64
16
+ benchmark (>= 0.3)
16
17
  bigdecimal
17
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
19
  connection_pool (>= 2.2.5)
19
20
  drb
20
21
  i18n (>= 1.6, < 2)
22
+ logger (>= 1.4.2)
21
23
  minitest (>= 5.1)
22
24
  mutex_m
25
+ securerandom (>= 0.3)
23
26
  tzinfo (~> 2.0)
24
- addressable (2.8.6)
25
- public_suffix (>= 2.0.2, < 6.0)
27
+ addressable (2.9.0)
28
+ public_suffix (>= 2.0.2, < 8.0)
26
29
  ast (2.4.2)
27
- base64 (0.2.0)
28
- bigdecimal (3.1.8)
30
+ base64 (0.3.0)
31
+ benchmark (0.5.0)
32
+ bigdecimal (4.1.3)
29
33
  claide (1.1.0)
30
34
  claide-plugins (0.9.2)
31
35
  cork
@@ -33,8 +37,8 @@ GEM
33
37
  open4 (~> 1.3)
34
38
  coderay (1.1.3)
35
39
  colored2 (3.1.2)
36
- concurrent-ruby (1.3.3)
37
- connection_pool (2.4.1)
40
+ concurrent-ruby (1.3.8)
41
+ connection_pool (2.5.5)
38
42
  cork (0.3.0)
39
43
  colored2 (~> 3.1)
40
44
  crack (1.0.0)
@@ -59,13 +63,16 @@ GEM
59
63
  gitlab (~> 4.2, >= 4.2.0)
60
64
  diff-lcs (1.5.1)
61
65
  drb (2.2.3)
62
- faraday (2.9.1)
63
- faraday-net_http (>= 2.0, < 3.2)
66
+ faraday (2.14.4)
67
+ faraday-net_http (>= 2.0, < 3.5)
68
+ json
69
+ logger
64
70
  faraday-http-cache (2.5.1)
65
71
  faraday (>= 0.8)
66
- faraday-net_http (3.1.0)
67
- net-http
72
+ faraday-net_http (3.4.4)
73
+ net-http (~> 0.5)
68
74
  ffi (1.17.0)
75
+ fiber-storage (1.0.0)
69
76
  formatador (1.1.0)
70
77
  git (1.19.1)
71
78
  addressable (~> 2.8)
@@ -85,9 +92,11 @@ GEM
85
92
  rubocop-rspec (~> 2.27.1)
86
93
  globalid (1.2.1)
87
94
  activesupport (>= 6.1)
88
- graphql (2.0.31)
95
+ graphql (2.6.11)
89
96
  base64
90
- graphql-client (0.22.0)
97
+ fiber-storage
98
+ logger
99
+ graphql-client (0.26.0)
91
100
  activesupport (>= 3.0)
92
101
  graphql (>= 1.13.0)
93
102
  guard (2.18.1)
@@ -109,7 +118,7 @@ GEM
109
118
  csv
110
119
  mini_mime (>= 1.0.0)
111
120
  multi_xml (>= 0.5.2)
112
- i18n (1.14.5)
121
+ i18n (1.14.8)
113
122
  concurrent-ruby (~> 1.0)
114
123
  json (2.7.2)
115
124
  kramdown (2.4.0)
@@ -121,16 +130,20 @@ GEM
121
130
  listen (3.9.0)
122
131
  rb-fsevent (~> 0.10, >= 0.10.3)
123
132
  rb-inotify (~> 0.9, >= 0.9.10)
133
+ logger (1.7.0)
124
134
  lumberjack (1.2.10)
125
135
  method_source (1.1.0)
126
136
  mini_mime (1.1.5)
127
137
  minitest (5.26.1)
128
138
  multi_xml (0.6.0)
129
- mutex_m (0.2.0)
139
+ mutex_m (0.3.0)
130
140
  nap (1.1.0)
131
141
  nenv (0.3.0)
132
- net-http (0.4.1)
133
- uri
142
+ net-http (0.10.0)
143
+ net-protocol (>= 0.3.0)
144
+ uri (>= 0.11.1)
145
+ net-protocol (0.4.0)
146
+ timeout
134
147
  no_proxy_fix (0.1.2)
135
148
  notiffany (0.1.3)
136
149
  nenv (~> 0.1)
@@ -146,9 +159,9 @@ GEM
146
159
  pry (0.14.2)
147
160
  coderay (~> 1.1)
148
161
  method_source (~> 1.0)
149
- public_suffix (5.0.5)
162
+ public_suffix (6.0.2)
150
163
  racc (1.8.0)
151
- rack (3.1.3)
164
+ rack (3.2.7)
152
165
  rainbow (3.1.1)
153
166
  rake (13.2.1)
154
167
  rb-fsevent (0.11.2)
@@ -156,8 +169,7 @@ GEM
156
169
  ffi (~> 1.0)
157
170
  rchardet (1.8.0)
158
171
  regexp_parser (2.9.2)
159
- rexml (3.3.0)
160
- strscan
172
+ rexml (3.4.4)
161
173
  rspec (3.13.0)
162
174
  rspec-core (~> 3.13.0)
163
175
  rspec-expectations (~> 3.13.0)
@@ -206,15 +218,16 @@ GEM
206
218
  sawyer (0.9.2)
207
219
  addressable (>= 2.3.5)
208
220
  faraday (>= 0.17.3, < 3)
221
+ securerandom (0.3.2)
209
222
  shellany (0.0.1)
210
- strscan (3.1.0)
211
223
  terminal-table (3.0.2)
212
224
  unicode-display_width (>= 1.1.1, < 3)
213
225
  thor (1.3.1)
226
+ timeout (0.6.1)
214
227
  tzinfo (2.0.6)
215
228
  concurrent-ruby (~> 1.0)
216
229
  unicode-display_width (2.5.0)
217
- uri (0.13.0)
230
+ uri (1.1.2)
218
231
  webmock (3.23.1)
219
232
  addressable (>= 2.8.0)
220
233
  crack (>= 0.3.2)
data/README.md CHANGED
@@ -203,6 +203,8 @@ resource_rules:
203
203
  - `labels` - add labels by name. If any requested label does not exist, the
204
204
  automation stops immediately (matching the `labels` action on the `issues`
205
205
  type) rather than applying nothing.
206
+ - `remove_labels` - remove labels by name. Like `labels`, a requested label that
207
+ does not exist stops the automation rather than applying a partial change.
206
208
  - `assignees` - set assignees by username. If any requested username does not
207
209
  exist, the automation stops immediately rather than sending an empty
208
210
  assignee set (which would unassign everyone).
@@ -215,7 +217,7 @@ resource_rules:
215
217
  to comment.
216
218
 
217
219
  Summaries are also supported (`summarize` and `comment_on_summary`). Any other
218
- action key (for example `remove_labels`, `mention`, `move`, or `status`) is
220
+ action key (for example `mention`, `move`, or `status`) is
219
221
  rejected at load time with an error listing the supported actions. On the
220
222
  older policy types these keys are applied through comment quick actions, which
221
223
  work items do not support yet - rejecting them up front beats silently doing
@@ -345,7 +347,7 @@ Accepts the name of a milestone to filter upon. Also accepts the following timeb
345
347
  - `upcoming`
346
348
  - `started`
347
349
 
348
- See the [`milestone_id` API field documentation](https://docs.gitlab.com/ee/api/issues.html) for their meaning.
350
+ See the [`milestone_id` API field documentation](https://docs.gitlab.com/api/issues/) for their meaning.
349
351
 
350
352
  Example:
351
353
 
@@ -362,7 +364,7 @@ timebox values:
362
364
  - `none`
363
365
  - `any`
364
366
 
365
- See the [`iteration_id` API field documentation](https://docs.gitlab.com/ee/api/issues.html) for their meaning.
367
+ See the [`iteration_id` API field documentation](https://docs.gitlab.com/api/issues/) for their meaning.
366
368
 
367
369
  Example:
368
370
 
@@ -427,7 +429,7 @@ conditions:
427
429
 
428
430
  ###### Predefined special label names
429
431
 
430
- Basing on the [issues API](https://docs.gitlab.com/ee/api/issues.html), there
432
+ Basing on the [issues API](https://docs.gitlab.com/api/issues/), there
431
433
  are two special predefined label names we can use here:
432
434
 
433
435
  * `None`: This indicates that no labels were present
@@ -694,7 +696,7 @@ conditions:
694
696
 
695
697
  **This condition is only applicable for issues.**
696
698
 
697
- Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues).
699
+ Accepts a string per the [API documentation](https://docs.gitlab.com/api/issues/#list-all-issues).
698
700
 
699
701
  | State | Type | Value |
700
702
  | --------- | ---- | ------ |
@@ -715,7 +717,7 @@ conditions:
715
717
 
716
718
  **This condition is only applicable for issues.**
717
719
 
718
- Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues).
720
+ Accepts a string per the [API documentation](https://docs.gitlab.com/api/issues/#list-all-issues).
719
721
 
720
722
  | State | Type | Value |
721
723
  | --------- | ---- | ------ |
@@ -734,7 +736,7 @@ conditions:
734
736
 
735
737
  **This condition is only applicable for issues.**
736
738
 
737
- Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues). This condition can only filter by one issue type.
739
+ Accepts a string per the [API documentation](https://docs.gitlab.com/api/issues/#list-all-issues). This condition can only filter by one issue type.
738
740
 
739
741
  | Issue type | Type | Value |
740
742
  | --------- | ---- | ------ |
@@ -1260,7 +1262,7 @@ The following comment types are supported:
1260
1262
  - `comment` (default): creates a regular comment on the resource
1261
1263
  - `thread`: starts a resolvable thread (discussion) on the resource
1262
1264
 
1263
- For merge requests, if `comment_type` is set to `thread`, we can also configure that [all threads should be resolved before merging](https://docs.gitlab.com/ee/user/discussions/#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved), therefore this comment can prevent it from merging.
1265
+ For merge requests, if `comment_type` is set to `thread`, we can also configure that [all threads should be resolved before merging](https://docs.gitlab.com/user/project/merge_requests/#prevent-merge-unless-all-threads-are-resolved), therefore this comment can prevent it from merging.
1264
1266
 
1265
1267
  Example:
1266
1268
 
@@ -1289,14 +1291,14 @@ actions:
1289
1291
 
1290
1292
  ###### Harnessing Quick Actions
1291
1293
 
1292
- [GitLab's quick actions feature](https://docs.gitlab.com/ce/user/project/quick_actions.html) is available in Core.
1294
+ [GitLab's quick actions feature](https://docs.gitlab.com/user/project/quick_actions/) is available in Core.
1293
1295
  All of the operations supported by executing a quick action can be carried out via the comment action.
1294
1296
 
1295
1297
  If GitLab triage does not support an operation natively, it may be possible via a quick action in a comment.
1296
1298
 
1297
1299
  For example:
1298
- - Flagging an issue as [confidential](https://docs.gitlab.com/ce/user/project/issues/confidential_issues.html)
1299
- - [Locking issue discussion](https://docs.gitlab.com/ce/user/discussions/#lock-discussions)
1300
+ - Flagging an issue as [confidential](https://docs.gitlab.com/user/project/issues/confidential_issues/)
1301
+ - [Locking issue discussion](https://docs.gitlab.com/user/discussions/#prevent-comments-by-locking-the-discussion)
1300
1302
 
1301
1303
  ```yml
1302
1304
  resource_rules:
@@ -1801,6 +1803,8 @@ Usage: gitlab-triage [options]
1801
1803
  -H, --host-url [string] A valid host url
1802
1804
  -r, --require [string] Require a file before performing
1803
1805
  -d, --debug Print debug information
1806
+ --summary Print API call summary at end of execution
1807
+ --quiet Suppress pagination dots and section headers
1804
1808
  --cache-dir [string] Directory containing pre-fetched API responses.
1805
1809
  Files must follow the naming convention: <source>_<id>_<resource>.json
1806
1810
  (e.g. groups_9970_members.json).
@@ -33,13 +33,10 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  spec.add_dependency 'activesupport', '>= 5.1'
35
35
  spec.add_dependency 'globalid', '~> 1.0', '>= 1.0.1'
36
- spec.add_dependency 'graphql-client', '~> 0.16'
37
- # Workaround - pin graphql version
38
- # see: https://github.com/github/graphql-client/issues/310
39
- # see: https://github.com/rmosolgo/graphql-ruby/pull/4577
40
- # see: https://github.com/github/graphql-client/pull/314
41
- # We can remove this check once PR 314 is merged and released
42
- spec.add_dependency 'graphql', '< 2.1.0'
36
+ spec.add_dependency 'graphql-client', '~> 0.23'
37
+ # graphql >= 2.3.5 needs graphql-client >= 0.23 (possible_types keys changed).
38
+ # 2.4.13 is the patched version on the highest affected minor line.
39
+ spec.add_dependency 'graphql', '>= 2.4.13', '< 2.7.0'
43
40
  spec.add_dependency 'httparty', '>= 0.21.0', '< 1.0'
44
41
 
45
42
  spec.add_development_dependency 'gitlab-dangerfiles', '~> 2.11.0'
@@ -63,7 +63,7 @@ module Gitlab
63
63
 
64
64
  def build_post_url(resource, issue)
65
65
  # POST /projects/:id/issues
66
- # https://docs.gitlab.com/ee/api/issues.html#new-issue
66
+ # https://docs.gitlab.com/api/issues/#create-an-issue
67
67
  post_url = UrlBuilders::UrlBuilder.new(
68
68
  network_options: network.options,
69
69
  source_id: issue.destination || resource[policy.source_id_sym],
@@ -79,7 +79,7 @@ module Gitlab
79
79
 
80
80
  def post_issue_url
81
81
  # POST /projects/:id/issues
82
- # https://docs.gitlab.com/ee/api/issues.html#new-issue
82
+ # https://docs.gitlab.com/api/issues/#create-an-issue
83
83
  post_url = UrlBuilders::UrlBuilder.new(
84
84
  network_options: network.options,
85
85
  source_id: destination,
@@ -11,8 +11,9 @@ module Gitlab
11
11
  # Applies work_items actions through the Work Items REST API update
12
12
  # endpoint (PATCH) rather than the issues note/quick-action path.
13
13
  #
14
- # - labels -> features.labels.add_label_ids (resolved from names)
15
- # - assignees -> features.assignees.assignee_ids (resolved from usernames)
14
+ # - labels -> features.labels.add_label_ids (resolved from names)
15
+ # - remove_labels -> features.labels.remove_label_ids (resolved from names)
16
+ # - assignees -> features.assignees.assignee_ids (resolved from usernames)
16
17
  #
17
18
  # Missing labels or usernames stop the automation immediately (matching
18
19
  # the labels action behaviour on the issues type) rather than silently
@@ -36,13 +37,14 @@ module Gitlab
36
37
  def perform(resource)
37
38
  puts "# #{resource[:web_url]}"
38
39
  puts "Would add labels: #{requested_labels.join(', ')}" if requested_labels.any?
40
+ puts "Would remove labels: #{requested_removed_labels.join(', ')}" if requested_removed_labels.any?
39
41
  puts "Would assign: #{requested_assignees.join(', ')}" if requested_assignees.any?
40
42
  puts "\n"
41
43
  end
42
44
  end
43
45
 
44
46
  def act
45
- return unless requested_labels.any? || requested_assignees.any?
47
+ return unless requested_labels.any? || requested_removed_labels.any? || requested_assignees.any?
46
48
 
47
49
  policy.resources.each do |resource|
48
50
  perform(resource)
@@ -63,7 +65,11 @@ module Gitlab
63
65
  def build_features(resource)
64
66
  features = {}
65
67
 
66
- features[:labels] = { add_label_ids: label_ids_to_add(resource) } if requested_labels.any?
68
+ label_changes = {}
69
+ label_changes[:add_label_ids] = label_ids_to_add(resource) if requested_labels.any?
70
+ label_changes[:remove_label_ids] = label_ids_to_remove(resource) if requested_removed_labels.any?
71
+ features[:labels] = label_changes if label_changes.any?
72
+
67
73
  features[:assignees] = { assignee_ids: assignee_ids } if requested_assignees.any?
68
74
 
69
75
  features
@@ -73,6 +79,10 @@ module Gitlab
73
79
  @requested_labels ||= Array(policy.actions[:labels])
74
80
  end
75
81
 
82
+ def requested_removed_labels
83
+ @requested_removed_labels ||= Array(policy.actions[:remove_labels])
84
+ end
85
+
76
86
  def requested_assignees
77
87
  @requested_assignees ||= Array(policy.actions[:assignees])
78
88
  end
@@ -82,14 +92,16 @@ module Gitlab
82
92
  network: network, options: network.options)
83
93
  end
84
94
 
85
- # Label ids are namespace-specific, so resolve them against the
86
- # resource's own namespace and cache per scope - a group-spanning run
87
- # then makes one label lookup per distinct project/group.
95
+ # Label ids are namespace-specific, so resolve against the resource's
96
+ # own namespace. The fetched label list is cached per scope, so a rule
97
+ # that both adds and removes labels - or a group-spanning run - makes
98
+ # one lookup per distinct project/group.
88
99
  def label_ids_to_add(resource)
89
- scope = source.scope_for(resource)
100
+ resolve_label_ids(requested_labels, source.scope_for(resource))
101
+ end
90
102
 
91
- @label_ids_by_scope ||= {}
92
- @label_ids_by_scope[scope] ||= resolve_label_ids(requested_labels, scope)
103
+ def label_ids_to_remove(resource)
104
+ resolve_label_ids(requested_removed_labels, source.scope_for(resource))
93
105
  end
94
106
 
95
107
  def assignee_ids
@@ -102,7 +114,7 @@ module Gitlab
102
114
  def resolve_label_ids(names, scope)
103
115
  return [] if names.empty?
104
116
 
105
- by_name = network.query_api(labels_url(scope)).index_by { |label| label[:name] }
117
+ by_name = labels_by_name(scope)
106
118
 
107
119
  names.map do |name|
108
120
  label = by_name[name]
@@ -112,24 +124,41 @@ module Gitlab
112
124
  end
113
125
  end
114
126
 
127
+ def labels_by_name(scope)
128
+ @labels_by_name_by_scope ||= {}
129
+ @labels_by_name_by_scope[scope] ||= network.query_api(labels_url(scope)).index_by { |label| label[:name] }
130
+ end
131
+
115
132
  # The Work Items REST update endpoint can return success without
116
133
  # persisting a change when the token lacks permission. The response
117
134
  # echoes the resulting widgets, so confirm the requested ids landed and
118
135
  # raise otherwise rather than reporting a silent no-op as success.
119
136
  def verify_applied!(resource, response)
120
137
  verify_labels_applied!(resource, response) if requested_labels.any?
138
+ verify_labels_removed!(resource, response) if requested_removed_labels.any?
121
139
  verify_assignees_applied!(resource, response) if requested_assignees.any?
122
140
  end
123
141
 
124
142
  def verify_labels_applied!(resource, response)
125
- applied_ids = [*response.dig(:features, :labels, :labels)].pluck(:id)
126
- missing = label_ids_to_add(resource) - applied_ids
143
+ missing = label_ids_to_add(resource) - applied_label_ids(response)
127
144
  return if missing.empty?
128
145
 
129
146
  raise UpdateNotAppliedError,
130
147
  "Labels were not applied to #{resource[:web_url]}; the token may lack permission."
131
148
  end
132
149
 
150
+ def verify_labels_removed!(resource, response)
151
+ still_present = label_ids_to_remove(resource) & applied_label_ids(response)
152
+ return if still_present.empty?
153
+
154
+ raise UpdateNotAppliedError,
155
+ "Labels were not removed from #{resource[:web_url]}; the token may lack permission."
156
+ end
157
+
158
+ def applied_label_ids(response)
159
+ [*response.dig(:features, :labels, :labels)].pluck(:id)
160
+ end
161
+
133
162
  def verify_assignees_applied!(resource, response)
134
163
  applied_ids = [*response.dig(:features, :assignees)].pluck(:id)
135
164
  missing = assignee_ids - applied_ids
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'active_support/all'
4
4
  require 'active_support/inflector'
5
+ require 'globalid'
5
6
 
6
7
  require_relative 'expand_condition'
7
8
  require_relative 'filters/issue_date_conditions_filter'
@@ -409,14 +410,14 @@ module Gitlab
409
410
  end
410
411
 
411
412
  ExpandCondition.perform(rule_conditions(rule_definition)) do |expanded_conditions|
412
- status_pre_filtered = pre_filter_by_work_item_status!(resource_type, expanded_conditions)
413
+ status_by_id = pre_filter_by_work_item_status!(resource_type, expanded_conditions)
413
414
 
414
415
  # retrieving the resources for every rule is inefficient
415
416
  # however, previous rules may affect those upcoming
416
417
  resources = options.resources ||
417
418
  fetch_resources(resource_type, expanded_conditions, rule_definition)
418
419
 
419
- apply_work_item_status!(resource_type, expanded_conditions, rule_definition, resources, status_pre_filtered)
420
+ apply_work_item_status!(resource_type, expanded_conditions, rule_definition, resources, status_by_id)
420
421
 
421
422
  # In some filters/actions we want to know which resource type it is
422
423
  attach_resource_type(resources, resource_type)
@@ -452,6 +453,7 @@ module Gitlab
452
453
 
453
454
  if rule_definition[:api] == 'graphql'
454
455
  graphql_query_options = { source: source_full_path }
456
+ pre_filter_status_ids = expanded_conditions.delete(:work_item_status_ids)
455
457
 
456
458
  if options.resource_reference
457
459
  expanded_conditions[:iids] = options.resource_reference[1..]
@@ -463,25 +465,16 @@ module Gitlab
463
465
  graphql_query = build_graphql_query(resource_type, expanded_conditions, rule_definition, true)
464
466
 
465
467
  resources = graphql_network.query(graphql_query, **graphql_query_options)
468
+ resources = narrow_to_pre_filtered(resources, nil, pre_filter_status_ids)
466
469
  else
467
470
  # FIXME: Epics listing endpoint doesn't support filtering by `iids`, so instead we
468
471
  # get a single epic when `--resource-reference` is given for epics.
469
472
  # Because of that, the query could return a single epic, so we make sure we get an array.
470
473
  pre_filter_iids = expanded_conditions.delete(:iids)
474
+ pre_filter_status_ids = expanded_conditions.delete(:work_item_status_ids)
471
475
 
472
476
  resources = Array(network.query_api(build_get_url(resource_type, expanded_conditions)))
473
-
474
- # Narrow the REST results to the pre-filtered IID set, when a pre-filter
475
- # ran. The contract is carried by the value itself: `nil` means no
476
- # pre-filter ran (keep all resources), while any array - including `[]` -
477
- # means "narrow to exactly these IIDs". An empty array therefore yields
478
- # zero resources, which is correct when a pre-filter (e.g. work_item_status)
479
- # ran and matched nothing. `present?` cannot express this because it is
480
- # false for `[]`.
481
- unless pre_filter_iids.nil?
482
- filtered_iids = pre_filter_iids.map(&:to_s)
483
- resources = resources.select { |r| filtered_iids.include?(r['iid'].to_s) }
484
- end
477
+ resources = narrow_to_pre_filtered(resources, pre_filter_iids, pre_filter_status_ids)
485
478
 
486
479
  iids = resources.pluck('iid').map(&:to_s)
487
480
  expanded_conditions[:iids] = iids
@@ -495,6 +488,29 @@ module Gitlab
495
488
  resources
496
489
  end
497
490
 
491
+ # Narrow the fetched results to a pre-filtered set, when a pre-filter ran.
492
+ # The contract is carried by each value: `nil` means that pre-filter did not
493
+ # run (keep all resources), while any array - including `[]` - means "narrow
494
+ # to exactly these". An empty array therefore yields zero resources, which is
495
+ # correct when a pre-filter (e.g. work_item_status) ran and matched nothing;
496
+ # `present?` cannot express this because it is false for `[]`.
497
+ #
498
+ # The work_item_status pre-filter narrows by the globally-unique id, so it
499
+ # cannot conflate same-iid issues from different projects of a group.
500
+ def narrow_to_pre_filtered(resources, pre_filter_iids, pre_filter_status_ids)
501
+ unless pre_filter_status_ids.nil?
502
+ filtered_ids = pre_filter_status_ids.map(&:to_s)
503
+ resources = resources.select { |r| filtered_ids.include?(r[:id].to_s) }
504
+ end
505
+
506
+ unless pre_filter_iids.nil?
507
+ filtered_iids = pre_filter_iids.map(&:to_s)
508
+ resources = resources.select { |r| filtered_iids.include?(r[:iid].to_s) }
509
+ end
510
+
511
+ resources
512
+ end
513
+
498
514
  def fetch_work_items(expanded_conditions)
499
515
  conditions = expanded_conditions.dup
500
516
  conditions[:iids] = options.resource_reference[1..] if options.resource_reference
@@ -714,7 +730,7 @@ module Gitlab
714
730
  end
715
731
 
716
732
  def draft_condition_builder(draft_condittion)
717
- # Issues API only accepts 'yes' and 'no' as strings: https://docs.gitlab.com/ee/api/merge_requests.html
733
+ # Issues API only accepts 'yes' and 'no' as strings: https://docs.gitlab.com/api/merge_requests/
718
734
  wip =
719
735
  case draft_condittion
720
736
  when true
@@ -729,32 +745,33 @@ module Gitlab
729
745
  end
730
746
 
731
747
  # When filtering by work_item_status, query workItems with server-side
732
- # status filtering first to narrow the IID set before the main fetch.
748
+ # status filtering first to narrow the resource set before the main fetch.
733
749
  # This avoids fetching all resources just to discard most of them.
750
+ #
751
+ # We narrow by the globally-unique work item id, not the iid: a group
752
+ # source flattens work items from every descendant project into one list,
753
+ # and iids are unique only within a project, so two projects can share an
754
+ # iid. The id side-steps that collision.
734
755
  def pre_filter_by_work_item_status!(resource_type, expanded_conditions)
735
- return false unless resource_type == 'issues' && expanded_conditions.key?(:work_item_status)
736
-
737
- pre_filter_iids = fetch_work_item_iids_by_status(expanded_conditions[:work_item_status])
756
+ return unless resource_type == 'issues' && expanded_conditions.key?(:work_item_status)
738
757
 
739
- expanded_conditions[:iids] =
740
- if expanded_conditions[:iids].present?
741
- Array(expanded_conditions[:iids]).map(&:to_s) & pre_filter_iids
742
- else
743
- pre_filter_iids
744
- end
758
+ status_by_id = fetch_work_item_status_by_id(expanded_conditions[:work_item_status])
759
+ expanded_conditions[:work_item_status_ids] = status_by_id.keys
745
760
 
746
- true
761
+ status_by_id
747
762
  end
748
763
 
749
- def apply_work_item_status!(resource_type, expanded_conditions, rule_definition, resources, status_pre_filtered)
764
+ # The pre-filter (pre_filter_by_work_item_status!) always runs before this
765
+ # for an issues rule that filters by work_item_status, and returns a map of
766
+ # id to matched status name. We stamp each resource from that map instead of
767
+ # a second, iid-keyed status query: the pre-filter already knows the answer,
768
+ # and keying by the globally-unique id avoids the group iid collision that a
769
+ # flattened `iids:` query would reintroduce.
770
+ def apply_work_item_status!(resource_type, expanded_conditions, rule_definition, resources, status_by_id)
750
771
  return unless resource_type == 'issues' && needs_work_item_status?(expanded_conditions, rule_definition)
751
772
 
752
- status_values = Array(expanded_conditions[:work_item_status])
753
-
754
- if status_pre_filtered && status_values.size == 1
755
- resources.each { |r| r[:work_item_status] = status_values.first }
756
- else
757
- decorate_resources_with_work_item_status(resources)
773
+ resources.each do |resource|
774
+ resource[:work_item_status] = status_by_id[resource[:id].to_s]
758
775
  end
759
776
  end
760
777
 
@@ -762,24 +779,40 @@ module Gitlab
762
779
  conditions.key?(:work_item_status)
763
780
  end
764
781
 
782
+ # A group's `workItems` connection returns only the group's own work items
783
+ # (epics), whose IIDs do not match the project-scoped issue IIDs the issues
784
+ # fetch returns. `includeDescendants: true` pulls in the descendant project
785
+ # issues so the status IIDs line up. The argument does not exist on
786
+ # `project.workItems`, so it is only emitted for a group source.
787
+ def work_item_descendants_argument
788
+ options.source == 'groups' ? 'includeDescendants: true, ' : ''
789
+ end
790
+
765
791
  # Pre-filter: query workItems with server-side status filtering to get only
766
- # the IIDs that match, avoiding fetching the full resource set when most
767
- # items won't pass the status filter.
768
- def fetch_work_item_iids_by_status(status_condition)
792
+ # the items that match, avoiding fetching the full resource set when most
793
+ # items won't pass the status filter. Because it queries one status at a
794
+ # time, it also learns which status each id matched, and returns that as a
795
+ # map of globally-unique id (from the work item GID) to status name. The id
796
+ # keys stay correct across a group's descendant projects where iids can
797
+ # collide, and the map lets the caller stamp statuses without a second
798
+ # iid-keyed query. When several statuses match the same id, the last one
799
+ # queried wins; a work item has a single status, so this does not happen in
800
+ # practice.
801
+ def fetch_work_item_status_by_id(status_condition)
769
802
  status_names = Array(status_condition)
770
- all_iids = []
803
+ status_by_id = {}
771
804
 
772
805
  status_names.each do |status_name|
773
806
  query = <<~GRAPHQL
774
807
  query($source: ID!, $after: String, $statusName: String!) {
775
808
  #{options.source.singularize}(fullPath: $source) {
776
- workItems(status: { name: $statusName }, after: $after, first: 100) {
809
+ workItems(status: { name: $statusName }, #{work_item_descendants_argument}after: $after, first: 100) {
777
810
  pageInfo {
778
811
  hasNextPage
779
812
  endCursor
780
813
  }
781
814
  nodes {
782
- iid
815
+ id
783
816
  }
784
817
  }
785
818
  }
@@ -797,8 +830,9 @@ module Gitlab
797
830
 
798
831
  work_items = Array.wrap(response[:results])
799
832
  work_items.each do |wi|
800
- iid = wi.is_a?(Hash) ? (wi['iid'] || wi[:iid]) : wi.try(:iid)
801
- all_iids << iid.to_s if iid
833
+ gid = wi.is_a?(Hash) ? (wi['id'] || wi[:id]) : wi.try(:id)
834
+ id = numeric_id_from_gid(gid)
835
+ status_by_id[id.to_s] = status_name if id
802
836
  end
803
837
 
804
838
  break unless response[:more_pages]
@@ -807,80 +841,16 @@ module Gitlab
807
841
  end
808
842
  end
809
843
 
810
- all_iids.uniq
811
- end
812
-
813
- # Status is a work item capability not available on the IssueType GraphQL type
814
- # or the REST API. We fetch it via a separate workItems query and merge it back
815
- # by IID, similar to how decorate_resources_with_graphql_data works for other fields.
816
- # We use widgets (not features) for backward compatibility with GitLab versions
817
- # before the features field was introduced in 18.9.
818
- def decorate_resources_with_work_item_status(resources)
819
- return if resources.empty?
820
-
821
- iids = resources.filter_map { |r| (r['iid'] || r[:iid])&.to_s }
822
- return if iids.empty?
823
-
824
- status_by_iid = fetch_work_item_statuses_by_iid(iids)
825
-
826
- resources.each do |resource|
827
- iid = (resource['iid'] || resource[:iid]).to_s
828
- resource[:work_item_status] = status_by_iid[iid]
829
- end
844
+ status_by_id
830
845
  end
831
846
 
832
- # The workItems connection caps `first` at 100, so we request the status
833
- # for the given IIDs in batches and paginate within each batch.
834
- def fetch_work_item_statuses_by_iid(iids)
835
- query = <<~GRAPHQL
836
- query($source: ID!, $iids: [String!], $after: String) {
837
- #{options.source.singularize}(fullPath: $source) {
838
- workItems(iids: $iids, after: $after, first: 100) {
839
- pageInfo {
840
- hasNextPage
841
- endCursor
842
- }
843
- nodes {
844
- iid
845
- widgets(onlyTypes: [STATUS]) {
846
- ... on WorkItemWidgetStatus {
847
- status {
848
- id
849
- name
850
- }
851
- }
852
- }
853
- }
854
- }
855
- }
856
- }
857
- GRAPHQL
858
-
859
- status_by_iid = {}
860
-
861
- iids.each_slice(100) do |iids_batch|
862
- after_cursor = nil
863
-
864
- loop do
865
- response = graphql_network.adapter.query_raw(
866
- query,
867
- resource_path: [options.source.singularize, 'workItems'],
868
- variables: { source: source_full_path, iids: iids_batch, after: after_cursor }
869
- )
870
-
871
- Array.wrap(response[:results]).each do |wi|
872
- wi = wi.deep_transform_keys(&:underscore).with_indifferent_access
873
- status_widget = Array.wrap(wi[:widgets]).find { |w| w[:status].present? }
874
- status_by_iid[wi[:iid].to_s] = status_widget.dig(:status, :name) if status_widget
875
- end
876
-
877
- break unless response[:more_pages]
878
-
879
- after_cursor = response[:end_cursor]
880
- end
881
- end
847
+ # A work item GID (`gid://gitlab/WorkItem/1234`) carries the same model id
848
+ # the REST issues endpoint returns as `id`. Parsed with GlobalID, matching
849
+ # GraphqlNetwork#extract_id_from_global_id.
850
+ def numeric_id_from_gid(gid)
851
+ return if gid.blank?
882
852
 
883
- status_by_iid
853
+ GlobalID.parse(gid).model_id
884
854
  end
885
855
 
886
856
  def build_graphql_query(resource_type, conditions, rule_definition, graphql_only = false)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/object/blank'
4
+
3
5
  require_relative '../version'
4
6
 
5
7
  module Gitlab
@@ -14,6 +16,21 @@ module Gitlab
14
16
  @options = options
15
17
  @tracker = tracker
16
18
  end
19
+
20
+ private
21
+
22
+ # When the rate-limit headers are absent (e.g. a server that doesn't send
23
+ # them), treat the limit as not-a-concern rather than coercing a missing
24
+ # header to 0/1970, which would otherwise trip rate_limit_wait.
25
+ def rate_limit_remaining(headers)
26
+ value = headers['ratelimit-remaining']
27
+ value.present? ? value.to_i : Float::INFINITY
28
+ end
29
+
30
+ def rate_limit_reset_at(headers)
31
+ value = headers['ratelimit-reset']
32
+ Time.at(value.to_i) if value.present?
33
+ end
17
34
  end
18
35
  end
19
36
  end
@@ -58,14 +58,9 @@ module Gitlab
58
58
 
59
59
  raise_on_error!(response)
60
60
 
61
- parsed_response = response.data
62
61
  headers = response.extensions.fetch('headers', {})
63
62
 
64
- {
65
- ratelimit_remaining: headers['ratelimit-remaining'].to_i,
66
- ratelimit_reset_at: Time.at(headers['ratelimit-reset'].to_i),
67
- results: parsed_response.to_h
68
- }
63
+ build_graphql_response(response.data.to_h, headers)
69
64
  end
70
65
 
71
66
  delegate :parse, to: :client
@@ -96,19 +91,6 @@ module Gitlab
96
91
  end
97
92
  end
98
93
 
99
- # When the rate-limit headers are absent (e.g. a server that doesn't send
100
- # them), treat the limit as not-a-concern rather than coercing a missing
101
- # header to 0/1970, which would otherwise trip rate_limit_wait.
102
- def rate_limit_remaining(headers)
103
- value = headers['ratelimit-remaining']
104
- value.present? ? value.to_i : Float::INFINITY
105
- end
106
-
107
- def rate_limit_reset_at(headers)
108
- value = headers['ratelimit-reset']
109
- Time.at(value.to_i) if value.present?
110
- end
111
-
112
94
  # Converts a parsed GraphQL::Client response node into plain Ruby so the
113
95
  # shared builder only ever deals with Hash/Array, never client objects.
114
96
  def plain_node(parsed_response)
@@ -28,8 +28,8 @@ module Gitlab
28
28
  more_pages: (response.headers["x-next-page"].to_s != ""),
29
29
  next_page_url: next_page_url(url, response),
30
30
  results: response.parsed_response,
31
- ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
32
- ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
31
+ ratelimit_remaining: rate_limit_remaining(response.headers),
32
+ ratelimit_reset_at: rate_limit_reset_at(response.headers)
33
33
  }
34
34
 
35
35
  resource_count = result[:results].is_a?(Array) ? result[:results].size : 0
@@ -57,8 +57,8 @@ module Gitlab
57
57
 
58
58
  {
59
59
  results: response.parsed_response,
60
- ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
61
- ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
60
+ ratelimit_remaining: rate_limit_remaining(response.headers),
61
+ ratelimit_reset_at: rate_limit_reset_at(response.headers)
62
62
  }
63
63
  end
64
64
 
@@ -81,8 +81,8 @@ module Gitlab
81
81
 
82
82
  {
83
83
  results: response.parsed_response,
84
- ratelimit_remaining: response.headers['ratelimit-remaining'].to_i,
85
- ratelimit_reset_at: Time.at(response.headers['ratelimit-reset'].to_i)
84
+ ratelimit_remaining: rate_limit_remaining(response.headers),
85
+ ratelimit_reset_at: rate_limit_reset_at(response.headers)
86
86
  }
87
87
  end
88
88
 
@@ -105,8 +105,8 @@ module Gitlab
105
105
 
106
106
  {
107
107
  results: response.parsed_response,
108
- ratelimit_remaining: response.headers['ratelimit-remaining'].to_i,
109
- ratelimit_reset_at: Time.at(response.headers['ratelimit-reset'].to_i)
108
+ ratelimit_remaining: rate_limit_remaining(response.headers),
109
+ ratelimit_reset_at: rate_limit_reset_at(response.headers)
110
110
  }
111
111
  end
112
112
 
@@ -127,8 +127,8 @@ module Gitlab
127
127
 
128
128
  {
129
129
  results: response.parsed_response,
130
- ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
131
- ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
130
+ ratelimit_remaining: rate_limit_remaining(response.headers),
131
+ ratelimit_reset_at: rate_limit_reset_at(response.headers)
132
132
  }
133
133
  end
134
134
 
@@ -74,10 +74,10 @@ module Gitlab
74
74
  end
75
75
 
76
76
  opts.on('--cache-dir [string]', String,
77
- 'Directory containing pre-fetched API responses. ' \
78
- 'Files must follow the naming convention: <source>_<id>_<resource>.json ' \
79
- '(e.g. groups_9970_members.json). ' \
80
- 'Can also be set via the GITLAB_TRIAGE_CACHE_DIR environment variable.') do |value|
77
+ 'Directory containing pre-fetched API responses.',
78
+ 'Files must follow the naming convention: <source>_<id>_<resource>.json',
79
+ '(e.g. groups_9970_members.json).',
80
+ 'Can also be set via the GITLAB_TRIAGE_CACHE_DIR environment variable.') do |value|
81
81
  options.cache_dir = value
82
82
  end
83
83
 
@@ -80,7 +80,7 @@ module Gitlab
80
80
  end
81
81
 
82
82
  def work_item?
83
- (actions.keys.map(&:to_sym) & %i[labels assignees]).any?
83
+ (actions.keys.map(&:to_sym) & %i[labels remove_labels assignees]).any?
84
84
  end
85
85
 
86
86
  def build_issue
@@ -210,9 +210,12 @@ module Gitlab
210
210
  def rate_limit_wait(response)
211
211
  return unless response.delete(:ratelimit_remaining) < MINIMUM_RATE_LIMIT
212
212
 
213
- puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{response[:ratelimit_reset_at] - Time.now} seconds" if options.debug
213
+ reset_at = response[:ratelimit_reset_at]
214
+ return unless reset_at
215
+
216
+ puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{reset_at - Time.now} seconds" if options.debug
214
217
  @adapter.tracker&.record_rate_limit_wait
215
- sleep(1) until Time.now >= response[:ratelimit_reset_at]
218
+ sleep(1) until Time.now >= reset_at
216
219
  end
217
220
 
218
221
  def raise_unexpected_response(results)
@@ -10,6 +10,7 @@ module Gitlab
10
10
  # where nobody sees it).
11
11
  class PolicyValidator
12
12
  UnsupportedActionError = Class.new(StandardError)
13
+ ConflictingActionError = Class.new(StandardError)
13
14
 
14
15
  COMMENT_NOT_SUPPORTED_MESSAGE =
15
16
  'Commenting on work_items is not yet supported by the Work Items REST API ' \
@@ -18,7 +19,7 @@ module Gitlab
18
19
 
19
20
  # Action keys Action.actions_for dispatches for work_items policies.
20
21
  # Anything outside this list would silently do nothing, so reject it up front.
21
- SUPPORTED_WORK_ITEM_ACTIONS = %i[labels assignees summarize comment_on_summary].freeze
22
+ SUPPORTED_WORK_ITEM_ACTIONS = %i[labels remove_labels assignees summarize comment_on_summary].freeze
22
23
 
23
24
  def initialize(resource_rules)
24
25
  @resource_rules = resource_rules || {}
@@ -38,6 +39,16 @@ module Gitlab
38
39
  action_sets_from(policy_definition).each do |actions|
39
40
  raise UnsupportedActionError, COMMENT_NOT_SUPPORTED_MESSAGE if actions.key?(:comment)
40
41
 
42
+ # GitLab applies remove_label_ids after add_label_ids, so a label in
43
+ # both lists is always removed. Catch the typo here rather than let the
44
+ # run fail later with a misleading "token may lack permission" error.
45
+ conflicting = Array(actions[:labels]).map(&:to_s) & Array(actions[:remove_labels]).map(&:to_s)
46
+ unless conflicting.none?
47
+ raise ConflictingActionError,
48
+ "Label(s) in both labels and remove_labels for a work_items rule: #{conflicting.join(', ')}. " \
49
+ "A label cannot be added and removed in the same rule."
50
+ end
51
+
41
52
  unsupported = actions.keys.map(&:to_sym) - SUPPORTED_WORK_ITEM_ACTIONS
42
53
  next if unsupported.none?
43
54
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Triage
5
- VERSION = '1.56.1'
5
+ VERSION = '1.57.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.56.1
4
+ version: 1.57.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-02 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -50,28 +50,34 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.16'
53
+ version: '0.23'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.16'
60
+ version: '0.23'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: graphql
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.4.13
65
68
  - - "<"
66
69
  - !ruby/object:Gem::Version
67
- version: 2.1.0
70
+ version: 2.7.0
68
71
  type: :runtime
69
72
  prerelease: false
70
73
  version_requirements: !ruby/object:Gem::Requirement
71
74
  requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.4.13
72
78
  - - "<"
73
79
  - !ruby/object:Gem::Version
74
- version: 2.1.0
80
+ version: 2.7.0
75
81
  - !ruby/object:Gem::Dependency
76
82
  name: httparty
77
83
  requirement: !ruby/object:Gem::Requirement
@@ -212,7 +218,6 @@ executables:
212
218
  extensions: []
213
219
  extra_rdoc_files: []
214
220
  files:
215
- - ".codeclimate.yml"
216
221
  - ".git-blame-ignore-revs"
217
222
  - ".gitignore"
218
223
  - ".gitlab-ci.yml"
data/.codeclimate.yml DELETED
@@ -1,19 +0,0 @@
1
- ---
2
- engines:
3
- bundler-audit:
4
- enabled: true
5
- duplication:
6
- enabled: true
7
- config:
8
- languages:
9
- - ruby
10
- rubocop-gitlab:
11
- enabled: true
12
- ratings:
13
- paths:
14
- - Gemfile.lock
15
- - "**.rb"
16
- exclude_paths:
17
- - spec/
18
- - vendor/
19
- - tmp/