summaryrefslogtreecommitdiff
path: root/lib/gitlab/danger/helper.rb
diff options
context:
space:
mode:
authorNick Thomas <nick@gitlab.com>2019-02-05 17:16:18 +0000
committerNick Thomas <nick@gitlab.com>2019-02-13 16:41:28 +0000
commit77b2ecd2b1de3f66c3d9ebd5e58fedafb17ea606 (patch)
treeeb360eb0264b50370ff3ad48910ad437183bc3b6 /lib/gitlab/danger/helper.rb
parent235fe67bdbc7f177a6f4b213bb1780f043944246 (diff)
downloadgitlab-ce-77b2ecd2b1de3f66c3d9ebd5e58fedafb17ea606.tar.gz
Reviewer roulette via Danger56087-danger-roulette
Make danger pick reviewers and maintainers at random, for feontend, backend, database, etc, changes, whenever files belonging to those teams get changed.
Diffstat (limited to 'lib/gitlab/danger/helper.rb')
-rw-r--r--lib/gitlab/danger/helper.rb131
1 files changed, 131 insertions, 0 deletions
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
new file mode 100644
index 00000000000..80760e41c7d
--- /dev/null
+++ b/lib/gitlab/danger/helper.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+require 'net/http'
+require 'json'
+
+require_relative 'teammate'
+
+module Gitlab
+ module Danger
+ module Helper
+ ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze
+
+ # Returns a list of all files that have been added, modified or renamed.
+ # `git.modified_files` might contain paths that already have been renamed,
+ # so we need to remove them from the list.
+ #
+ # Considering these changes:
+ #
+ # - A new_file.rb
+ # - D deleted_file.rb
+ # - M modified_file.rb
+ # - R renamed_file_before.rb -> renamed_file_after.rb
+ #
+ # it will return
+ # ```
+ # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
+ # ```
+ #
+ # @return [Array<String>]
+ def all_changed_files
+ Set.new
+ .merge(git.added_files.to_a)
+ .merge(git.modified_files.to_a)
+ .merge(git.renamed_files.map { |x| x[:after] })
+ .subtract(git.renamed_files.map { |x| x[:before] })
+ .to_a
+ .sort
+ end
+
+ def ee?
+ ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
+ end
+
+ def project_name
+ ee? ? 'gitlab-ee' : 'gitlab-ce'
+ end
+
+ # Looks up the current list of GitLab team members and parses it into a
+ # useful form
+ #
+ # @return [Array<Teammate>]
+ def team
+ @team ||=
+ begin
+ rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
+ raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
+ rsp.is_a?(Net::HTTPSuccess)
+
+ data = JSON.parse(rsp.body)
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
+ rescue JSON::ParserError
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
+ end
+ end
+
+ # Like +team+, but only returns teammates in the current project, based on
+ # project_name.
+ #
+ # @return [Array<Teammate>]
+ def project_team
+ team.select { |member| member.in_project?(project_name) }
+ end
+
+ # @return [Hash<String,Array<String>>]
+ def changes_by_category
+ all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
+ hash[category_for_file(file)] << file
+ end
+ end
+
+ # Determines the category a file is in, e.g., `:frontend` or `:backend`
+ # @return[Symbol]
+ def category_for_file(file)
+ _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
+
+ category || :unknown
+ end
+
+ # Returns the GFM for a category label, making its best guess if it's not
+ # a category we know about.
+ #
+ # @return[String]
+ def label_for_category(category)
+ CATEGORY_LABELS.fetch(category, "~#{category}")
+ end
+
+ CATEGORY_LABELS = {
+ docs: "~Documentation",
+ qa: "~QA"
+ }.freeze
+
+ # rubocop:disable Style/RegexpLiteral
+ CATEGORIES = {
+ %r{\Adoc/} => :docs,
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
+
+ %r{\A(ee/)?app/(assets|views)/} => :frontend,
+ %r{\A(ee/)?public/} => :frontend,
+ %r{\A(ee/)?spec/javascripts/} => :frontend,
+ %r{\A(ee/)?vendor/assets/} => :frontend,
+ %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
+
+ %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
+ %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/(?!javascripts)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
+ %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
+ %r{\A[A-Z_]+_VERSION\z} => :backend,
+
+ %r{\A(ee/)?db/} => :database,
+ %r{\A(ee/)?qa/} => :qa,
+
+ # Fallbacks in case the above patterns miss anything
+ %r{\.rb\z} => :backend,
+ %r{\.(md|txt)\z} => :docs,
+ %r{\.js\z} => :frontend
+ }.freeze
+ # rubocop:enable Style/RegexpLiteral
+ end
+ end
+end