gitlab_roulette 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5cc6e8fe267c4dc7c5e6609f0425c218c4eb3fee2e929483da159d0940015a73
4
+ data.tar.gz: df8c969d4d068993f2478b4913d4fb7e8762461fbc5fab4dd14f018d7d4d3c79
5
+ SHA512:
6
+ metadata.gz: 4e8bf7d129da294a986eafd3bb59f8aa9b0edc6c23852feba0cc0346b494d9dc902113c85c860ddaeaeab504da19616e969c1403e1b744cf5a56502952ce8923
7
+ data.tar.gz: 68dbb71f8222cdcdae6e6afe9deec123794675681b9bf227fc73135205e1af1d45b0a3af36e60d301da771006125cd7fb651fb5721d5b06a5b30c52cbc4fdb88
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at andrei.merfu@sparktech.ro. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Dangerfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/gitlab_roulette/gitlab_danger'
4
+ require_relative 'lib/gitlab_roulette/danger/request_helper'
5
+
6
+ danger.import_plugin('danger/plugins/helper.rb')
7
+ danger.import_plugin('danger/plugins/roulette.rb')
8
+
9
+ danger.import_dangerfile(path: 'danger/roulette')
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gitlab_roulette.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gitlab_roulette (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
+ claide (1.0.3)
12
+ claide-plugins (0.9.2)
13
+ cork
14
+ nap
15
+ open4 (~> 1.3)
16
+ colored2 (3.1.2)
17
+ cork (0.3.0)
18
+ colored2 (~> 3.1)
19
+ danger (6.1.0)
20
+ claide (~> 1.0)
21
+ claide-plugins (>= 0.9.2)
22
+ colored2 (~> 3.1)
23
+ cork (~> 0.1)
24
+ faraday (~> 0.9)
25
+ faraday-http-cache (~> 2.0)
26
+ git (~> 1.5)
27
+ kramdown (~> 2.0)
28
+ kramdown-parser-gfm (~> 1.0)
29
+ no_proxy_fix
30
+ octokit (~> 4.7)
31
+ terminal-table (~> 1)
32
+ danger-gitlab (7.0.0)
33
+ danger (~> 6.0)
34
+ gitlab (~> 4.2, >= 4.2.0)
35
+ faraday (0.17.1)
36
+ multipart-post (>= 1.2, < 3)
37
+ faraday-http-cache (2.0.0)
38
+ faraday (~> 0.8)
39
+ git (1.5.0)
40
+ gitlab (4.12.0)
41
+ httparty (~> 0.14, >= 0.14.0)
42
+ terminal-table (~> 1.5, >= 1.5.1)
43
+ httparty (0.17.1)
44
+ mime-types (~> 3.0)
45
+ multi_xml (>= 0.5.2)
46
+ kramdown (2.1.0)
47
+ kramdown-parser-gfm (1.1.0)
48
+ kramdown (~> 2.0)
49
+ mime-types (3.3)
50
+ mime-types-data (~> 3.2015)
51
+ mime-types-data (3.2019.1009)
52
+ multi_xml (0.6.0)
53
+ multipart-post (2.1.1)
54
+ nap (1.1.0)
55
+ no_proxy_fix (0.1.2)
56
+ octokit (4.14.0)
57
+ sawyer (~> 0.8.0, >= 0.5.3)
58
+ open4 (1.3.4)
59
+ public_suffix (4.0.1)
60
+ rake (10.5.0)
61
+ sawyer (0.8.2)
62
+ addressable (>= 2.3.5)
63
+ faraday (> 0.8, < 2.0)
64
+ terminal-table (1.8.0)
65
+ unicode-display_width (~> 1.1, >= 1.1.1)
66
+ unicode-display_width (1.6.0)
67
+
68
+ PLATFORMS
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ bundler (~> 2.0)
73
+ danger (~> 6.1.0)
74
+ danger-gitlab (~> 7.0.0)
75
+ faraday (~> 0.17.1)
76
+ gitlab_roulette!
77
+ rake (~> 10.0)
78
+
79
+ BUNDLED WITH
80
+ 2.0.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Andrei Merfu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Gitlab Roulette
2
+
3
+ Gitlab Roulette chooses automatically a random reviewer and maintainer for your merge request and prints a beautiful message using DangerBot.
4
+
5
+ <a href="https://ibb.co/Sn9Dsyd"><img src="https://i.ibb.co/hWtx79Z/Screenshot-2019-12-15-at-16-10-36.png" alt="Screenshot-2019-12-15-at-16-10-36" border="0"></a>
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'gitlab_roulette'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install gitlab_roulette
22
+
23
+ ## Usage
24
+
25
+ First of all, you must set some environment variables to your Gitlab CI pipeline:
26
+
27
+ ```yaml
28
+ CI_PROJECT_NAME: "your_project_name"
29
+ GITLAB_ROULETTE_URL: "url to json file"
30
+ GITLAB_HOST: "https://self_hosted_gitlab_url"
31
+ ```
32
+
33
+ Then all you have to do is to create a Dangerfile in the root directory of the project with the following configuration:
34
+
35
+ ```ruby
36
+ # frozen_string_literal: true
37
+
38
+ danger.import_dangerfile(gem: "gitlab_roulette")
39
+ ```
40
+
41
+ GITLAB_ROULETTE_URL contains a JSON file with all the users from your Gitlab and his rank
42
+
43
+ ```json
44
+ [
45
+ {
46
+ "username":"andrei.merfu","name":"Andrei Merfu",
47
+ "role":"Backend Engineer",
48
+ "projects": {
49
+ "gitlab_roulette":"maintainer backend"
50
+ }
51
+ },
52
+ {
53
+ "username":"another.user","name":"X Y",
54
+ "role":"Backend Engineer",
55
+ "projects": {
56
+ "gitlab_roulette":"reviewer backend"
57
+ }
58
+ }
59
+ ]
60
+ ```
61
+
62
+ ## License
63
+
64
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gitlab_roulette"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/gitlab_roulette/danger/helper'
4
+
5
+ module Danger
6
+ # Common helper functions for our danger scripts. See Gitlab::Danger::Helper
7
+ # for more details
8
+ class Helper < Plugin
9
+ # Put the helper code somewhere it can be tested
10
+ include GitlabRoulette::Danger::Helper
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/gitlab_roulette/danger/roulette'
4
+
5
+ module Danger
6
+ class Roulette < Plugin
7
+ # Put the helper code somewhere it can be tested
8
+ include GitlabRoulette::Danger::Roulette
9
+ end
10
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ MESSAGE = <<MARKDOWN
6
+ ## Reviewer roulette
7
+
8
+ Changes that require review have been detected! A merge request is normally
9
+ reviewed by both a reviewer and a maintainer in its primary category (e.g.
10
+ ~frontend or ~backend), and by a maintainer in all other categories.
11
+ MARKDOWN
12
+
13
+ CATEGORY_TABLE_HEADER = <<MARKDOWN
14
+
15
+ To spread load more evenly across eligible reviewers, Danger has randomly picked
16
+ a candidate for each review slot. Feel free to override this selection if you
17
+ think someone else would be better-suited, or the chosen person is unavailable.
18
+
19
+ Once you've decided who will review this merge request, mention them as you
20
+ normally would! Danger does not (yet?) automatically notify them for you.
21
+
22
+ | Category | Reviewer | Maintainer |
23
+ | -------- | -------- | ---------- |
24
+ MARKDOWN
25
+
26
+ UNKNOWN_FILES_MESSAGE = <<MARKDOWN
27
+
28
+ These files couldn't be categorised, so Danger was unable to suggest a reviewer.
29
+ Please consider creating a merge request to
30
+ [add support](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/danger/helper.rb)
31
+ for them.
32
+ MARKDOWN
33
+
34
+ NO_REVIEWER = 'No reviewer available'.freeze
35
+ NO_MAINTAINER = 'No maintainer available'.freeze
36
+
37
+ def spin_for_category(team, project, category, branch_name)
38
+ puts "team = #{team}\n project=#{project}\n category=#{category}\n branch_name=#{branch_name}\n"
39
+ random = roulette.new_random(branch_name)
40
+ labels = gitlab.mr_labels
41
+ puts "labels = #{labels}"
42
+
43
+ reviewers, traintainers, maintainers =
44
+ %i[reviewer? traintainer? maintainer?].map do |kind|
45
+ team.select do |member|
46
+ member.public_send(kind, project, category, labels) # rubocop:disable GitlabSecurity/PublicSend
47
+ end
48
+ end
49
+
50
+ # TODO: take CODEOWNERS into account?
51
+ # https://gitlab.com/gitlab-org/gitlab/issues/26723
52
+
53
+ puts "reviewers = #{reviewers}\n maintainers=#{maintainers}\n"
54
+ # Make traintainers have triple the chance to be picked as a reviewer
55
+ reviewer = roulette.spin_for_person(reviewers + traintainers + traintainers, random: random)
56
+ maintainer = roulette.spin_for_person(maintainers, random: random)
57
+
58
+ "| #{helper.label_for_category(category)} | #{reviewer&.markdown_name || NO_REVIEWER} | #{maintainer&.markdown_name || NO_MAINTAINER} |"
59
+ end
60
+
61
+ changes = helper.changes_by_category
62
+
63
+ # Ignore any files that are known but uncategorized. Prompt for any unknown files
64
+ changes.delete(:none)
65
+ categories = changes.keys - [:unknown]
66
+
67
+ # Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
68
+ categories << :database if gitlab.mr_labels.include?('database') && !categories.include?(:database)
69
+
70
+ # Single codebase MRs are reviewed using a slightly different process, so we
71
+ # disable the review roulette for such MRs.
72
+ # CSS Clean up MRs are reviewed using a slightly different process, so we
73
+ # disable the review roulette for such MRs.
74
+ if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_labels.include?('CSS cleanup')
75
+ # Strip leading and trailing CE/EE markers
76
+ canonical_branch_name =
77
+ roulette.canonical_branch_name(gitlab.mr_json['source_branch'])
78
+
79
+ team =
80
+ begin
81
+ roulette.project_team(helper.project_name)
82
+ rescue => err
83
+ warn("Reviewer roulette failed to load team data: #{err.message}")
84
+ []
85
+ end
86
+
87
+ project = helper.project_name
88
+ unknown = changes.fetch(:unknown, [])
89
+
90
+ rows = categories.map { |category| spin_for_category(team, project, category, canonical_branch_name) }
91
+
92
+ markdown(MESSAGE)
93
+ markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
94
+ markdown(UNKNOWN_FILES_MESSAGE + helper.markdown_list(unknown)) unless unknown.empty?
95
+ end
@@ -0,0 +1,36 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "gitlab_roulette/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gitlab_roulette"
7
+ spec.version = GitlabRoulette::VERSION
8
+ spec.authors = ["Andrei Merfu"]
9
+ spec.email = ["andreimerfu@outlook.com"]
10
+
11
+ spec.summary = %q{GitlabRoulette adds a reviewer roulette to your self hosted Gitlab repository}
12
+ spec.description = %q{GitlabRoulette choose reviewer and a maintainer for your merge request automatically}
13
+ spec.homepage = "https://github.com/andreimerfu/gitlab_roulette.git"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/andreimerfu/gitlab_roulette.git"
20
+ spec.metadata["changelog_uri"] = "https://github.com/andreimerfu/gitlab_roulette.git"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_development_dependency "bundler", "~> 2.0"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "danger-gitlab", "~> 7.0.0"
34
+ spec.add_development_dependency "danger", "~> 6.1.0"
35
+ spec.add_development_dependency "faraday", "~> 0.17.1"
36
+ end
@@ -0,0 +1,15 @@
1
+ require "gitlab_roulette/version"
2
+ require "faraday"
3
+ require "ostruct"
4
+ require "json"
5
+
6
+ require "danger"
7
+
8
+ require "gitlab_roulette/danger/helper"
9
+ require "gitlab_roulette/danger/request_helper"
10
+ require "gitlab_roulette/danger/roulette"
11
+ require "gitlab_roulette/danger/teammate"
12
+
13
+ module GitlabRoulette
14
+ class Error < StandardError; end
15
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'teammate'
4
+
5
+ module Gitlab
6
+ module Danger
7
+ module Helper
8
+ RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
9
+
10
+ # Returns a list of all files that have been added, modified or renamed.
11
+ # `git.modified_files` might contain paths that already have been renamed,
12
+ # so we need to remove them from the list.
13
+ #
14
+ # Considering these changes:
15
+ #
16
+ # - A new_file.rb
17
+ # - D deleted_file.rb
18
+ # - M modified_file.rb
19
+ # - R renamed_file_before.rb -> renamed_file_after.rb
20
+ #
21
+ # it will return
22
+ # ```
23
+ # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
24
+ # ```
25
+ #
26
+ # @return [Array<String>]
27
+ def all_changed_files
28
+ Set.new
29
+ .merge(git.added_files.to_a)
30
+ .merge(git.modified_files.to_a)
31
+ .merge(git.renamed_files.map { |x| x[:after] })
32
+ .subtract(git.renamed_files.map { |x| x[:before] })
33
+ .to_a
34
+ .sort
35
+ end
36
+
37
+ def ee?
38
+ # Support former project name for `dev` and support local Danger run
39
+ %w[gitlab gitlab-ee].include?(ENV['CI_PROJECT_NAME']) || Dir.exist?('../../ee')
40
+ end
41
+
42
+ def gitlab_helper
43
+ # Unfortunately the following does not work:
44
+ # - respond_to?(:gitlab)
45
+ # - respond_to?(:gitlab, true)
46
+ gitlab
47
+ rescue NoMethodError
48
+ nil
49
+ end
50
+
51
+ def release_automation?
52
+ gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
53
+ end
54
+
55
+ def project_name
56
+ ENV["CI_PROJECT_NAME"]
57
+ end
58
+
59
+ def markdown_list(items)
60
+ list = items.map { |item| "* `#{item}`" }.join("\n")
61
+
62
+ if items.size > 10
63
+ "\n<details>\n\n#{list}\n\n</details>\n"
64
+ else
65
+ list
66
+ end
67
+ end
68
+
69
+ # @return [Hash<String,Array<String>>]
70
+ def changes_by_category
71
+ all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
72
+ hash[category_for_file(file)] << file
73
+ end
74
+ end
75
+
76
+ # Determines the category a file is in, e.g., `:frontend` or `:backend`
77
+ # @return[Symbol]
78
+ def category_for_file(file)
79
+ _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
80
+
81
+ category || :unknown
82
+ end
83
+
84
+ # Returns the GFM for a category label, making its best guess if it's not
85
+ # a category we know about.
86
+ #
87
+ # @return[String]
88
+ def label_for_category(category)
89
+ CATEGORY_LABELS.fetch(category, "~#{category}")
90
+ end
91
+
92
+ CATEGORY_LABELS = {
93
+ docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
94
+ none: "",
95
+ qa: "~QA",
96
+ test: "~test ~Quality for `spec/features/*`",
97
+ engineering_productivity: '~"Engineering Productivity" for CI, Danger'
98
+ }.freeze
99
+ CATEGORIES = {
100
+ %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
101
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
102
+
103
+ %r{\A(ee/)?app/(assets|views)/} => :frontend,
104
+ %r{\A(ee/)?public/} => :frontend,
105
+ %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
106
+ %r{\A(ee/)?vendor/assets/} => :frontend,
107
+ %r{\A(ee/)?scripts/frontend/} => :frontend,
108
+ %r{(\A|/)(
109
+ \.babelrc |
110
+ \.eslintignore |
111
+ \.eslintrc(\.yml)? |
112
+ \.nvmrc |
113
+ \.prettierignore |
114
+ \.prettierrc |
115
+ \.scss-lint.yml |
116
+ \.stylelintrc |
117
+ \.haml-lint.yml |
118
+ \.haml-lint_todo.yml |
119
+ babel\.config\.js |
120
+ jest\.config\.js |
121
+ karma\.config\.js |
122
+ webpack\.config\.js |
123
+ package\.json |
124
+ yarn\.lock |
125
+ \.gitlab/ci/frontend\.gitlab-ci\.yml
126
+ )\z}x => :frontend,
127
+
128
+ %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
129
+ %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
130
+ %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
131
+ %r{\Arubocop/cop/migration(/|\.rb)} => :database,
132
+
133
+ %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
134
+ %r{Dangerfile\z} => :engineering_productivity,
135
+ %r{\A(ee/)?(danger/|lib/gitlab/danger/)} => :engineering_productivity,
136
+ %r{\A(ee/)?scripts/} => :engineering_productivity,
137
+
138
+ %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
139
+ %r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend,
140
+ %r{\A(ee/)?spec/features/} => :test,
141
+ %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
142
+ %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
143
+ %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
144
+ %r{\A(Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
145
+ %r{\A[A-Z_]+_VERSION\z} => :backend,
146
+ %r{\A\.rubocop(_todo)?\.yml\z} => :backend,
147
+
148
+ %r{\A(ee/)?qa/} => :qa,
149
+
150
+ # Files that don't fit into any category are marked with :none
151
+ %r{\A(ee/)?changelogs/} => :none,
152
+ %r{\Alocale/gitlab\.pot\z} => :none,
153
+
154
+ # Fallbacks in case the above patterns miss anything
155
+ %r{\.rb\z} => :backend,
156
+ %r{(
157
+ \.(md|txt)\z |
158
+ \.markdownlint\.json
159
+ )}x => :none, # To reinstate roulette for documentation, set to `:docs`.
160
+ %r{\.js\z} => :frontend
161
+ }.freeze
162
+
163
+ def new_teammates(usernames)
164
+ usernames.map { |u| Gitlab::Danger::Teammate.new('username' => u) }
165
+ end
166
+
167
+ def missing_database_labels(current_mr_labels)
168
+ labels = if has_database_scoped_labels?(current_mr_labels)
169
+ ['database']
170
+ else
171
+ ['database', 'database::review pending']
172
+ end
173
+
174
+ labels - current_mr_labels
175
+ end
176
+
177
+ private
178
+
179
+ def has_database_scoped_labels?(current_mr_labels)
180
+ current_mr_labels.any? { |label| label.start_with?('database::') }
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module Gitlab
7
+ module Danger
8
+ module RequestHelper
9
+ HTTPError = Class.new(RuntimeError)
10
+
11
+ # @param [String] url
12
+ def self.http_get_json(url)
13
+ rsp = Net::HTTP.get_response(URI.parse(url))
14
+
15
+ unless rsp.is_a?(Net::HTTPOK)
16
+ raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
17
+ end
18
+
19
+ JSON.parse(rsp.body)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'teammate'
4
+
5
+ module Gitlab
6
+ module Danger
7
+ module Roulette
8
+ ROULETTE_DATA_URL = ENV["GITLAB_ROULETTE_URL"]
9
+
10
+ # Looks up the current list of GitLab team members and parses it into a
11
+ # useful form
12
+ #
13
+ # @return [Array<Teammate>]
14
+ def team
15
+ @team ||=
16
+ begin
17
+ if ROULETTE_DATA_URL.include?("https") || ROULETTE_DATA_URL.include?("http")
18
+ data = Gitlab::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL)
19
+ else
20
+ file = File.read(ROULETTE_DATA_URL)
21
+
22
+ data = JSON.parse(file)
23
+ end
24
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
25
+ rescue JSON::ParserError
26
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
27
+ end
28
+ end
29
+
30
+ # Like +team+, but only returns teammates in the current project, based on
31
+ # project_name.
32
+ #
33
+ # @return [Array<Teammate>]
34
+ def project_team(project_name)
35
+ team.select { |member| member.in_project?(project_name) }
36
+ end
37
+
38
+ def canonical_branch_name(branch_name)
39
+ branch_name.gsub(/^[ce]e-|-[ce]e$/, '')
40
+ end
41
+
42
+ def new_random(seed)
43
+ Random.new(Digest::MD5.hexdigest(seed).to_i(16))
44
+ end
45
+
46
+ # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
47
+ # selection will change on next spin
48
+ # @param [Array<Teammate>] people
49
+ def spin_for_person(people, random:)
50
+ people.shuffle(random: random)
51
+ .find(&method(:valid_person?))
52
+ end
53
+
54
+ private
55
+
56
+ # @param [Teammate] person
57
+ # @return [Boolean]
58
+ def valid_person?(person)
59
+ !mr_author?(person) && person.available?
60
+ end
61
+
62
+ # @param [Teammate] person
63
+ # @return [Boolean]
64
+ def mr_author?(person)
65
+ person.username == gitlab.mr_author
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module Gitlab
6
+ module Danger
7
+ class Teammate
8
+ attr_reader :name, :username, :role, :projects
9
+
10
+ def initialize(options = {})
11
+ @username = options['username']
12
+ @name = options['name'] || @username
13
+ @role = options['role']
14
+ @projects = options['projects']
15
+ end
16
+
17
+ def markdown_name
18
+ "[#{name}](#{gitlab_host}/#{username}) (`@#{username}`)"
19
+ end
20
+
21
+ def in_project?(name)
22
+ projects&.has_key?(name)
23
+ end
24
+
25
+ # Traintainers also count as reviewers
26
+ def reviewer?(project, category, labels)
27
+ has_capability?(project, category, :reviewer, labels) ||
28
+ traintainer?(project, category, labels)
29
+ end
30
+
31
+ def traintainer?(project, category, labels)
32
+ has_capability?(project, category, :trainee_maintainer, labels)
33
+ end
34
+
35
+ def maintainer?(project, category, labels)
36
+ has_capability?(project, category, :maintainer, labels)
37
+ end
38
+
39
+ def status
40
+ api_endpoint = "#{gitlab_host}/api/v4/users/#{CGI.escape(username)}/status"
41
+ @status ||= Gitlab::Danger::RequestHelper.http_get_json(api_endpoint)
42
+ rescue Gitlab::Danger::RequestHelper::HTTPError, JSON::ParserError
43
+ nil # better no status than a crashing Danger
44
+ end
45
+
46
+ # @return [Boolean]
47
+ def available?
48
+ !out_of_office? && has_capacity?
49
+ end
50
+
51
+ private
52
+
53
+ def gitlab_host
54
+ @gitlab_host ||= ENV["GITLAB_HOST"]
55
+ end
56
+
57
+ # @return [Boolean]
58
+ def out_of_office?
59
+ status&.dig("message")&.match?(/OOO/i) || false
60
+ end
61
+
62
+ # @return [Boolean]
63
+ def has_capacity?
64
+ status&.dig("emoji") != 'red_circle'
65
+ end
66
+
67
+ def has_capability?(project, category, kind, labels)
68
+ case category
69
+ when :test
70
+ area = role[/Software Engineer in Test(?:.*?, (\w+))/, 1]
71
+
72
+ area && labels.any?("devops::#{area.downcase}") if kind == :reviewer
73
+ when :engineering_productivity
74
+ return false unless role[/Engineering Productivity/]
75
+ return true if kind == :reviewer
76
+
77
+ capabilities(project).include?("#{kind} backend")
78
+ else
79
+ capabilities(project).include?("#{kind} #{category}")
80
+ end
81
+ end
82
+
83
+ def capabilities(project)
84
+ Array(projects.fetch(project, []))
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GitlabDanger
4
+ LOCAL_RULES ||= %w[
5
+ changes_size
6
+ gemfile
7
+ documentation
8
+ frozen_string
9
+ duplicate_yarn_dependencies
10
+ prettier
11
+ eslint
12
+ database
13
+ commit_messages
14
+ ].freeze
15
+
16
+ CI_ONLY_RULES ||= %w[
17
+ metadata
18
+ changelog
19
+ specs
20
+ roulette
21
+ single_codebase
22
+ gitlab_ui_wg
23
+ ce_ee_vue_templates
24
+ ].freeze
25
+
26
+ MESSAGE_PREFIX = '==>'.freeze
27
+
28
+ attr_reader :gitlab_danger_helper
29
+
30
+ def initialize(gitlab_danger_helper)
31
+ @gitlab_danger_helper = gitlab_danger_helper
32
+ end
33
+
34
+ def self.local_warning_message
35
+ "#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(', ')}"
36
+ end
37
+
38
+ def self.success_message
39
+ "#{MESSAGE_PREFIX} No Danger rule violations!"
40
+ end
41
+
42
+ def rule_names
43
+ ci? ? LOCAL_RULES | CI_ONLY_RULES : LOCAL_RULES
44
+ end
45
+
46
+ def html_link(str)
47
+ self.ci? ? gitlab_danger_helper.html_link(str) : str
48
+ end
49
+
50
+ def ci?
51
+ !gitlab_danger_helper.nil?
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module GitlabRoulette
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitlab_roulette
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrei Merfu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: danger-gitlab
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 7.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 7.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: danger
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 6.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 6.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.17.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.17.1
83
+ description: GitlabRoulette choose reviewer and a maintainer for your merge request
84
+ automatically
85
+ email:
86
+ - andreimerfu@outlook.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - CODE_OF_CONDUCT.md
93
+ - Dangerfile
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - danger/plugins/helper.rb
102
+ - danger/plugins/roulette.rb
103
+ - danger/roulette/Dangerfile
104
+ - gitlab_roulette.gemspec
105
+ - lib/gitlab_roulette.rb
106
+ - lib/gitlab_roulette/danger/helper.rb
107
+ - lib/gitlab_roulette/danger/request_helper.rb
108
+ - lib/gitlab_roulette/danger/roulette.rb
109
+ - lib/gitlab_roulette/danger/teammate.rb
110
+ - lib/gitlab_roulette/gitlab_danger.rb
111
+ - lib/gitlab_roulette/version.rb
112
+ homepage: https://github.com/andreimerfu/gitlab_roulette.git
113
+ licenses:
114
+ - MIT
115
+ metadata:
116
+ allowed_push_host: https://rubygems.org
117
+ homepage_uri: https://github.com/andreimerfu/gitlab_roulette.git
118
+ source_code_uri: https://github.com/andreimerfu/gitlab_roulette.git
119
+ changelog_uri: https://github.com/andreimerfu/gitlab_roulette.git
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.0.3
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: GitlabRoulette adds a reviewer roulette to your self hosted Gitlab repository
139
+ test_files: []