gitlab-triage 1.23.1 → 1.24.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: 34fcebd4ec7d33597bb45cf4a48a9708143972b9d5c836f374f2cc629dc392a8
4
- data.tar.gz: 48ed9e443369d98b3681d07bd83d81962a82fc5e5449c6c3198935f46a52e764
3
+ metadata.gz: 0d4dafafa454a68a9258f5753ef3df345e0a0f13c104158bc1243ecb561d408a
4
+ data.tar.gz: c0d12641e77a6865e0f5569d405e2548b03884e61280dc262a922ff38b786c84
5
5
  SHA512:
6
- metadata.gz: d32b0e581d1067771b25a1124d591e1e084c10e042a82d20e00ac4d00346e0f386c4c0962477db98d166dab2b0e893d82cbc171f7a5e81c398d2758c6839807d
7
- data.tar.gz: 94b2712733c583bab7bc2a4f0c7d92155f9b454a257cd8461cecb1290b4e3aef77d758fadbba1b8720f95585372d2b1c4366ed38481d013f8c19a7ae52d19f44
6
+ metadata.gz: ea2b0d33abf1eb19403519f264ab25d249a9992e97e08f830505f497a50cc91ae0f9210db270b87d45649ef41c1d1248fcf2ef13b6820991455cfe4d09e8247f
7
+ data.tar.gz: 063b0a7ec5503f55ee4971e84cc1ddeb81b1ce83997a41dbd48a9c730ada4b402693fc1e4f2be6a955cc9de57c283ed4ee1b37e059633bec355a95c2a55efe3b
@@ -9,3 +9,5 @@ If you are experiencing an issue when using GitLab.com, your first port of call
9
9
  If you feel that your issue can be categorized as a reproducible bug or a feature proposal, please use one of the issue templates provided and include as much information as possible.
10
10
 
11
11
  Thank you for helping to make GitLab a better product.
12
+
13
+ <!-- template sourced from https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/blob/master/.gitlab/issue_templates/Default.md -->
@@ -7,3 +7,5 @@ Please keep this description updated with any discussion that takes place so
7
7
  that reviewers can understand your intent. Keeping the description updated is
8
8
  especially important if they didn't participate in the discussion.
9
9
  -->
10
+
11
+ <!-- template sourced from https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/blob/master/.gitlab/merge_request_templates/Default.md -->
data/.gitlab-ci.yml CHANGED
@@ -106,6 +106,13 @@ specs:
106
106
  script:
107
107
  - bundle exec rake spec
108
108
 
109
+ specs ruby3.0:
110
+ image: ruby:3.0
111
+ needs: ["setup-test-env"]
112
+ stage: test
113
+ script:
114
+ - bundle exec rake spec
115
+
109
116
  ##################
110
117
  ## Triage stage ##
111
118
  ##################
data/.rubocop.yml CHANGED
@@ -1,3 +1,5 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  inherit_gem:
2
4
  gitlab-styles:
3
5
  - rubocop-default.yml
@@ -14,3 +16,6 @@ Rails/Output:
14
16
 
15
17
  Metrics/LineLength:
16
18
  Max: 152
19
+
20
+ Style/SingleArgumentDig:
21
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,145 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2022-05-26 15:15:19 UTC using RuboCop version 0.93.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 43
10
+ CodeReuse/ActiveRecord:
11
+ Exclude:
12
+ - 'lib/gitlab/triage/engine.rb'
13
+ - 'lib/gitlab/triage/graphql_network.rb'
14
+ - 'spec/gitlab/triage/engine_spec.rb'
15
+ - 'spec/gitlab/triage/graphql_network_spec.rb'
16
+ - 'spec/gitlab/triage/network_adapters/graphql_adapter_spec.rb'
17
+ - 'spec/gitlab/triage/rest_api_network_spec.rb'
18
+ - 'spec/gitlab/triage/policies/rule_policy_spec.rb'
19
+ - 'spec/gitlab/triage/resource/label_spec.rb'
20
+ - 'spec/support/expect_next_instance_of.rb'
21
+ - 'spec/support/shared_examples/issuable_shared_examples.rb'
22
+ - 'spec/support/shared_examples/label_command_shared_examples.rb'
23
+ - 'spec/support/stub_api.rb'
24
+
25
+ # Offense count: 1
26
+ # Cop supports --auto-correct.
27
+ Cop/LineBreakAroundConditionalBlock:
28
+ Exclude:
29
+ - 'lib/gitlab/triage/engine.rb'
30
+
31
+ # Offense count: 1
32
+ # Configuration parameters: Include.
33
+ # Include: **/*.gemspec
34
+ Gemspec/RequiredRubyVersion:
35
+ Exclude:
36
+ - 'gitlab-triage.gemspec'
37
+
38
+ # Offense count: 1
39
+ # Cop supports --auto-correct.
40
+ Lint/NonDeterministicRequireOrder:
41
+ Exclude:
42
+ - 'spec/spec_helper.rb'
43
+
44
+ # Offense count: 1
45
+ # Configuration parameters: IgnoredMethods.
46
+ Metrics/AbcSize:
47
+ Max: 57
48
+
49
+ # Offense count: 1
50
+ # Cop supports --auto-correct.
51
+ Performance/ConstantRegexp:
52
+ Exclude:
53
+ - 'lib/gitlab/triage/command_builders/text_content_builder.rb'
54
+
55
+ # Offense count: 2
56
+ # Cop supports --auto-correct.
57
+ # Configuration parameters: SafeMultiline.
58
+ Performance/DeleteSuffix:
59
+ Exclude:
60
+ - 'lib/gitlab/triage/api_query_builders/date_query_param_builder.rb'
61
+ - 'lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb'
62
+
63
+ # Offense count: 5
64
+ Performance/MethodObjectAsBlock:
65
+ Exclude:
66
+ - 'lib/gitlab/triage/engine.rb'
67
+ - 'lib/gitlab/triage/entity_builders/issue_builder.rb'
68
+ - 'lib/gitlab/triage/expand_condition.rb'
69
+ - 'lib/gitlab/triage/expand_condition/expansion.rb'
70
+ - 'lib/gitlab/triage/limiters/date_field_limiter.rb'
71
+
72
+ # Offense count: 31
73
+ # Cop supports --auto-correct.
74
+ RSpec/EmptyLineAfterLetBlock:
75
+ Enabled: false
76
+
77
+ # Offense count: 93
78
+ # Configuration parameters: AllowSubject.
79
+ RSpec/MultipleMemoizedHelpers:
80
+ Max: 12
81
+
82
+ # Offense count: 12
83
+ # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
84
+ RSpec/VerifiedDoubles:
85
+ Exclude:
86
+ - 'spec/gitlab/triage/graphql_network_spec.rb'
87
+ - 'spec/gitlab/triage/network_adapters/graphql_adapter_spec.rb'
88
+ - 'spec/gitlab/triage/rest_api_network_spec.rb'
89
+ - 'spec/support/shared_examples/label_command_shared_examples.rb'
90
+
91
+ # Offense count: 1
92
+ # Cop supports --auto-correct.
93
+ Rails/IndexBy:
94
+ Exclude:
95
+ - 'lib/gitlab/triage/engine.rb'
96
+
97
+ # Offense count: 1
98
+ # Cop supports --auto-correct.
99
+ Rails/NegateInclude:
100
+ Exclude:
101
+ - 'lib/gitlab/triage/filters/member_conditions_filter.rb'
102
+
103
+ # Offense count: 3
104
+ # Cop supports --auto-correct.
105
+ Rails/Pluck:
106
+ Exclude:
107
+ - 'lib/gitlab/triage/filters/base_conditions_filter.rb'
108
+ - 'lib/gitlab/triage/filters/member_conditions_filter.rb'
109
+ - 'lib/gitlab/triage/validators/limiter_validator.rb'
110
+
111
+ # Offense count: 2
112
+ # Cop supports --auto-correct.
113
+ Style/ArrayCoercion:
114
+ Exclude:
115
+ - 'lib/gitlab/triage/graphql_network.rb'
116
+
117
+ # Offense count: 1
118
+ # Cop supports --auto-correct.
119
+ Style/ExplicitBlockArgument:
120
+ Exclude:
121
+ - 'spec/support/expect_next_instance_of.rb'
122
+
123
+ # Offense count: 115
124
+ # Cop supports --auto-correct.
125
+ # Configuration parameters: EnforcedStyle.
126
+ # SupportedStyles: always, always_true, never
127
+ Style/FrozenStringLiteralComment:
128
+ Enabled: false
129
+
130
+ # Offense count: 5
131
+ Style/OpenStructUse:
132
+ Exclude:
133
+ - 'spec/gitlab/triage/network_adapters/httparty_adapter_spec.rb'
134
+
135
+ # Offense count: 1
136
+ # Cop supports --auto-correct.
137
+ Style/RedundantRegexpEscape:
138
+ Exclude:
139
+ - 'lib/gitlab/triage/command_builders/text_content_builder.rb'
140
+
141
+ # Offense count: 1
142
+ # Configuration parameters: AllowModifier.
143
+ Style/SoleNestedConditional:
144
+ Exclude:
145
+ - 'lib/gitlab/triage/validators/params_validator.rb'
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ gemspec
6
6
  gem "yard"
7
7
 
8
8
  group :test do
9
- gem 'gitlab-styles', '~> 3.3.0'
9
+ gem 'gitlab-styles', '~> 7.0'
10
10
  end
11
11
 
12
12
  group :development, :test, :danger do
data/README.md CHANGED
@@ -178,8 +178,9 @@ Accepts a hash of fields.
178
178
  | `condition` | string | `older_than`, `newer_than` | yes |
179
179
  | `interval_type` | string | `days`, `weeks`, `months`, `years` | yes |
180
180
  | `interval` | integer | integer | yes |
181
-
182
- > **Note:** `merged_at` only works on merge requests.
181
+ > **Note:**
182
+ > - `merged_at` only works on merge requests.
183
+ > - `closed_at` is not supported in the GitLab API, but can be used in a [`ruby` condition](#ruby-condition).
183
184
 
184
185
  Example:
185
186
 
@@ -612,6 +613,13 @@ conditions:
612
613
  This will make it only act on resources which have active milestones and
613
614
  there exists next milestone which has already started.
614
615
 
616
+ Since `closed_at` is not a queryable attribute in the GitLab API, we can use a Ruby expression to filter resources like:
617
+
618
+ ```yml
619
+ conditions:
620
+ ruby: resource[:closed_at] > 7.days.ago.strftime('%Y-%m-%dT00:00:00.000Z')
621
+ ```
622
+
615
623
  See [Ruby expression API](#ruby-expression-api) for the list of currently
616
624
  available API.
617
625
 
@@ -984,6 +992,46 @@ resource_rules:
984
992
  /label ~"needs attention"
985
993
  ```
986
994
 
995
+ ##### Create a new issue from each resource
996
+
997
+ Generates one issue for each resource, by default in the same project as the resource.
998
+
999
+ The use case for this is, for example, creating test issues in the same (or different)
1000
+ project for issues labeled "extended-testing"; or automatically splitting one issue with a
1001
+ certain label into multiple ones.
1002
+
1003
+ Accepts a hash of fields.
1004
+
1005
+ | Field | Type | Description | Required | Placeholders | Ruby expression | Default |
1006
+ | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
1007
+ | `title` | string | The title of the generated issue | yes | yes | yes | |
1008
+ | `destination` | integer or string | The project ID or path to create the generated issue in | no | no | no | source project |
1009
+ | `description` | string | The description of the generated issue | no | yes | yes | |
1010
+ | `redact_confidential_resources` | boolean | Whether redact fields for confidential resources | no | no | no | true |
1011
+
1012
+ The placeholders available in `title` and `destination` are the properties of the resource being used to generate the issue.
1013
+
1014
+ Example
1015
+
1016
+ ```yml
1017
+ resource_rules:
1018
+ issues:
1019
+ rules:
1020
+ - name: Issues requiring extra testing
1021
+ labels:
1022
+ - needs-testing
1023
+ actions:
1024
+ issue:
1025
+ title: |
1026
+ Testing: {{ title }}
1027
+ description: |
1028
+ The issue {{ full_reference }} needs testing.
1029
+
1030
+ Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
1031
+
1032
+ /label ~"needs attention"
1033
+ ```
1034
+
987
1035
  ### Summary policies
988
1036
 
989
1037
  Summary policies are special policies that join multiple rule policies together
@@ -1090,13 +1138,20 @@ Here's a list of currently available Ruby expression API:
1090
1138
  | project_path | String | The path with namespace to the issues or merge requests project |
1091
1139
  | full_resource_reference | String | A full reference including project path to the issue or merge request |
1092
1140
 
1093
- ##### Methods for `Issue` (issue context)
1141
+ ##### Methods for `Issue` and `LinkedIssue` (issue context)
1094
1142
 
1095
1143
  | Name | Return type | Description |
1096
1144
  | ---- | ---- | ---- |
1097
1145
  | merge_requests_count | Integer | The number of merge requests related to the issue |
1098
1146
  | related_merge_requests | [MergeRequest] | The list of merge requests related to the issue |
1099
1147
  | closed_by | [MergeRequest] | The list of merge requests that close the issue |
1148
+ | linked_issues | [LinkedIssue] | The list of issues that are linked to the issue |
1149
+
1150
+ ##### Methods for `LinkedIssue`
1151
+
1152
+ | Method | Return type | Description |
1153
+ | ---- | ---- | ---- |
1154
+ | link_type | String | The link type of the linked issue (`blocks`, `is_blocked_by`, or `relates_to`) |
1100
1155
 
1101
1156
  ##### Methods for `MergeRequest` (merge request context)
1102
1157
 
@@ -1227,8 +1282,8 @@ run:triage:triage:
1227
1282
  script:
1228
1283
  - gem install gitlab-triage
1229
1284
  - gitlab-triage --token $GITLAB_API_TOKEN --source-id $CI_PROJECT_PATH
1230
- only:
1231
- - schedules
1285
+ rules:
1286
+ - if: $CI_PIPELINE_SOURCE == "schedule"
1232
1287
  ```
1233
1288
 
1234
1289
  > **Note:** You can use the [`--init-ci`](#usage) option to add an example [`.gitlab-ci.yml` file](support/.gitlab-ci.example.yml) to your project
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module Action
8
+ class Issue < Base
9
+ class Dry < Issue
10
+ def act
11
+ puts "The following issues would be created for the rule **#{policy.name}**:\n\n"
12
+
13
+ super
14
+ end
15
+
16
+ private
17
+
18
+ def perform(resource, issue)
19
+ puts ">>>"
20
+ puts "* Project: #{issue.destination || resource[policy.source_id_sym]}"
21
+ puts "* Title: #{issue.title}"
22
+ puts "* Description: #{issue.description}"
23
+ puts ">>>"
24
+ end
25
+ end
26
+
27
+ def act
28
+ policy.resources.each do |resource|
29
+ issue = policy.build_issue(resource)
30
+
31
+ perform(resource, issue) if issue.valid?
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def perform(resource, issue)
38
+ network.post_api(build_post_url(resource, issue), post_issue_body(issue))
39
+ end
40
+
41
+ def build_post_url(resource, issue)
42
+ # POST /projects/:id/issues
43
+ # https://docs.gitlab.com/ee/api/issues.html#new-issue
44
+ post_url = UrlBuilders::UrlBuilder.new(
45
+ network_options: network.options,
46
+ source_id: issue.destination || resource[policy.source_id_sym],
47
+ resource_type: 'issues'
48
+ ).build
49
+
50
+ puts Gitlab::Triage::UI.debug "post_issue_url: #{post_url}" if network.options.debug
51
+
52
+ post_url
53
+ end
54
+
55
+ def post_issue_body(issue)
56
+ {
57
+ title: issue.title,
58
+ description: issue.description
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -45,7 +45,7 @@ module Gitlab
45
45
  end
46
46
 
47
47
  def issue
48
- @issue ||= policy.build_issue
48
+ @issue ||= policy.build_summary
49
49
  end
50
50
 
51
51
  def destination
@@ -1,6 +1,7 @@
1
1
  require_relative 'action/summarize'
2
2
  require_relative 'action/comment'
3
3
  require_relative 'action/comment_on_summary'
4
+ require_relative 'action/issue'
4
5
 
5
6
  module Gitlab
6
7
  module Triage
@@ -11,7 +12,8 @@ module Gitlab
11
12
  [
12
13
  [Summarize, policy.summarize?],
13
14
  [Comment, policy.comment?],
14
- [CommentOnSummary, policy.comment_on_summary?]
15
+ [CommentOnSummary, policy.comment_on_summary?],
16
+ [Issue, policy.issue?]
15
17
  ].each do |action, active|
16
18
  act(action: action, policy: policy, **args) if active
17
19
  end
@@ -21,6 +21,7 @@ require_relative 'api_query_builders/multi_query_param_builder'
21
21
  require_relative 'url_builders/url_builder'
22
22
  require_relative 'network'
23
23
  require_relative 'graphql_network'
24
+ require_relative 'rest_api_network'
24
25
  require_relative 'network_adapters/httparty_adapter'
25
26
  require_relative 'network_adapters/graphql_adapter'
26
27
  require_relative 'graphql_queries/query_builder'
@@ -78,7 +79,11 @@ module Gitlab
78
79
  end
79
80
 
80
81
  def network
81
- @network ||= Network.new(network_adapter)
82
+ @network ||= Network.new(restapi: restapi_network, graphql: graphql_network)
83
+ end
84
+
85
+ def restapi_network
86
+ @restapi_network ||= RestAPINetwork.new(network_adapter)
82
87
  end
83
88
 
84
89
  def graphql_network
@@ -6,72 +6,44 @@ module Gitlab
6
6
  module Triage
7
7
  module EntityBuilders
8
8
  class IssueBuilder
9
+ attr_reader :destination
10
+
9
11
  def initialize(
10
- type:, action:, resources:, network:,
12
+ type:, action:, resource:, network:,
11
13
  policy_spec: {}, separator: "\n")
12
14
  @type = type
13
15
  @policy_spec = policy_spec
14
16
  @item_template = action[:item]
15
17
  @title_template = action[:title]
16
- @summary_template = action[:summary]
17
- @summary_destination = action[:destination]
18
+ @description_template = action[:description]
19
+ @destination = action[:destination]
18
20
  @redact_confidentials =
19
21
  action[:redact_confidential_resources] != false
20
- @resources = resources
22
+ @resource = resource
21
23
  @network = network
22
24
  @separator = separator
23
25
  end
24
26
 
25
27
  def title
26
- @title ||= build_text(title_resource, @title_template)
28
+ @title ||= build_text(@title_template)
27
29
  end
28
30
 
29
31
  def description
30
- @description ||= build_text(description_resource, @summary_template)
31
- end
32
-
33
- def destination
34
- @summary_destination
32
+ @description ||= build_text(@description_template)
35
33
  end
36
34
 
37
35
  def valid?
38
- title =~ /\S+/ && any_resources?
39
- end
40
-
41
- def any_resources?
42
- @resources.any?
36
+ title =~ /\S+/
43
37
  end
44
38
 
45
39
  private
46
40
 
47
- def title_resource
48
- { type: @type }
49
- end
50
-
51
- def description_resource
52
- title_resource.merge(
53
- title: title, items: items, resources: @resources)
54
- end
55
-
56
- def items
57
- @items ||= @resources.map(&method(:build_item)).join(@separator)
58
- end
59
-
60
- def build_item(resource)
61
- case resource
62
- when IssueBuilder
63
- resource.description
64
- else
65
- build_text(resource, @item_template)
66
- end
67
- end
68
-
69
- def build_text(resource, template)
41
+ def build_text(template)
70
42
  return '' unless template
71
43
 
72
44
  CommandBuilders::TextContentBuilder.new(
73
45
  template,
74
- resource: resource,
46
+ resource: @resource,
75
47
  network: @network,
76
48
  redact_confidentials: @redact_confidentials)
77
49
  .build_command.chomp
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command_builders/text_content_builder'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module EntityBuilders
8
+ class SummaryBuilder
9
+ def initialize(
10
+ type:, action:, resources:, network:,
11
+ policy_spec: {}, separator: "\n")
12
+ @type = type
13
+ @policy_spec = policy_spec
14
+ @item_template = action[:item]
15
+ @title_template = action[:title]
16
+ @summary_template = action[:summary]
17
+ @summary_destination = action[:destination]
18
+ @redact_confidentials =
19
+ action[:redact_confidential_resources] != false
20
+ @resources = resources
21
+ @network = network
22
+ @separator = separator
23
+ end
24
+
25
+ def title
26
+ @title ||= build_text(title_resource, @title_template)
27
+ end
28
+
29
+ def description
30
+ @description ||= build_text(description_resource, @summary_template)
31
+ end
32
+
33
+ def destination
34
+ @summary_destination
35
+ end
36
+
37
+ def valid?
38
+ title =~ /\S+/ && any_resources?
39
+ end
40
+
41
+ def any_resources?
42
+ @resources.any?
43
+ end
44
+
45
+ private
46
+
47
+ def title_resource
48
+ { type: @type }
49
+ end
50
+
51
+ def description_resource
52
+ title_resource.merge(
53
+ title: title, items: items, resources: @resources)
54
+ end
55
+
56
+ def items
57
+ @items ||= @resources.map { |x| build_item(x) }.join(@separator)
58
+ end
59
+
60
+ def build_item(resource)
61
+ case resource
62
+ when SummaryBuilder
63
+ resource.description
64
+ else
65
+ build_text(resource, @item_template)
66
+ end
67
+ end
68
+
69
+ def build_text(resource, template)
70
+ return '' unless template
71
+
72
+ CommandBuilders::TextContentBuilder.new(
73
+ template,
74
+ resource: resource,
75
+ network: @network,
76
+ redact_confidentials: @redact_confidentials)
77
+ .build_command.chomp
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -103,28 +103,28 @@ module Gitlab
103
103
  when 'forbidden_labels'
104
104
  ['labelName', condition_params, { negated: true }]
105
105
  when 'labels'
106
- ['labelName', condition_params]
106
+ ['labelName', condition_params, {}]
107
107
  else
108
108
  return nil
109
109
  end
110
110
 
111
- QueryParamBuilders::LabelsParamBuilder.new(*args)
111
+ QueryParamBuilders::LabelsParamBuilder.new(*args[0...-1], **args.last)
112
112
  end
113
113
 
114
114
  def merge_requests_resource_query(condition, condition_params)
115
115
  args =
116
116
  case condition.to_s
117
117
  when 'source_branch'
118
- ['sourceBranches', condition_params]
118
+ ['sourceBranches', condition_params, {}]
119
119
  when 'target_branch'
120
- ['targetBranches', condition_params]
120
+ ['targetBranches', condition_params, {}]
121
121
  when 'draft'
122
122
  ['draft', condition_params, { with_quotes: false }]
123
123
  else
124
124
  return nil
125
125
  end
126
126
 
127
- QueryParamBuilders::BaseParamBuilder.new(*args)
127
+ QueryParamBuilders::BaseParamBuilder.new(*args[0...-1], **args.last)
128
128
  end
129
129
 
130
130
  def merge_requests_label_query(condition, condition_params)
@@ -133,12 +133,12 @@ module Gitlab
133
133
  when 'forbidden_labels'
134
134
  ['labels', condition_params, { negated: true }]
135
135
  when 'labels'
136
- ['labels', condition_params]
136
+ ['labels', condition_params, {}]
137
137
  else
138
138
  return nil
139
139
  end
140
140
 
141
- QueryParamBuilders::LabelsParamBuilder.new(*args)
141
+ QueryParamBuilders::LabelsParamBuilder.new(*args[0...-1], **args.last)
142
142
  end
143
143
  end
144
144
  end
@@ -1,110 +1,34 @@
1
- require 'active_support/all'
2
- require 'net/protocol'
3
-
4
- require_relative 'retryable'
5
- require_relative 'ui'
6
- require_relative 'errors'
1
+ require_relative 'graphql_network'
2
+ require_relative 'rest_api_network'
7
3
 
8
4
  module Gitlab
9
5
  module Triage
10
- class Network
11
- include Retryable
12
-
13
- TokenNotFound = Class.new(StandardError)
14
- MINIMUM_RATE_LIMIT = 25
15
-
16
- attr_reader :options, :adapter
17
-
18
- def initialize(adapter)
19
- @adapter = adapter
20
- @options = adapter.options
21
- @cache = {}
22
- end
23
-
24
- def query_api_cached(url)
25
- @cache[url] || @cache[url] = query_api(url)
26
- end
27
-
6
+ Network = Struct.new(:restapi, :graphql, keyword_init: true) do
28
7
  def query_api(url)
29
- response = {}
30
- resources = []
31
-
32
- begin
33
- print '.'
34
- url = response.fetch(:next_page_url) { url }
35
-
36
- response = execute_with_retry(
37
- exception_types: [Net::ReadTimeout, Errors::Network::InternalServerError],
38
- backoff_exceptions: Errors::Network::TooManyRequests) do
39
- puts Gitlab::Triage::UI.debug "query_api: #{url}" if options.debug
40
-
41
- @adapter.get(token, url)
42
- end
43
-
44
- results = response.delete(:results)
45
-
46
- case results
47
- when Array
48
- resources.concat(results)
49
- when Hash
50
- resources << results
51
- else
52
- raise_unexpected_response(results)
53
- end
54
-
55
- rate_limit_debug(response) if options.debug
56
- rate_limit_wait(response)
57
- end while response.delete(:more_pages)
58
-
59
- resources.map!(&:with_indifferent_access)
60
- rescue Net::ReadTimeout
61
- []
8
+ restapi.query_api(url)
62
9
  end
63
10
 
64
- def post_api(url, body)
65
- response = execute_with_retry(
66
- exception_types: Net::ReadTimeout,
67
- backoff_exceptions: Errors::Network::TooManyRequests) do
68
- puts Gitlab::Triage::UI.debug "post_api: #{url}" if options.debug
69
-
70
- @adapter.post(token, url, body)
71
- end
72
-
73
- rate_limit_debug(response) if options.debug
74
- rate_limit_wait(response)
75
-
76
- results = response.delete(:results)
77
-
78
- case results
79
- when Hash
80
- results.with_indifferent_access
81
- else
82
- raise_unexpected_response(results)
83
- end
84
- rescue Net::ReadTimeout
85
- {}
11
+ def query_graphql(...)
12
+ graphql.query(...)
86
13
  end
87
14
 
88
- private
89
-
90
- def token
91
- options.token || raise(TokenNotFound)
15
+ def query_api_cached(url)
16
+ restapi.query_api_cached(url)
92
17
  end
93
18
 
94
- def rate_limit_debug(response)
95
- rate_limit_infos = "Rate limit remaining: #{response[:ratelimit_remaining]} (reset at #{response[:ratelimit_reset_at]})"
96
- puts Gitlab::Triage::UI.debug "rate_limit_infos: #{rate_limit_infos}"
19
+ def restapi_options
20
+ restapi.options
97
21
  end
98
22
 
99
- def rate_limit_wait(response)
100
- return unless response.delete(:ratelimit_remaining) < MINIMUM_RATE_LIMIT
23
+ # FIXME: Remove the alias method
24
+ alias_method :options, :restapi_options
101
25
 
102
- puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{response[:ratelimit_reset_at] - Time.now} seconds" if options.debug
103
- sleep(1) until Time.now >= response[:ratelimit_reset_at]
26
+ def graphql_options
27
+ graphql.options
104
28
  end
105
29
 
106
- def raise_unexpected_response(results)
107
- raise Errors::Network::UnexpectedResponse, "Unexpected response: #{results.inspect}"
30
+ def post_api(...)
31
+ restapi.post_api(...)
108
32
  end
109
33
  end
110
34
  end
@@ -59,9 +59,17 @@ module Gitlab
59
59
  (actions.keys.map(&:to_sym) - [:summarize, :comment_on_summary]).any?
60
60
  end
61
61
 
62
+ def issue?
63
+ actions.key?(:issue)
64
+ end
65
+
62
66
  def build_issue
63
67
  raise NotImplementedError
64
68
  end
69
+
70
+ def build_summary
71
+ raise NotImplementedError
72
+ end
65
73
  end
66
74
  end
67
75
  end
@@ -2,22 +2,34 @@
2
2
 
3
3
  require_relative 'base_policy'
4
4
  require_relative '../entity_builders/issue_builder'
5
+ require_relative '../entity_builders/summary_builder'
5
6
 
6
7
  module Gitlab
7
8
  module Triage
8
9
  module Policies
9
10
  class RulePolicy < BasePolicy
10
- # Build an issue from a single rule policy
11
- def build_issue
11
+ # Build a summary from a single rule policy
12
+ def build_summary
12
13
  action = actions.fetch(:summarize, {})
13
14
 
14
- EntityBuilders::IssueBuilder.new(
15
+ EntityBuilders::SummaryBuilder.new(
15
16
  type: type,
16
17
  policy_spec: policy_spec,
17
18
  action: action,
18
19
  resources: resources,
19
20
  network: network)
20
21
  end
22
+
23
+ def build_issue(resource)
24
+ action = actions.fetch(:issue, {})
25
+
26
+ EntityBuilders::IssueBuilder.new(
27
+ type: type,
28
+ policy_spec: policy_spec,
29
+ action: action,
30
+ resource: resource,
31
+ network: network)
32
+ end
21
33
  end
22
34
  end
23
35
  end
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_policy'
4
- require_relative '../entity_builders/issue_builder'
4
+ require_relative '../entity_builders/summary_builder'
5
5
 
6
6
  module Gitlab
7
7
  module Triage
8
8
  module Policies
9
9
  class SummaryPolicy < BasePolicy
10
- # Build an issue from several rules policies
11
- def build_issue
10
+ # Build a summary from several rules policies
11
+ def build_summary
12
12
  action = actions[:summarize]
13
13
  issues = resources.map do |inner_policy_spec, inner_resources|
14
14
  Policies::RulePolicy.new(
15
15
  type, inner_policy_spec, inner_resources, network)
16
- .build_issue
16
+ .build_summary
17
17
  end
18
18
 
19
- EntityBuilders::IssueBuilder.new(
19
+ EntityBuilders::SummaryBuilder.new(
20
20
  type: type,
21
21
  action: action,
22
22
  resources: issues.select(&:any_resources?),
@@ -3,6 +3,7 @@
3
3
  require_relative 'base'
4
4
  require_relative 'epic'
5
5
  require_relative 'issue'
6
+ require_relative 'linked_issue'
6
7
  require_relative 'merge_request'
7
8
  require_relative 'instance_version'
8
9
 
@@ -5,7 +5,7 @@ module Gitlab
5
5
  module Resource
6
6
  class InstanceVersion < Base
7
7
  def initialize(**options)
8
- super({}, options)
8
+ super({}, **options)
9
9
  end
10
10
 
11
11
  def version
@@ -24,6 +24,12 @@ module Gitlab
24
24
  resource_url(sub_resource_type: 'closed_by'))
25
25
  .map { |merge_request| MergeRequest.new(merge_request, parent: self) }
26
26
  end
27
+
28
+ def linked_issues
29
+ @linked_issues ||= network.query_api_cached(
30
+ resource_url(sub_resource_type: 'links'))
31
+ .map { |issue| LinkedIssue.new(issue, parent: self) }
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'issue'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module Resource
8
+ class LinkedIssue < Issue
9
+ def link_type
10
+ @link_type ||= resource.dig(:link_type)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,111 @@
1
+ require 'active_support/all'
2
+ require 'net/protocol'
3
+
4
+ require_relative 'retryable'
5
+ require_relative 'ui'
6
+ require_relative 'errors'
7
+
8
+ module Gitlab
9
+ module Triage
10
+ class RestAPINetwork
11
+ include Retryable
12
+
13
+ TokenNotFound = Class.new(StandardError)
14
+ MINIMUM_RATE_LIMIT = 25
15
+
16
+ attr_reader :options, :adapter
17
+
18
+ def initialize(adapter)
19
+ @adapter = adapter
20
+ @options = adapter.options
21
+ @cache = {}
22
+ end
23
+
24
+ def query_api_cached(url)
25
+ @cache[url] || @cache[url] = query_api(url)
26
+ end
27
+
28
+ def query_api(url)
29
+ response = {}
30
+ resources = []
31
+
32
+ begin
33
+ print '.'
34
+ url = response.fetch(:next_page_url) { url }
35
+
36
+ response = execute_with_retry(
37
+ exception_types: [Net::ReadTimeout, Errors::Network::InternalServerError],
38
+ backoff_exceptions: Errors::Network::TooManyRequests) do
39
+ puts Gitlab::Triage::UI.debug "query_api: #{url}" if options.debug
40
+
41
+ @adapter.get(token, url)
42
+ end
43
+
44
+ results = response.delete(:results)
45
+
46
+ case results
47
+ when Array
48
+ resources.concat(results)
49
+ when Hash
50
+ resources << results
51
+ else
52
+ raise_unexpected_response(results)
53
+ end
54
+
55
+ rate_limit_debug(response) if options.debug
56
+ rate_limit_wait(response)
57
+ end while response.delete(:more_pages)
58
+
59
+ resources.map!(&:with_indifferent_access)
60
+ rescue Net::ReadTimeout
61
+ []
62
+ end
63
+
64
+ def post_api(url, body)
65
+ response = execute_with_retry(
66
+ exception_types: Net::ReadTimeout,
67
+ backoff_exceptions: Errors::Network::TooManyRequests) do
68
+ puts Gitlab::Triage::UI.debug "post_api: #{url}" if options.debug
69
+
70
+ @adapter.post(token, url, body)
71
+ end
72
+
73
+ rate_limit_debug(response) if options.debug
74
+ rate_limit_wait(response)
75
+
76
+ results = response.delete(:results)
77
+
78
+ case results
79
+ when Hash
80
+ results.with_indifferent_access
81
+ else
82
+ raise_unexpected_response(results)
83
+ end
84
+ rescue Net::ReadTimeout
85
+ {}
86
+ end
87
+
88
+ private
89
+
90
+ def token
91
+ options.token || raise(TokenNotFound)
92
+ end
93
+
94
+ def rate_limit_debug(response)
95
+ rate_limit_infos = "Rate limit remaining: #{response[:ratelimit_remaining]} (reset at #{response[:ratelimit_reset_at]})"
96
+ puts Gitlab::Triage::UI.debug "rate_limit_infos: #{rate_limit_infos}"
97
+ end
98
+
99
+ def rate_limit_wait(response)
100
+ return unless response.delete(:ratelimit_remaining) < MINIMUM_RATE_LIMIT
101
+
102
+ puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{response[:ratelimit_reset_at] - Time.now} seconds" if options.debug
103
+ sleep(1) until Time.now >= response[:ratelimit_reset_at]
104
+ end
105
+
106
+ def raise_unexpected_response(results)
107
+ raise Errors::Network::UnexpectedResponse, "Unexpected response: #{results.inspect}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Triage
5
- VERSION = '1.23.1'
5
+ VERSION = '1.24.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.23.1
4
+ version: 1.24.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-04-27 00:00:00.000000000 Z
11
+ date: 2022-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -153,6 +153,7 @@ files:
153
153
  - ".gitlab/merge_request_templates/Default.md"
154
154
  - ".gitlab/merge_request_templates/Release.md"
155
155
  - ".rubocop.yml"
156
+ - ".rubocop_todo.yml"
156
157
  - ".ruby-version"
157
158
  - ".tool-versions"
158
159
  - ".yardopts"
@@ -170,6 +171,7 @@ files:
170
171
  - lib/gitlab/triage/action/base.rb
171
172
  - lib/gitlab/triage/action/comment.rb
172
173
  - lib/gitlab/triage/action/comment_on_summary.rb
174
+ - lib/gitlab/triage/action/issue.rb
173
175
  - lib/gitlab/triage/action/summarize.rb
174
176
  - lib/gitlab/triage/api_query_builders/base_query_param_builder.rb
175
177
  - lib/gitlab/triage/api_query_builders/date_query_param_builder.rb
@@ -185,6 +187,7 @@ files:
185
187
  - lib/gitlab/triage/command_builders/text_content_builder.rb
186
188
  - lib/gitlab/triage/engine.rb
187
189
  - lib/gitlab/triage/entity_builders/issue_builder.rb
190
+ - lib/gitlab/triage/entity_builders/summary_builder.rb
188
191
  - lib/gitlab/triage/errors.rb
189
192
  - lib/gitlab/triage/errors/network.rb
190
193
  - lib/gitlab/triage/expand_condition.rb
@@ -228,9 +231,11 @@ files:
228
231
  - lib/gitlab/triage/resource/issue.rb
229
232
  - lib/gitlab/triage/resource/label.rb
230
233
  - lib/gitlab/triage/resource/label_event.rb
234
+ - lib/gitlab/triage/resource/linked_issue.rb
231
235
  - lib/gitlab/triage/resource/merge_request.rb
232
236
  - lib/gitlab/triage/resource/milestone.rb
233
237
  - lib/gitlab/triage/resource/shared/issuable.rb
238
+ - lib/gitlab/triage/rest_api_network.rb
234
239
  - lib/gitlab/triage/retryable.rb
235
240
  - lib/gitlab/triage/ui.rb
236
241
  - lib/gitlab/triage/url_builders/url_builder.rb