gitlab-triage 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +57 -26
- data/gitlab-triage.gemspec +1 -1
- data/lib/gitlab/triage/action.rb +28 -0
- data/lib/gitlab/triage/action/base.rb +17 -0
- data/lib/gitlab/triage/action/comment.rb +74 -0
- data/lib/gitlab/triage/command_builders/comment_body_builder.rb +30 -16
- data/lib/gitlab/triage/engine.rb +19 -53
- data/lib/gitlab/triage/limiters/date_field_limiter.rb +3 -3
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 565a738248a7ded7917c1dd9f43962bc564b86a391083c93b91a8e1dc70b40f3
|
4
|
+
data.tar.gz: 2faa52c47be6f7823b9a70efa108bdea330b42a60defaa72ceba563cf7820a76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a248b17fdaf0faf77e7462e2d69d29efdee698680398643bf575961860a06f996f2114856cc05b146570eac93872786db654c7496389f734084de89bbe129142
|
7
|
+
data.tar.gz: 16bcfc3e4ed9e2db7d52358d4630e94cc5bf4158b37a63000e19ce75a5ad9f5b0cb013ec4e8f43f52782e33632476eea25798fd7a7a86a9c13ab65f397a841b6
|
data/README.md
CHANGED
@@ -54,7 +54,7 @@ resource_rules:
|
|
54
54
|
mention:
|
55
55
|
- markglenfletcher
|
56
56
|
comment: |
|
57
|
-
{{author}} This issue is unlabelled after 5 days. It needs attention.
|
57
|
+
{{author}} This issue is unlabelled after 5 days. It needs attention. Please take care of this before the end of #{2.days.from_now.strftime('%Y-%m-%d')}
|
58
58
|
merge_requests:
|
59
59
|
rules:
|
60
60
|
[]
|
@@ -358,31 +358,8 @@ conditions:
|
|
358
358
|
This will make it only act on resources which have active milestones and
|
359
359
|
there exists next milestone which has already started.
|
360
360
|
|
361
|
-
|
362
|
-
|
363
|
-
##### API
|
364
|
-
|
365
|
-
| Name | Return type | Description |
|
366
|
-
| ---- | ---- | ---- |
|
367
|
-
| milestone | Milestone | The milestone attached to the resource |
|
368
|
-
|
369
|
-
##### Methods for `Milestone`
|
370
|
-
|
371
|
-
| Method | Return type | Description |
|
372
|
-
| ---- | ---- | ---- |
|
373
|
-
| id | Integer | The id of the milestone |
|
374
|
-
| iid | Integer | The iid of the milestone |
|
375
|
-
| project_id | Integer | The project id of the milestone if available |
|
376
|
-
| group_id | Integer | The group id of the milestone if available |
|
377
|
-
| title | String | The title of the milestone |
|
378
|
-
| description | String | The description of the milestone |
|
379
|
-
| state | String | The state of the milestone. Could be `active` or `closed` |
|
380
|
-
| due_date | Date | The due date of the milestone. Could be `nil` |
|
381
|
-
| start_date | Date | The start date of the milestone. Could be `nil` |
|
382
|
-
| updated_at | Time | The updated timestamp of the milestone |
|
383
|
-
| created_at | Time | The created timestamp of the milestone |
|
384
|
-
| succ | Milestone | The next active milestone beside this milestone |
|
385
|
-
| active? | Boolean | `true` if `state` is `active`; `false` otherwise |
|
361
|
+
See [Ruby expression API](#ruby-expression-api) for the list of currently
|
362
|
+
available API.
|
386
363
|
|
387
364
|
#### Limits field
|
388
365
|
|
@@ -525,6 +502,60 @@ actions:
|
|
525
502
|
@{{author}} Are you still interested in finishing this merge request?
|
526
503
|
```
|
527
504
|
|
505
|
+
###### Ruby expression
|
506
|
+
|
507
|
+
The comment can also contain Ruby expression, using Ruby's own string
|
508
|
+
interpolation syntax: `#{ expression }`. This gives you the most flexibility.
|
509
|
+
Suppose you want to mention the next active milestone relative to the one
|
510
|
+
associated with the resource, you can write:
|
511
|
+
|
512
|
+
```yml
|
513
|
+
actions:
|
514
|
+
comment: |
|
515
|
+
Please move this to %"#{milestone.succ.title}".
|
516
|
+
```
|
517
|
+
|
518
|
+
See [Ruby expression API](#ruby-expression-api) for the list of currently
|
519
|
+
available API.
|
520
|
+
|
521
|
+
**Note:** If you get a syntax error due to stray braces (`{` or `}`), use `\`
|
522
|
+
to escape it. For example:
|
523
|
+
|
524
|
+
```yml
|
525
|
+
actions:
|
526
|
+
comment: |
|
527
|
+
If \} comes first and/or following \{, you'll need to escape them. If it's just { wrapping something } then you don't need to, but it's also fine to escape them like \{ this \} if you prefer.
|
528
|
+
```
|
529
|
+
|
530
|
+
### Ruby expression API
|
531
|
+
|
532
|
+
Here's a list of currently available Ruby expression API:
|
533
|
+
|
534
|
+
##### API
|
535
|
+
|
536
|
+
| Name | Return type | Description |
|
537
|
+
| ---- | ---- | ---- |
|
538
|
+
| resource | Hash | The hash containing the raw data of the resource |
|
539
|
+
| milestone | Milestone | The milestone attached to the resource |
|
540
|
+
|
541
|
+
##### Methods for `Milestone`
|
542
|
+
|
543
|
+
| Method | Return type | Description |
|
544
|
+
| ---- | ---- | ---- |
|
545
|
+
| id | Integer | The id of the milestone |
|
546
|
+
| iid | Integer | The iid of the milestone |
|
547
|
+
| project_id | Integer | The project id of the milestone if available |
|
548
|
+
| group_id | Integer | The group id of the milestone if available |
|
549
|
+
| title | String | The title of the milestone |
|
550
|
+
| description | String | The description of the milestone |
|
551
|
+
| state | String | The state of the milestone. Could be `active` or `closed` |
|
552
|
+
| due_date | Date | The due date of the milestone. Could be `nil` |
|
553
|
+
| start_date | Date | The start date of the milestone. Could be `nil` |
|
554
|
+
| updated_at | Time | The updated timestamp of the milestone |
|
555
|
+
| created_at | Time | The created timestamp of the milestone |
|
556
|
+
| succ | Milestone | The next active milestone beside this milestone |
|
557
|
+
| active? | Boolean | `true` if `state` is `active`; `false` otherwise |
|
558
|
+
|
528
559
|
### Usage
|
529
560
|
|
530
561
|
```
|
data/gitlab-triage.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ['remy@rymai.me']
|
10
10
|
|
11
11
|
spec.summary = 'GitLab triage automation project.'
|
12
|
-
spec.homepage = 'https://gitlab.com/gitlab-org/triage'
|
12
|
+
spec.homepage = 'https://gitlab.com/gitlab-org/gitlab-triage'
|
13
13
|
spec.license = 'MIT'
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'action/comment'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module Action
|
6
|
+
def self.process(rules:, **args)
|
7
|
+
comment = rules.any? && rules
|
8
|
+
|
9
|
+
{
|
10
|
+
Comment => comment
|
11
|
+
}.compact.each do |action, rule|
|
12
|
+
act(action: action, rule: rule, **args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.act(action:, dry:, **args)
|
17
|
+
klass =
|
18
|
+
if dry
|
19
|
+
action.const_get(:Dry)
|
20
|
+
else
|
21
|
+
action
|
22
|
+
end
|
23
|
+
|
24
|
+
klass.new(**args).act
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Triage
|
3
|
+
module Action
|
4
|
+
class Base
|
5
|
+
attr_reader :name, :type, :rule, :resources, :net
|
6
|
+
|
7
|
+
def initialize(name:, type:, rule:, resources:, net:)
|
8
|
+
@name = name
|
9
|
+
@type = type
|
10
|
+
@rule = rule
|
11
|
+
@resources = resources
|
12
|
+
@net = net
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative '../command_builders/comment_body_builder'
|
3
|
+
require_relative '../command_builders/comment_command_builder'
|
4
|
+
require_relative '../command_builders/label_command_builder'
|
5
|
+
require_relative '../command_builders/remove_label_command_builder'
|
6
|
+
require_relative '../command_builders/cc_command_builder'
|
7
|
+
require_relative '../command_builders/status_command_builder'
|
8
|
+
|
9
|
+
module Gitlab
|
10
|
+
module Triage
|
11
|
+
module Action
|
12
|
+
class Comment < Base
|
13
|
+
class Dry < Comment
|
14
|
+
def act
|
15
|
+
puts "\nThe following comments would be posted for the rule **#{name}**:\n\n"
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def perform(resource, comment)
|
23
|
+
puts "# #{resource[:web_url]}\n```\n#{comment}\n```\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def act
|
28
|
+
resources.each do |resource|
|
29
|
+
comment = build_comment(resource)
|
30
|
+
|
31
|
+
perform(resource, comment)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_comment(resource)
|
38
|
+
CommandBuilders::CommentCommandBuilder.new(
|
39
|
+
[
|
40
|
+
CommandBuilders::CommentBodyBuilder.new(rule[:comment], resource: resource, net: net).build_command,
|
41
|
+
CommandBuilders::LabelCommandBuilder.new(rule[:labels]).build_command,
|
42
|
+
CommandBuilders::RemoveLabelCommandBuilder.new(rule[:remove_labels]).build_command,
|
43
|
+
CommandBuilders::CcCommandBuilder.new(rule[:mention]).build_command,
|
44
|
+
CommandBuilders::StatusCommandBuilder.new(rule[:status]).build_command
|
45
|
+
]
|
46
|
+
).build_command
|
47
|
+
end
|
48
|
+
|
49
|
+
def perform(resource, comment)
|
50
|
+
net[:network].post_api(
|
51
|
+
net[:token],
|
52
|
+
build_post_url(resource),
|
53
|
+
comment)
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_post_url(resource)
|
57
|
+
# POST /projects/:id/issues/:issue_iid/notes
|
58
|
+
post_url = UrlBuilders::UrlBuilder.new(
|
59
|
+
host_url: net[:host_url],
|
60
|
+
api_version: net[:api_version],
|
61
|
+
source_id: net[:source_id],
|
62
|
+
resource_type: type,
|
63
|
+
resource_id: resource['iid'],
|
64
|
+
sub_resource_type: 'notes'
|
65
|
+
).build
|
66
|
+
|
67
|
+
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if net[:debug]
|
68
|
+
|
69
|
+
post_url
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,32 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'base_command_builder'
|
4
|
+
require_relative '../resource/context'
|
2
5
|
|
3
6
|
module Gitlab
|
4
7
|
module Triage
|
5
8
|
module CommandBuilders
|
6
9
|
class CommentBodyBuilder < BaseCommandBuilder
|
7
10
|
SUPPORTED_PLACEHOLDERS = {
|
8
|
-
created_at: "{{created_at}}"
|
9
|
-
updated_at: "{{updated_at}}"
|
10
|
-
closed_at: "{{closed_at}}"
|
11
|
-
merged_at: "{{merged_at}}"
|
12
|
-
state: "{{state}}"
|
13
|
-
author: "@{{author.username}}"
|
14
|
-
assignee: "@{{assignee.username}}"
|
15
|
-
assignees: "@{{assignees.username}}"
|
16
|
-
closed_by: "@{{closed_by.username}}"
|
17
|
-
merged_by: "@{{merged_by.username}}"
|
18
|
-
milestone: %(%"{{milestone.title}}")
|
19
|
-
labels: %(~"{{labels}}")
|
20
|
-
upvotes: "{{upvotes}}"
|
21
|
-
downvotes: "{{downvotes}}"
|
11
|
+
created_at: "{{created_at}}",
|
12
|
+
updated_at: "{{updated_at}}",
|
13
|
+
closed_at: "{{closed_at}}",
|
14
|
+
merged_at: "{{merged_at}}",
|
15
|
+
state: "{{state}}",
|
16
|
+
author: "@{{author.username}}",
|
17
|
+
assignee: "@{{assignee.username}}",
|
18
|
+
assignees: "@{{assignees.username}}",
|
19
|
+
closed_by: "@{{closed_by.username}}",
|
20
|
+
merged_by: "@{{merged_by.username}}",
|
21
|
+
milestone: %(%"{{milestone.title}}"),
|
22
|
+
labels: %(~"{{labels}}"),
|
23
|
+
upvotes: "{{upvotes}}",
|
24
|
+
downvotes: "{{downvotes}}"
|
22
25
|
}.freeze
|
23
26
|
PLACEHOLDER_REGEX = /{{([\w\.]+)}}/
|
24
27
|
|
25
|
-
attr_reader :resource
|
28
|
+
attr_reader :resource, :net
|
26
29
|
|
27
|
-
def initialize(items, resource: nil)
|
30
|
+
def initialize(items, resource: nil, net: {})
|
28
31
|
super(items)
|
29
32
|
@resource = resource
|
33
|
+
@net = net
|
30
34
|
end
|
31
35
|
|
32
36
|
private
|
@@ -38,6 +42,16 @@ module Gitlab
|
|
38
42
|
def format_item(item)
|
39
43
|
return item unless resource
|
40
44
|
|
45
|
+
replace_placeholders(eval_interpolation(item))
|
46
|
+
end
|
47
|
+
|
48
|
+
def eval_interpolation(item)
|
49
|
+
quoted_comment = "%Q{#{item}}"
|
50
|
+
|
51
|
+
Resource::Context.new(resource, net).eval(quoted_comment)
|
52
|
+
end
|
53
|
+
|
54
|
+
def replace_placeholders(item)
|
41
55
|
SUPPORTED_PLACEHOLDERS.inject(item) do |comment, (placeholder, formatted_text)|
|
42
56
|
next comment unless comment.include?("{{#{placeholder}}}")
|
43
57
|
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -9,12 +9,7 @@ require_relative 'filters/author_member_conditions_filter'
|
|
9
9
|
require_relative 'filters/assignee_member_conditions_filter'
|
10
10
|
require_relative 'filters/ruby_conditions_filter'
|
11
11
|
require_relative 'limiters/date_field_limiter'
|
12
|
-
require_relative '
|
13
|
-
require_relative 'command_builders/comment_command_builder'
|
14
|
-
require_relative 'command_builders/label_command_builder'
|
15
|
-
require_relative 'command_builders/remove_label_command_builder'
|
16
|
-
require_relative 'command_builders/cc_command_builder'
|
17
|
-
require_relative 'command_builders/status_command_builder'
|
12
|
+
require_relative 'action'
|
18
13
|
require_relative 'api_query_builders/single_query_param_builder'
|
19
14
|
require_relative 'api_query_builders/multi_query_param_builder'
|
20
15
|
require_relative 'url_builders/url_builder'
|
@@ -77,6 +72,17 @@ module Gitlab
|
|
77
72
|
@network ||= Network.new(network_adapter, options)
|
78
73
|
end
|
79
74
|
|
75
|
+
def net
|
76
|
+
@net ||= {
|
77
|
+
host_url: host_url,
|
78
|
+
api_version: api_version,
|
79
|
+
token: options.token,
|
80
|
+
source_id: options.project_id,
|
81
|
+
debug: options.debug,
|
82
|
+
network: network
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
80
86
|
def network_adapter
|
81
87
|
@network_adapter ||= @network_adapter_class.new(options)
|
82
88
|
end
|
@@ -105,13 +111,17 @@ module Gitlab
|
|
105
111
|
print "* Limiting resources..."
|
106
112
|
resources = limit_resources(resources, rule_limits(rule))
|
107
113
|
puts "\n* Total after limiting: #{resources.count} resources"
|
108
|
-
|
114
|
+
Action.process(
|
115
|
+
name: rule[:name],
|
116
|
+
type: resource_type,
|
117
|
+
rules: rule_actions(rule),
|
118
|
+
resources: resources,
|
119
|
+
net: net,
|
120
|
+
dry: options.dry_run)
|
109
121
|
end
|
110
122
|
end
|
111
123
|
|
112
124
|
def filter_resources(resources, conditions)
|
113
|
-
net = { host_url: host_url, api_version: api_version, token: options.token, network: network }
|
114
|
-
|
115
125
|
resources.select do |resource|
|
116
126
|
results = []
|
117
127
|
|
@@ -135,34 +145,6 @@ module Gitlab
|
|
135
145
|
end
|
136
146
|
end
|
137
147
|
|
138
|
-
def process_resources(resource_type, resources, rule)
|
139
|
-
if options.dry_run
|
140
|
-
puts "\nThe following comments would be posted for the rule **#{rule[:name]}**:\n\n"
|
141
|
-
end
|
142
|
-
|
143
|
-
resources.each do |resource|
|
144
|
-
comment = build_comment(rule_actions(rule), resource: resource)
|
145
|
-
|
146
|
-
if options.dry_run
|
147
|
-
puts "# #{resource[:web_url]}\n```\n#{comment}\n```\n"
|
148
|
-
else
|
149
|
-
network.post_api(options.token, build_post_url(resource_type, resource), comment)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def build_comment(actions, resource: nil)
|
155
|
-
CommandBuilders::CommentCommandBuilder.new(
|
156
|
-
[
|
157
|
-
CommandBuilders::CommentBodyBuilder.new(actions[:comment], resource: resource).build_command,
|
158
|
-
CommandBuilders::LabelCommandBuilder.new(actions[:labels]).build_command,
|
159
|
-
CommandBuilders::RemoveLabelCommandBuilder.new(actions[:remove_labels]).build_command,
|
160
|
-
CommandBuilders::CcCommandBuilder.new(actions[:mention]).build_command,
|
161
|
-
CommandBuilders::StatusCommandBuilder.new(actions[:status]).build_command
|
162
|
-
]
|
163
|
-
).build_command
|
164
|
-
end
|
165
|
-
|
166
148
|
def build_get_url(resource_type, conditions)
|
167
149
|
# Example issues query with state and labels
|
168
150
|
# https://gitlab.com/api/v4/projects/test-triage%2Fissue-project/issues?state=open&labels=project%20label%20with%20spaces,group_label_no_spaces
|
@@ -191,22 +173,6 @@ module Gitlab
|
|
191
173
|
|
192
174
|
get_url
|
193
175
|
end
|
194
|
-
|
195
|
-
def build_post_url(resource_type, resource)
|
196
|
-
# POST /projects/:id/issues/:issue_iid/notes
|
197
|
-
post_url = UrlBuilders::UrlBuilder.new(
|
198
|
-
host_url: host_url,
|
199
|
-
api_version: api_version,
|
200
|
-
source_id: options.project_id,
|
201
|
-
resource_type: resource_type,
|
202
|
-
resource_id: resource['iid'],
|
203
|
-
sub_resource_type: 'notes'
|
204
|
-
).build
|
205
|
-
|
206
|
-
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if options.debug
|
207
|
-
|
208
|
-
post_url
|
209
|
-
end
|
210
176
|
end
|
211
177
|
end
|
212
178
|
end
|
@@ -27,10 +27,10 @@ module Gitlab
|
|
27
27
|
|
28
28
|
def limit
|
29
29
|
case @criterion
|
30
|
-
when :most_recent
|
31
|
-
@resources.first(@threshold)
|
32
30
|
when :oldest
|
33
|
-
@resources.
|
31
|
+
@resources.first(@threshold)
|
32
|
+
when :most_recent
|
33
|
+
@resources.last(@threshold).reverse
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
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: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -131,6 +131,9 @@ files:
|
|
131
131
|
- bin/gitlab-triage
|
132
132
|
- gitlab-triage.gemspec
|
133
133
|
- lib/gitlab/triage.rb
|
134
|
+
- lib/gitlab/triage/action.rb
|
135
|
+
- lib/gitlab/triage/action/base.rb
|
136
|
+
- lib/gitlab/triage/action/comment.rb
|
134
137
|
- lib/gitlab/triage/api_query_builders/base_query_param_builder.rb
|
135
138
|
- lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb
|
136
139
|
- lib/gitlab/triage/api_query_builders/single_query_param_builder.rb
|
@@ -172,7 +175,7 @@ files:
|
|
172
175
|
- lib/gitlab/triage/version.rb
|
173
176
|
- support/.gitlab-ci.example.yml
|
174
177
|
- support/.triage-policies.example.yml
|
175
|
-
homepage: https://gitlab.com/gitlab-org/triage
|
178
|
+
homepage: https://gitlab.com/gitlab-org/gitlab-triage
|
176
179
|
licenses:
|
177
180
|
- MIT
|
178
181
|
metadata: {}
|