gitlab-triage 1.23.1 → 1.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab/issue_templates/Default.md +2 -0
- data/.gitlab/merge_request_templates/Default.md +2 -0
- data/.gitlab-ci.yml +7 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +145 -0
- data/Gemfile +1 -1
- data/README.md +60 -5
- data/lib/gitlab/triage/action/issue.rb +64 -0
- data/lib/gitlab/triage/action/summarize.rb +1 -1
- data/lib/gitlab/triage/action.rb +3 -1
- data/lib/gitlab/triage/engine.rb +6 -1
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +11 -39
- data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +7 -7
- data/lib/gitlab/triage/network.rb +16 -92
- data/lib/gitlab/triage/policies/base_policy.rb +8 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +15 -3
- data/lib/gitlab/triage/policies/summary_policy.rb +5 -5
- data/lib/gitlab/triage/resource/context.rb +1 -0
- data/lib/gitlab/triage/resource/instance_version.rb +1 -1
- data/lib/gitlab/triage/resource/issue.rb +6 -0
- data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
- data/lib/gitlab/triage/rest_api_network.rb +111 -0
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d4dafafa454a68a9258f5753ef3df345e0a0f13c104158bc1243ecb561d408a
|
4
|
+
data.tar.gz: c0d12641e77a6865e0f5569d405e2548b03884e61280dc262a922ff38b786c84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
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
|
-
>
|
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
|
-
|
1231
|
-
-
|
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
|
data/lib/gitlab/triage/action.rb
CHANGED
@@ -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
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -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(
|
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:,
|
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
|
-
@
|
17
|
-
@
|
18
|
+
@description_template = action[:description]
|
19
|
+
@destination = action[:destination]
|
18
20
|
@redact_confidentials =
|
19
21
|
action[:redact_confidential_resources] != false
|
20
|
-
@
|
22
|
+
@resource = resource
|
21
23
|
@network = network
|
22
24
|
@separator = separator
|
23
25
|
end
|
24
26
|
|
25
27
|
def title
|
26
|
-
@title ||= build_text(
|
28
|
+
@title ||= build_text(@title_template)
|
27
29
|
end
|
28
30
|
|
29
31
|
def description
|
30
|
-
@description ||= build_text(
|
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+/
|
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
|
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
|
-
|
2
|
-
|
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
|
-
|
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
|
-
|
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
|
65
|
-
|
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
|
-
|
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
|
95
|
-
|
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
|
-
|
100
|
-
|
23
|
+
# FIXME: Remove the alias method
|
24
|
+
alias_method :options, :restapi_options
|
101
25
|
|
102
|
-
|
103
|
-
|
26
|
+
def graphql_options
|
27
|
+
graphql.options
|
104
28
|
end
|
105
29
|
|
106
|
-
def
|
107
|
-
|
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
|
11
|
-
def
|
11
|
+
# Build a summary from a single rule policy
|
12
|
+
def build_summary
|
12
13
|
action = actions.fetch(:summarize, {})
|
13
14
|
|
14
|
-
EntityBuilders::
|
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/
|
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
|
11
|
-
def
|
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
|
-
.
|
16
|
+
.build_summary
|
17
17
|
end
|
18
18
|
|
19
|
-
EntityBuilders::
|
19
|
+
EntityBuilders::SummaryBuilder.new(
|
20
20
|
type: type,
|
21
21
|
action: action,
|
22
22
|
resources: issues.select(&:any_resources?),
|
@@ -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,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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-triage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.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-
|
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
|