gitlab-triage 0.8.1 → 0.9.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 +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: {}
|