danger-gitlab_reviewbot 1.0.2 → 1.1.5
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/.gitlab-ci.yml +34 -0
- data/Gemfile.lock +20 -17
- data/danger-gitlab_reviewbot.gemspec +6 -3
- data/lib/danger_gitlab_reviewbot.rb +2 -0
- data/lib/danger_plugin.rb +2 -0
- data/lib/gitlab_reviewbot/gem_version.rb +3 -1
- data/lib/gitlab_reviewbot/gitlab.rb +17 -12
- data/lib/gitlab_reviewbot/plugin.rb +44 -21
- data/lib/gitlab_reviewbot/strategies.rb +5 -4
- data/lib/gitlab_reviewbot/strategies/least_busy.rb +10 -10
- data/lib/gitlab_reviewbot/strategies/random.rb +6 -4
- data/lib/gitlab_reviewbot/strategies/strategy.rb +16 -9
- data/spec/gitlab_reviewbot_spec.rb +20 -14
- data/spec/least_busy_strategy_spec.rb +50 -9
- data/spec/random_strategy_spec.rb +13 -12
- data/spec/spec_helper.rb +10 -8
- metadata +40 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a87855f2af8575497c523339ced8070408e9e2191bbf1eec2b4cf1ef0e431376
|
|
4
|
+
data.tar.gz: 5d30b4bde8ad0ce2ccf8e95c5efb75899f1e3acf40cc8e806211da79e518acdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90895467fe4e12bf949f2bdc38bc72e2885aa0c77ec73cd3b7d47c4cfb0fe07f1f14641e159b47dc64fdbbd17b59d29bb49805794521305d077ae726c1063953
|
|
7
|
+
data.tar.gz: 39ab6152e3916f0ab1e473c06ca5514608906174bf416a28d3ebe4be8ac249414020effbc57a61375335d5eee864bd122eb0b425fe01ef18bc07e2566a80bd7f
|
data/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
image: "ruby:2.6"
|
|
2
|
+
|
|
3
|
+
cache:
|
|
4
|
+
paths:
|
|
5
|
+
- vendor/bundle/
|
|
6
|
+
|
|
7
|
+
stages:
|
|
8
|
+
- test
|
|
9
|
+
- deploy
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
stage: test
|
|
13
|
+
script:
|
|
14
|
+
- gem install bundler
|
|
15
|
+
- bundle --path vendor/bundle
|
|
16
|
+
- bundle exec rake spec
|
|
17
|
+
artifacts:
|
|
18
|
+
paths:
|
|
19
|
+
- rspec.xml
|
|
20
|
+
reports:
|
|
21
|
+
junit: rspec.xml
|
|
22
|
+
|
|
23
|
+
deploy:
|
|
24
|
+
stage: deploy
|
|
25
|
+
only:
|
|
26
|
+
- tags
|
|
27
|
+
before_script:
|
|
28
|
+
- 'mkdir ~/.gem && echo -e "---\r\n:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials && chmod 0600 ~/.gem/credentials'
|
|
29
|
+
script:
|
|
30
|
+
- gem install bundler
|
|
31
|
+
- bundle --path vendor/bundle
|
|
32
|
+
- bundle exec rake build
|
|
33
|
+
- gem push pkg/danger-gitlab_reviewbot-${CI_COMMIT_TAG}.gem
|
|
34
|
+
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
danger-gitlab_reviewbot (1.
|
|
4
|
+
danger-gitlab_reviewbot (1.1.5)
|
|
5
5
|
danger-gitlab
|
|
6
6
|
danger-plugin-api (~> 1.0)
|
|
7
7
|
|
|
@@ -11,6 +11,7 @@ GEM
|
|
|
11
11
|
addressable (2.7.0)
|
|
12
12
|
public_suffix (>= 2.0.2, < 5.0)
|
|
13
13
|
ast (2.4.0)
|
|
14
|
+
awesome_print (1.8.0)
|
|
14
15
|
claide (1.0.3)
|
|
15
16
|
claide-plugins (0.9.2)
|
|
16
17
|
cork
|
|
@@ -20,7 +21,7 @@ GEM
|
|
|
20
21
|
colored2 (3.1.2)
|
|
21
22
|
cork (0.3.0)
|
|
22
23
|
colored2 (~> 3.1)
|
|
23
|
-
danger (8.0.
|
|
24
|
+
danger (8.0.1)
|
|
24
25
|
claide (~> 1.0)
|
|
25
26
|
claide-plugins (>= 0.9.2)
|
|
26
27
|
colored2 (~> 3.1)
|
|
@@ -33,9 +34,9 @@ GEM
|
|
|
33
34
|
no_proxy_fix
|
|
34
35
|
octokit (~> 4.7)
|
|
35
36
|
terminal-table (~> 1)
|
|
36
|
-
danger-gitlab (
|
|
37
|
-
danger
|
|
38
|
-
gitlab (~> 4.0)
|
|
37
|
+
danger-gitlab (8.0.0)
|
|
38
|
+
danger
|
|
39
|
+
gitlab (~> 4.2, >= 4.2.0)
|
|
39
40
|
danger-plugin-api (1.0.0)
|
|
40
41
|
danger (> 2.0)
|
|
41
42
|
diff-lcs (1.3)
|
|
@@ -47,7 +48,7 @@ GEM
|
|
|
47
48
|
formatador (0.2.5)
|
|
48
49
|
git (1.7.0)
|
|
49
50
|
rchardet (~> 1.8)
|
|
50
|
-
gitlab (4.
|
|
51
|
+
gitlab (4.15.0)
|
|
51
52
|
httparty (~> 0.14, >= 0.14.0)
|
|
52
53
|
terminal-table (~> 1.5, >= 1.5.1)
|
|
53
54
|
guard (2.16.2)
|
|
@@ -64,10 +65,9 @@ GEM
|
|
|
64
65
|
guard (~> 2.1)
|
|
65
66
|
guard-compat (~> 1.1)
|
|
66
67
|
rspec (>= 2.99.0, < 4.0)
|
|
67
|
-
httparty (0.18.
|
|
68
|
+
httparty (0.18.1)
|
|
68
69
|
mime-types (~> 3.0)
|
|
69
70
|
multi_xml (>= 0.5.2)
|
|
70
|
-
jaro_winkler (1.5.4)
|
|
71
71
|
kramdown (2.2.1)
|
|
72
72
|
rexml
|
|
73
73
|
kramdown-parser-gfm (1.1.0)
|
|
@@ -76,7 +76,7 @@ GEM
|
|
|
76
76
|
rb-fsevent (>= 0.9.3)
|
|
77
77
|
rb-inotify (>= 0.9.7)
|
|
78
78
|
lumberjack (1.2.4)
|
|
79
|
-
method_source (
|
|
79
|
+
method_source (0.9.2)
|
|
80
80
|
mime-types (3.3.1)
|
|
81
81
|
mime-types-data (~> 3.2015)
|
|
82
82
|
mime-types-data (3.2020.0512)
|
|
@@ -95,9 +95,11 @@ GEM
|
|
|
95
95
|
parallel (1.19.1)
|
|
96
96
|
parser (2.7.1.2)
|
|
97
97
|
ast (~> 2.4.0)
|
|
98
|
-
pry (0.
|
|
99
|
-
coderay (~> 1.1)
|
|
100
|
-
method_source (~>
|
|
98
|
+
pry (0.12.2)
|
|
99
|
+
coderay (~> 1.1.0)
|
|
100
|
+
method_source (~> 0.9.0)
|
|
101
|
+
pry-nav (0.3.0)
|
|
102
|
+
pry (>= 0.9.10, < 0.13.0)
|
|
101
103
|
public_suffix (4.0.5)
|
|
102
104
|
rainbow (3.0.0)
|
|
103
105
|
rake (13.0.1)
|
|
@@ -112,15 +114,14 @@ GEM
|
|
|
112
114
|
rspec-mocks (~> 3.9.0)
|
|
113
115
|
rspec-core (3.9.2)
|
|
114
116
|
rspec-support (~> 3.9.3)
|
|
115
|
-
rspec-expectations (3.9.
|
|
117
|
+
rspec-expectations (3.9.2)
|
|
116
118
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
117
119
|
rspec-support (~> 3.9.0)
|
|
118
120
|
rspec-mocks (3.9.1)
|
|
119
121
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
120
122
|
rspec-support (~> 3.9.0)
|
|
121
123
|
rspec-support (3.9.3)
|
|
122
|
-
rubocop (0.
|
|
123
|
-
jaro_winkler (~> 1.5.1)
|
|
124
|
+
rubocop (0.83.0)
|
|
124
125
|
parallel (~> 1.10)
|
|
125
126
|
parser (>= 2.7.0.1)
|
|
126
127
|
rainbow (>= 2.2.2, < 4.0)
|
|
@@ -142,12 +143,14 @@ PLATFORMS
|
|
|
142
143
|
ruby
|
|
143
144
|
|
|
144
145
|
DEPENDENCIES
|
|
146
|
+
awesome_print
|
|
145
147
|
bundler (~> 2.1)
|
|
146
148
|
danger-gitlab_reviewbot!
|
|
147
|
-
guard
|
|
148
|
-
guard-rspec
|
|
149
|
+
guard
|
|
150
|
+
guard-rspec
|
|
149
151
|
listen (= 3.0.7)
|
|
150
152
|
pry
|
|
153
|
+
pry-nav
|
|
151
154
|
rake
|
|
152
155
|
rspec
|
|
153
156
|
rubocop
|
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.email = ['fabio.gallonetto@curve.com']
|
|
11
11
|
spec.description = %q{A review raffle bot for Gitlab }
|
|
12
12
|
spec.summary = %q{A review raffle bot for Gitlab.}
|
|
13
|
-
spec.homepage = 'https://
|
|
13
|
+
spec.homepage = 'https://git.curve.tools/fabio.gallonetto/danger-gitlab_reviewbot'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
15
|
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
|
@@ -33,8 +33,8 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_development_dependency "yard"
|
|
34
34
|
|
|
35
35
|
# Makes testing easy via `bundle exec guard`
|
|
36
|
-
spec.add_development_dependency 'guard'
|
|
37
|
-
spec.add_development_dependency 'guard-rspec'
|
|
36
|
+
spec.add_development_dependency 'guard'
|
|
37
|
+
spec.add_development_dependency 'guard-rspec'
|
|
38
38
|
|
|
39
39
|
# If you want to work on older builds of ruby
|
|
40
40
|
spec.add_development_dependency 'listen', '3.0.7'
|
|
@@ -47,4 +47,7 @@ Gem::Specification.new do |spec|
|
|
|
47
47
|
#
|
|
48
48
|
# This will stop test execution and let you inspect the results
|
|
49
49
|
spec.add_development_dependency 'pry'
|
|
50
|
+
spec.add_development_dependency 'pry-nav'
|
|
51
|
+
spec.add_development_dependency 'awesome_print'
|
|
52
|
+
|
|
50
53
|
end
|
data/lib/danger_plugin.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "gitlab"
|
|
2
4
|
|
|
3
5
|
module Gitlab
|
|
4
6
|
class User
|
|
@@ -25,12 +27,12 @@ module Gitlab
|
|
|
25
27
|
res = group_members(group_id)
|
|
26
28
|
|
|
27
29
|
developer_access_level = 30
|
|
28
|
-
res.select { |u| u.state ==
|
|
30
|
+
res.select { |u| u.state == "active" && u.access_level >= developer_access_level }.map { |u| User.new(u.id, u.username) }
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def assign_mr_to_users(project_id, mr_iid, users)
|
|
32
34
|
user_ids = users.map(&:id)
|
|
33
|
-
update_merge_request(project_id, mr_iid,
|
|
35
|
+
update_merge_request(project_id, mr_iid, "assignee_ids" => user_ids)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def fetch_author_for_mr(project_id, mr_iid)
|
|
@@ -39,30 +41,34 @@ module Gitlab
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def fetch_mrs_requiring_review(project_id)
|
|
42
|
-
merge_requests(project_id, :
|
|
44
|
+
merge_requests(project_id, state: "opened", per_page: "100").reject { |mr| mr.merge_status == "can_be_merged" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_user_with_username(username)
|
|
48
|
+
users({ username: username }).map { |u| User.new(u.id, u.username) }
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
def users_with_pending_mr_review(project_id)
|
|
46
52
|
outstanding_mrs = fetch_mrs_requiring_review(project_id)
|
|
47
53
|
all_assignees = outstanding_mrs.reduce([]) { |acc, mr| acc + mr.assignees }
|
|
48
|
-
assignees_id_map = all_assignees.
|
|
49
|
-
aid = a[
|
|
50
|
-
ausername = a[
|
|
54
|
+
assignees_id_map = all_assignees.each_with_object({}) do |a, acc|
|
|
55
|
+
aid = a["id"]
|
|
56
|
+
ausername = a["username"]
|
|
51
57
|
assignee = acc[aid] || User.new(aid, ausername)
|
|
52
58
|
assignee.review_count += 1
|
|
53
59
|
acc[aid] = assignee
|
|
54
|
-
|
|
55
|
-
}
|
|
60
|
+
end
|
|
56
61
|
assignees_id_map.values
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
def fetch_mr_reviewers(project_id, mr_iid)
|
|
60
|
-
merge_request(project_id, mr_iid).assignees.map { |u| User.new(u[
|
|
65
|
+
merge_request(project_id, mr_iid).assignees.map { |u| User.new(u["id"], u["username"]) }
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
private
|
|
69
|
+
|
|
64
70
|
def search_group(group_name)
|
|
65
|
-
short_name = group_name.split(
|
|
71
|
+
short_name = group_name.split("/").last
|
|
66
72
|
res = group_search(short_name)
|
|
67
73
|
res = res.find { |i| i.full_path == group_name }
|
|
68
74
|
|
|
@@ -74,4 +80,3 @@ module Gitlab
|
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "gitlab_reviewbot/strategies"
|
|
3
4
|
|
|
4
5
|
module Danger
|
|
5
6
|
# This is your plugin class. Any attributes or methods you expose here will
|
|
@@ -20,8 +21,7 @@ module Danger
|
|
|
20
21
|
# @tags monday, weekends, time, rattata
|
|
21
22
|
#
|
|
22
23
|
class DangerGitlabReviewbot < Plugin
|
|
23
|
-
|
|
24
|
-
# Define the group to take the reviewers from.
|
|
24
|
+
# Define the group to take the reviewers from.
|
|
25
25
|
# NOTE: This is the group full path as in 'tech/iOS' instead of just the group name
|
|
26
26
|
#
|
|
27
27
|
# @return String
|
|
@@ -32,7 +32,7 @@ module Danger
|
|
|
32
32
|
# NOTE: The plugin won't remove existing assigned reviewers
|
|
33
33
|
#
|
|
34
34
|
# @return Int
|
|
35
|
-
|
|
35
|
+
attr_writer :assignees_amount
|
|
36
36
|
def assignees_amount
|
|
37
37
|
@assignees_amount || 1
|
|
38
38
|
end
|
|
@@ -44,17 +44,15 @@ module Danger
|
|
|
44
44
|
# * Danger::AssignStrategies::LeastBusyStrategy - assign the N users with the least amount of open MRs
|
|
45
45
|
# to review
|
|
46
46
|
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@strategy || Danger::AssignStrategies::RandomStrategy
|
|
47
|
+
def strategy=(klass)
|
|
48
|
+
@strategy = klass.new(client: gitlab.api, project: project_id, mr_iid: mr_iid)
|
|
50
49
|
end
|
|
51
50
|
|
|
51
|
+
|
|
52
52
|
# Call this method from the Dangerfile to assign reviewers to your merge requests
|
|
53
53
|
# @return The usernames list of assigned reviewes [Array<String>]
|
|
54
54
|
#
|
|
55
55
|
def assign!
|
|
56
|
-
project_id = ENV['CI_PROJECT_ID']
|
|
57
|
-
mr_iid = ENV['CI_MERGE_REQUEST_IID']
|
|
58
56
|
if mr_iid.nil?
|
|
59
57
|
raise "Env variable CI_MERGE_REQUEST_IID doesn't point to a valid merge request iid"
|
|
60
58
|
end
|
|
@@ -63,27 +61,52 @@ module Danger
|
|
|
63
61
|
raise "Env variable CI_PROJECT_ID doesn't point to a valid project id"
|
|
64
62
|
end
|
|
65
63
|
|
|
66
|
-
current_assignees = (ENV[
|
|
67
|
-
already_assigned_count = current_assignees.length
|
|
68
|
-
required_assignees_count = [assignees_amount - already_assigned_count, 0].max
|
|
64
|
+
current_assignees = (ENV["CI_MERGE_REQUEST_ASSIGNEES"] || "").split(",") # buggy?
|
|
65
|
+
# already_assigned_count = current_assignees.length
|
|
66
|
+
# required_assignees_count = [assignees_amount - already_assigned_count, 0].max
|
|
69
67
|
|
|
70
|
-
puts "Project ID: #{project_id}" if verbose
|
|
71
|
-
puts "MR IID: #{mr_iid}" if verbose
|
|
72
|
-
puts "Currently assigned: #{current_assignees}" if verbose
|
|
73
|
-
|
|
68
|
+
ui.puts "Project ID: #{project_id}" if verbose
|
|
69
|
+
ui.puts "MR IID: #{mr_iid}" if verbose
|
|
70
|
+
ui.puts "Currently assigned: #{current_assignees}" if verbose
|
|
71
|
+
# puts "Required: #{required_assignees_count}" if @verbose
|
|
74
72
|
|
|
75
73
|
# if required_assignees_count == 0
|
|
76
|
-
# puts "Nothing to do" if
|
|
74
|
+
# puts "Nothing to do" if verbose
|
|
77
75
|
# return
|
|
78
76
|
# end
|
|
79
77
|
|
|
80
|
-
|
|
78
|
+
@strategy.group_name = gitlab_group
|
|
81
79
|
|
|
82
|
-
assignees =
|
|
80
|
+
assignees = @strategy.assign! assignees_amount
|
|
83
81
|
|
|
84
|
-
puts "Assigning: #{assignees}" if verbose
|
|
82
|
+
ui.puts "Assigning: #{assignees}" if verbose
|
|
85
83
|
return assignees
|
|
86
84
|
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
attr_reader :strategy
|
|
89
|
+
|
|
90
|
+
def project_id
|
|
91
|
+
ENV["CI_PROJECT_ID"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def mr_iid
|
|
95
|
+
ENV["CI_MERGE_REQUEST_IID"]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Once a strategy is in place, adopt the conf methods
|
|
99
|
+
def method_missing(method, *args)
|
|
100
|
+
super unless method.to_s.start_with? "strategy_"
|
|
101
|
+
if strategy.respond_to? method.to_s.delete_prefix("strategy_")
|
|
102
|
+
strategy.send(method.to_s.delete_prefix("strategy_"), *args)
|
|
103
|
+
else
|
|
104
|
+
super
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
109
|
+
method_name.to_s.start_with?("strategy_") || super
|
|
110
|
+
end
|
|
87
111
|
end
|
|
88
112
|
end
|
|
89
|
-
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Danger
|
|
2
4
|
module AssignStrategies
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
5
|
+
require "gitlab_reviewbot/strategies/strategy"
|
|
6
|
+
require "gitlab_reviewbot/strategies/random"
|
|
7
|
+
require "gitlab_reviewbot/strategies/least_busy"
|
|
6
8
|
end
|
|
7
9
|
end
|
|
8
|
-
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "gitlab_reviewbot/gitlab"
|
|
2
4
|
|
|
3
5
|
module Danger
|
|
4
6
|
module AssignStrategies
|
|
5
7
|
class LeastBusyStrategy < Strategy
|
|
6
8
|
def assignees(amount)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
invalid_assignees = [
|
|
9
|
+
# This doesn't fetch the review count for the users so we need to fetch it separately later
|
|
10
|
+
users_in_group = fetch_users_in_group
|
|
11
|
+
invalid_assignees = [fetch_author] + fetch_assigned_reviewers
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
group_users_with_reviews_pending = client.users_with_pending_mr_review(project_id).filter { |u| users_in_group.include? u }
|
|
14
|
+
group_users_without_reviews_pending = users_in_group.filter { |u| !group_users_with_reviews_pending.include? u }
|
|
13
15
|
|
|
14
|
-
(
|
|
15
|
-
|
|
16
|
-
.first(amount)
|
|
16
|
+
candidates = (group_users_with_reviews_pending + group_users_without_reviews_pending).filter { |u| !invalid_assignees.include? u }
|
|
17
|
+
candidates.shuffle.sort_by(&:review_count).first(amount)
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
|
-
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "gitlab_reviewbot/gitlab"
|
|
2
4
|
|
|
3
5
|
module Danger
|
|
4
6
|
module AssignStrategies
|
|
5
7
|
class RandomStrategy < Strategy
|
|
6
8
|
def assignees(amount)
|
|
7
|
-
invalid_assignees = [
|
|
8
|
-
fetch_users_in_group.filter { |u| !
|
|
9
|
-
|
|
9
|
+
invalid_assignees = [fetch_author] + fetch_assigned_reviewers
|
|
10
|
+
fetch_users_in_group.filter { |u| !invalid_assignees.include? u }
|
|
11
|
+
.sample(amount)
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Danger
|
|
2
4
|
module AssignStrategies
|
|
3
5
|
class Strategy
|
|
@@ -5,26 +7,27 @@ module Danger
|
|
|
5
7
|
attr_accessor :project_id
|
|
6
8
|
attr_accessor :group_name
|
|
7
9
|
attr_accessor :client
|
|
10
|
+
attr_accessor :excluded_users
|
|
8
11
|
|
|
9
|
-
def initialize(client:, project:,
|
|
12
|
+
def initialize(client:, project:, mr_iid:)
|
|
10
13
|
@client = client
|
|
11
14
|
@project_id = project
|
|
12
|
-
@mr_iid =
|
|
13
|
-
@
|
|
15
|
+
@mr_iid = mr_iid
|
|
16
|
+
@excluded_users = []
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def assign!(amount)
|
|
17
|
-
currently_assigned = fetch_assigned_reviewers
|
|
18
|
-
return [] if (amount - currently_assigned.length)
|
|
20
|
+
currently_assigned = fetch_assigned_reviewers
|
|
21
|
+
return [] if (amount - currently_assigned.length) <= 0
|
|
19
22
|
|
|
20
23
|
to_be_assigned = assignees(amount - currently_assigned.length)
|
|
21
24
|
all_assignees = currently_assigned + to_be_assigned
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
client.assign_mr_to_users(project_id, mr_iid, all_assignees)
|
|
24
27
|
all_assignees.map(&:username)
|
|
25
28
|
end
|
|
26
29
|
|
|
27
|
-
def assignees(
|
|
30
|
+
def assignees(_amount)
|
|
28
31
|
raise "To be implemented in the subclasses"
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -37,9 +40,13 @@ module Danger
|
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def fetch_users_in_group
|
|
40
|
-
|
|
43
|
+
excluded = @excluded_users.map do |u|
|
|
44
|
+
server_user = client.find_user_with_username(u).first
|
|
45
|
+
raise "ERROR: Invalid username #{u} among excluded_users" if server_user.nil?
|
|
46
|
+
server_user
|
|
47
|
+
end
|
|
48
|
+
client.fetch_users_for_group(@group_name).filter { |u| !excluded.include? u }
|
|
41
49
|
end
|
|
42
50
|
end
|
|
43
51
|
end
|
|
44
52
|
end
|
|
45
|
-
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("spec_helper", __dir__)
|
|
2
4
|
|
|
3
5
|
module Danger
|
|
4
6
|
describe Danger::DangerGitlabReviewbot do
|
|
@@ -11,46 +13,50 @@ module Danger
|
|
|
11
13
|
#
|
|
12
14
|
describe "with Dangerfile" do
|
|
13
15
|
before do
|
|
14
|
-
testing_env.each { |k,v| ENV[k] =
|
|
16
|
+
testing_env.each { |k, v| ENV[k] = v.to_s }
|
|
15
17
|
|
|
16
18
|
@dangerfile = testing_dangerfile
|
|
17
19
|
@plugin = @dangerfile.gitlab_reviewbot
|
|
18
|
-
@plugin.strategy = Danger::AssignStrategies::RandomStrategy
|
|
19
20
|
@strategy_mock = instance_double(Danger::AssignStrategies::Strategy)
|
|
20
21
|
allow(Danger::AssignStrategies::RandomStrategy).to receive(:new).and_return(@strategy_mock)
|
|
22
|
+
allow(@strategy_mock).to receive(:group_name=).with("tech/ios")
|
|
23
|
+
@plugin.strategy = Danger::AssignStrategies::RandomStrategy
|
|
24
|
+
@plugin.gitlab_group = "tech/ios"
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
it "Assign one reviewer" do
|
|
24
|
-
@
|
|
25
|
-
|
|
26
|
-
expect(@strategy_mock).to receive(:assign!).with(1).and_return(['Sam'])
|
|
28
|
+
expect(@strategy_mock).to receive(:assign!).with(1).and_return(["Sam"])
|
|
27
29
|
|
|
28
30
|
@plugin.assign!
|
|
29
31
|
end
|
|
30
32
|
it "Assign one reviewer" do
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
expect(@strategy_mock).to receive(:assign!).with(1).and_return(['Sam'])
|
|
33
|
+
expect(@strategy_mock).to receive(:assign!).with(1).and_return(["Sam"])
|
|
34
34
|
|
|
35
35
|
@plugin.assign!
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
it "Assign multiple reviewers" do
|
|
39
|
-
@plugin.gitlab_group = 'tech/ios'
|
|
40
39
|
@plugin.assignees_amount = 2
|
|
41
40
|
|
|
42
|
-
expect(@strategy_mock).to receive(:assign!).with(2).and_return([
|
|
41
|
+
expect(@strategy_mock).to receive(:assign!).with(2).and_return(["Sam, Nic"])
|
|
43
42
|
|
|
44
43
|
@plugin.assign!
|
|
45
44
|
end
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
it "Correctly sets strategy options" do
|
|
47
|
+
expect(@strategy_mock).to receive(:excluded_users=)
|
|
48
|
+
expect(@strategy_mock).to receive(:excluded_users).and_return([])
|
|
49
|
+
|
|
50
|
+
@plugin.strategy_excluded_users = ["Tom"]
|
|
51
|
+
@plugin.strategy_excluded_users << "Sam"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
["CI_PROJECT_ID", "CI_MERGE_REQUEST_IID"].each do |var|
|
|
48
55
|
it "Fails when required #{var} variables are not available" do
|
|
49
56
|
ENV[var] = nil
|
|
50
|
-
expect{@plugin.assign!}.to raise_error(RuntimeError)
|
|
57
|
+
expect { @plugin.assign! }.to raise_error(RuntimeError)
|
|
51
58
|
end
|
|
52
59
|
end
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
end
|
|
56
|
-
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("spec_helper", __dir__)
|
|
2
4
|
|
|
3
5
|
module Danger
|
|
4
6
|
describe Danger::AssignStrategies::LeastBusyStrategy do
|
|
5
7
|
before do
|
|
6
|
-
testing_env.each { |k,v| ENV[k] =
|
|
8
|
+
testing_env.each { |k, v| ENV[k] = v.to_s }
|
|
7
9
|
@dangerfile = testing_dangerfile
|
|
8
10
|
|
|
9
|
-
@sam = Gitlab::User.new(1,
|
|
10
|
-
@tom = Gitlab::User.new(2,
|
|
11
|
-
@nic = Gitlab::User.new(3,
|
|
12
|
-
@luke = Gitlab::User.new(4,
|
|
11
|
+
@sam = Gitlab::User.new(1, "Sam")
|
|
12
|
+
@tom = Gitlab::User.new(2, "Tom")
|
|
13
|
+
@nic = Gitlab::User.new(3, "Nic")
|
|
14
|
+
@luke = Gitlab::User.new(4, "Luke")
|
|
13
15
|
|
|
14
16
|
@mock_client = double(Gitlab::Client)
|
|
15
17
|
@author = @nic
|
|
16
18
|
@nic.review_count = 0
|
|
17
19
|
@members = [@author, @tom, @sam, @luke]
|
|
18
20
|
allow(@mock_client).to receive(:fetch_author_for_mr).and_return(@author)
|
|
19
|
-
allow(@mock_client).to receive(:fetch_users_for_group).with(
|
|
21
|
+
allow(@mock_client).to receive(:fetch_users_for_group).with("tech/ios").and_return(@members)
|
|
20
22
|
|
|
21
|
-
@strategy = AssignStrategies::LeastBusyStrategy.new(client: @mock_client, project: 10,
|
|
23
|
+
@strategy = AssignStrategies::LeastBusyStrategy.new(client: @mock_client, project: 10, mr_iid: 110)
|
|
24
|
+
@strategy.group_name = "tech/ios"
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
it "Assign the one least busy" do
|
|
@@ -89,6 +92,44 @@ module Danger
|
|
|
89
92
|
|
|
90
93
|
@strategy.assign!(1)
|
|
91
94
|
end
|
|
95
|
+
|
|
96
|
+
# TODO: This should go up to the superclass for testing
|
|
97
|
+
it "honours excluded users" do
|
|
98
|
+
allow(@mock_client).to receive(:fetch_mr_reviewers).with(10, 110).and_return([])
|
|
99
|
+
@tom.review_count = 0
|
|
100
|
+
@sam.review_count = 4
|
|
101
|
+
@luke.review_count = 3
|
|
102
|
+
users_with_pending_mr_review = [@author, @sam, @luke]
|
|
103
|
+
expect(@mock_client).to receive(:users_with_pending_mr_review).and_return(users_with_pending_mr_review)
|
|
104
|
+
|
|
105
|
+
allow(@mock_client).to receive(:find_user_with_username).with("Tom").and_return([@tom])
|
|
106
|
+
@strategy.excluded_users = ["Tom"]
|
|
107
|
+
|
|
108
|
+
expect(@mock_client).to receive(:assign_mr_to_users) do |project, mr, users|
|
|
109
|
+
expect(project).to be == 10
|
|
110
|
+
expect(mr).to be == 110
|
|
111
|
+
expect(users).to contain_exactly(@luke)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
@strategy.assign!(1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "Raises error when excluded_user is invalid" do
|
|
118
|
+
allow(@mock_client).to receive(:fetch_mr_reviewers).with(10, 110).and_return([])
|
|
119
|
+
|
|
120
|
+
allow(@mock_client).to receive(:find_user_with_username).with("WrongTom").and_return([])
|
|
121
|
+
@strategy.excluded_users = ["WrongTom"]
|
|
122
|
+
|
|
123
|
+
expect { @strategy.assign!(1) }.to raise_error("ERROR: Invalid username WrongTom among excluded_users")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# it "test" do
|
|
127
|
+
# strategy = AssignStrategies::LeastBusyStrategy.new(client: @dangerfile.gitlab.api, project: 346, mr_iid: 960)
|
|
128
|
+
# strategy.excluded_users << 'fabio.gallonetto'
|
|
129
|
+
# strategy.group_name = 'tech/ios'
|
|
130
|
+
# require 'pry'
|
|
131
|
+
# binding.pry
|
|
132
|
+
# puts strategy.assign!(2)
|
|
133
|
+
# end
|
|
92
134
|
end
|
|
93
135
|
end
|
|
94
|
-
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("spec_helper", __dir__)
|
|
2
4
|
|
|
3
5
|
module Danger
|
|
4
6
|
describe Danger::AssignStrategies::RandomStrategy do
|
|
5
7
|
before do
|
|
6
|
-
testing_env.each { |k,v| ENV[k] =
|
|
8
|
+
testing_env.each { |k, v| ENV[k] = v.to_s }
|
|
7
9
|
@dangerfile = testing_dangerfile
|
|
8
10
|
|
|
9
|
-
@sam = Gitlab::User.new(1,
|
|
10
|
-
@tom = Gitlab::User.new(2,
|
|
11
|
-
@nic = Gitlab::User.new(3,
|
|
12
|
-
@luke = Gitlab::User.new(4,
|
|
11
|
+
@sam = Gitlab::User.new(1, "Sam")
|
|
12
|
+
@tom = Gitlab::User.new(2, "Tom")
|
|
13
|
+
@nic = Gitlab::User.new(3, "Nic")
|
|
14
|
+
@luke = Gitlab::User.new(4, "Luke")
|
|
15
|
+
@lei = Gitlab::User.new(5, "Lei")
|
|
13
16
|
|
|
14
17
|
@mock_client = double(Gitlab::Client)
|
|
15
|
-
@author = @
|
|
18
|
+
@author = @lei
|
|
16
19
|
@members = [@author, @tom, @sam]
|
|
17
20
|
allow(@mock_client).to receive(:fetch_author_for_mr).and_return(@author)
|
|
18
|
-
allow(@mock_client).to receive(:fetch_users_for_group).with(
|
|
21
|
+
allow(@mock_client).to receive(:fetch_users_for_group).with("tech/ios").and_return(@members)
|
|
19
22
|
|
|
20
|
-
@strategy = AssignStrategies::RandomStrategy.new(client: @mock_client, project: 10,
|
|
23
|
+
@strategy = AssignStrategies::RandomStrategy.new(client: @mock_client, project: 10, mr_iid: 110)
|
|
24
|
+
@strategy.group_name = "tech/ios"
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
it "assign the right amount of reviewers" do
|
|
@@ -53,8 +57,5 @@ module Danger
|
|
|
53
57
|
|
|
54
58
|
@strategy.assign!(2)
|
|
55
59
|
end
|
|
56
|
-
|
|
57
|
-
|
|
58
60
|
end
|
|
59
61
|
end
|
|
60
|
-
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "pathname"
|
|
2
|
-
ROOT = Pathname.new(File.expand_path("
|
|
4
|
+
ROOT = Pathname.new(File.expand_path("..", __dir__))
|
|
3
5
|
$:.unshift((ROOT + "lib").to_s)
|
|
4
6
|
$:.unshift((ROOT + "spec").to_s)
|
|
5
7
|
|
|
@@ -9,7 +11,7 @@ require "pry"
|
|
|
9
11
|
require "rspec"
|
|
10
12
|
require "danger"
|
|
11
13
|
|
|
12
|
-
if `git remote -v` ==
|
|
14
|
+
if `git remote -v` == ""
|
|
13
15
|
puts "You cannot run tests without setting a local git remote on this repo"
|
|
14
16
|
puts "It's a weird side-effect of Danger's internals."
|
|
15
17
|
exit(0)
|
|
@@ -50,12 +52,12 @@ end
|
|
|
50
52
|
# running a PR on TravisCI
|
|
51
53
|
def testing_env
|
|
52
54
|
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
"CI_MERGE_REQUEST_IID" => "549",
|
|
56
|
+
"CI_MERGE_REQUEST_PROJECT_PATH" => "...",
|
|
57
|
+
"CI_MERGE_REQUEST_PROJECT_URL" => "...",
|
|
58
|
+
"DANGER_GITLAB_HOST" => "git.curve.tools", # This needs to be the same as where the repo is stored due to Danger internals :facepalm:
|
|
59
|
+
"CI_API_V4_URL" => "https://git.curve.tools/api/v4",
|
|
60
|
+
"CI_PROJECT_ID" => "346",
|
|
59
61
|
"GITLAB_CI" => true,
|
|
60
62
|
"DANGER_GITLAB_API_TOKEN" => "token-token-token"
|
|
61
63
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: danger-gitlab_reviewbot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fabio Gallonetto
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-06-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: danger-plugin-api
|
|
@@ -112,30 +112,30 @@ dependencies:
|
|
|
112
112
|
name: guard
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
|
-
- - "
|
|
115
|
+
- - ">="
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
117
|
+
version: '0'
|
|
118
118
|
type: :development
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
|
-
- - "
|
|
122
|
+
- - ">="
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
124
|
+
version: '0'
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
126
|
name: guard-rspec
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
|
-
- - "
|
|
129
|
+
- - ">="
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '
|
|
131
|
+
version: '0'
|
|
132
132
|
type: :development
|
|
133
133
|
prerelease: false
|
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
135
|
requirements:
|
|
136
|
-
- - "
|
|
136
|
+
- - ">="
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '
|
|
138
|
+
version: '0'
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
140
|
name: listen
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,6 +164,34 @@ dependencies:
|
|
|
164
164
|
- - ">="
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: pry-nav
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: awesome_print
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0'
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - ">="
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '0'
|
|
167
195
|
description: 'A review raffle bot for Gitlab '
|
|
168
196
|
email:
|
|
169
197
|
- fabio.gallonetto@curve.com
|
|
@@ -172,6 +200,7 @@ extensions: []
|
|
|
172
200
|
extra_rdoc_files: []
|
|
173
201
|
files:
|
|
174
202
|
- ".gitignore"
|
|
203
|
+
- ".gitlab-ci.yml"
|
|
175
204
|
- ".rubocop.yml"
|
|
176
205
|
- Gemfile
|
|
177
206
|
- Gemfile.lock
|
|
@@ -193,7 +222,7 @@ files:
|
|
|
193
222
|
- spec/least_busy_strategy_spec.rb
|
|
194
223
|
- spec/random_strategy_spec.rb
|
|
195
224
|
- spec/spec_helper.rb
|
|
196
|
-
homepage: https://
|
|
225
|
+
homepage: https://git.curve.tools/fabio.gallonetto/danger-gitlab_reviewbot
|
|
197
226
|
licenses:
|
|
198
227
|
- MIT
|
|
199
228
|
metadata: {}
|