helper.rb 5 KB
Newer Older
Nick Thomas's avatar
Nick Thomas committed
1 2 3 4 5 6 7 8 9
# frozen_string_literal: true
require 'net/http'
require 'json'

require_relative 'teammate'

module Gitlab
  module Danger
    module Helper
10
      RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
Nick Thomas's avatar
Nick Thomas committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
      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

44 45 46 47
      def release_automation?
        gitlab.mr_author == RELEASE_TOOLS_BOT
      end

Nick Thomas's avatar
Nick Thomas committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
      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 = {
102
        docs: "~Documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
103
        none: "",
Nick Thomas's avatar
Nick Thomas committed
104 105 106
        qa: "~QA"
      }.freeze
      CATEGORIES = {
107 108
        %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
        %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
Nick Thomas's avatar
Nick Thomas committed
109 110 111

        %r{\A(ee/)?app/(assets|views)/} => :frontend,
        %r{\A(ee/)?public/} => :frontend,
112
        %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
Nick Thomas's avatar
Nick Thomas committed
113
        %r{\A(ee/)?vendor/assets/} => :frontend,
114
        %r{\Ascripts/frontend/} => :frontend,
115
        %r{(\A|/)(
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
          \.babelrc |
          \.eslintignore |
          \.eslintrc(\.yml)? |
          \.nvmrc |
          \.prettierignore |
          \.prettierrc |
          \.scss-lint.yml |
          \.stylelintrc |
          babel\.config\.js |
          jest\.config\.js |
          karma\.config\.js |
          webpack\.config\.js |
          package\.json |
          yarn\.lock
        )\z}x => :frontend,
Nick Thomas's avatar
Nick Thomas committed
131 132 133

        %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
        %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
134
        %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
Nick Thomas's avatar
Nick Thomas committed
135 136
        %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
        %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
137
        %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
Nick Thomas's avatar
Nick Thomas committed
138 139 140 141 142
        %r{\A[A-Z_]+_VERSION\z} => :backend,

        %r{\A(ee/)?db/} => :database,
        %r{\A(ee/)?qa/} => :qa,

143 144
        # Files that don't fit into any category are marked with :none
        %r{\A(ee/)?changelogs/} => :none,
Nick Thomas's avatar
Nick Thomas committed
145
        %r{\Alocale/gitlab\.pot\z} => :none,
146

Nick Thomas's avatar
Nick Thomas committed
147 148
        # Fallbacks in case the above patterns miss anything
        %r{\.rb\z} => :backend,
149
        %r{\.(md|txt)\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
Nick Thomas's avatar
Nick Thomas committed
150 151 152 153 154
        %r{\.js\z} => :frontend
      }.freeze
    end
  end
end