summaryrefslogtreecommitdiff
path: root/tooling/lib/tooling/mappings/graphql_base_type_mappings.rb
blob: cd8b55d5820a212c9da5186a4dbe3cc5c8316157 (plain)
1
2
3
4
5
6
7
8
9
10
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
44
45
46
47
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# frozen_string_literal: true

require 'active_support/inflector'

require_relative 'base'
require_relative '../../../../lib/gitlab_edition'

# If a GraphQL type class changed, we try to identify the other GraphQL types that potentially include this type.
module Tooling
  module Mappings
    class GraphqlBaseTypeMappings < Base
      # Checks for the implements keyword, and graphql_base_types the class name
      GRAPHQL_IMPLEMENTS_REGEXP = /implements[( ]([\w:]+)[)]?$/

      # GraphQL types are a bit scattered in the codebase based on the edition.
      #
      # Also, a higher edition is able to include lower editions.
      #   e.g. EE can include FOSS GraphQL types, and JH can include all GraphQL types
      GRAPHQL_TYPES_FOLDERS_FOSS = ['app/graphql/types'].freeze
      GRAPHQL_TYPES_FOLDERS_EE   = GRAPHQL_TYPES_FOLDERS_FOSS + ['ee/app/graphql/types', 'ee/app/graphql/ee/types']
      GRAPHQL_TYPES_FOLDERS_JH   = GRAPHQL_TYPES_FOLDERS_EE + ['jh/app/graphql/types', 'jh/app/graphql/jh/types']
      GRAPHQL_TYPES_FOLDERS      = {
        nil => GRAPHQL_TYPES_FOLDERS_FOSS,
        'ee' => GRAPHQL_TYPES_FOLDERS_EE,
        'jh' => GRAPHQL_TYPES_FOLDERS_JH
      }.freeze

      def initialize(changes_file, matching_tests_paths)
        @matching_tests_paths = matching_tests_paths
        @changed_files        = read_array_from_file(changes_file)
      end

      def execute
        # We go through the available editions when searching for base types
        #
        # `nil` is the FOSS edition
        matching_graphql_tests = ([nil] + ::GitlabEdition.extensions).flat_map do |edition|
          hierarchy = types_hierarchies[edition]

          filter_files.flat_map do |graphql_file|
            children_types = hierarchy[filename_to_class_name(graphql_file)]
            next if children_types.empty?

            # We find the specs for the children GraphQL types that are implementing the current GraphQL Type
            children_types.map { |filename| filename_to_spec_filename(filename) }
          end
        end.compact.uniq

        write_array_to_file(matching_tests_paths, matching_graphql_tests)
      end

      def filter_files
        changed_files.select do |filename|
          filename.start_with?(*GRAPHQL_TYPES_FOLDERS.values.flatten.uniq) &&
            filename.end_with?('.rb') &&
            File.exist?(filename)
        end
      end

      # Regroup all GraphQL types (by edition) that are implementing another GraphQL type.
      #
      # The key is the type that is being implemented (e.g. NoteableInterface, TodoableInterface below)
      # The value is an array of GraphQL type files that are implementing those types.
      #
      # Example output:
      #
      # {
      #   nil => {
      #     "NoteableInterface" => [
      #       "app/graphql/types/alert_management/alert_type.rb",
      #       "app/graphql/types/design_management/design_type.rb"
      #     , "TodoableInterface" => [...]
      #   },
      #   "ee" => {
      #     "NoteableInterface" => [
      #       "app/graphql/types/alert_management/alert_type.rb",
      #       "app/graphql/types/design_management/design_type.rb",
      #       "ee/app/graphql/types/epic_type.rb"],
      #    "TodoableInterface"=> [...]
      #   }
      # }
      def types_hierarchies
        return @types_hierarchies if @types_hierarchies

        @types_hierarchies = {}
        GRAPHQL_TYPES_FOLDERS.each_key do |edition|
          @types_hierarchies[edition] = Hash.new { |h, k| h[k] = [] }

          graphql_files_for_edition_glob = File.join("{#{GRAPHQL_TYPES_FOLDERS[edition].join(',')}}", '**', '*.rb')
          Dir[graphql_files_for_edition_glob].each do |graphql_file|
            graphql_base_types = File.read(graphql_file).scan(GRAPHQL_IMPLEMENTS_REGEXP)
            next if graphql_base_types.empty?

            graphql_base_classes = graphql_base_types.flatten.map { |class_name| class_name.split('::').last }
            graphql_base_classes.each do |graphql_base_class|
              @types_hierarchies[edition][graphql_base_class] += [graphql_file]
            end
          end
        end

        @types_hierarchies
      end

      def filename_to_class_name(filename)
        File.basename(filename, '.*').camelize
      end

      def filename_to_spec_filename(filename)
        spec_file = filename.sub('app', 'spec').sub('.rb', '_spec.rb')

        return spec_file if File.exist?(spec_file)
      end

      private

      attr_reader :changed_files, :matching_tests_paths
    end
  end
end