summaryrefslogtreecommitdiff
path: root/lib/tasks/gitlab/graphql.rake
blob: e4eb4604138e4a54b45e29681bf354110b040873 (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# frozen_string_literal: true

return if Rails.env.production?

require 'graphql/rake_task'

namespace :gitlab do
  OUTPUT_DIR = Rails.root.join("doc/api/graphql/reference")
  TEMPLATES_DIR = 'lib/gitlab/graphql/docs/templates/'

  # Make all feature flags enabled so that all feature flag
  # controlled fields are considered visible and are output.
  # Also avoids pipeline failures in case developer
  # dumps schema with flags disabled locally before pushing
  task enable_feature_flags: :environment do
    class Feature
      def self.enabled?(*args)
        true
      end
    end
  end

  # Defines tasks for dumping the GraphQL schema:
  # - gitlab:graphql:schema:dump
  # - gitlab:graphql:schema:idl
  # - gitlab:graphql:schema:json
  GraphQL::RakeTask.new(
    schema_name: 'GitlabSchema',
    dependencies: [:environment, :enable_feature_flags],
    directory: OUTPUT_DIR,
    idl_outfile: "gitlab_schema.graphql",
    json_outfile: "gitlab_schema.json"
  )

  namespace :graphql do
    desc 'GitLab | GraphQL | Analyze queries'
    task analyze: [:environment, :enable_feature_flags] do |t, args|
      queries = if args.to_a.present?
                  args.to_a.flat_map { |path| Gitlab::Graphql::Queries.find(path) }
                else
                  Gitlab::Graphql::Queries.all
                end

      queries.each do |defn|
        $stdout.puts defn.file
        summary, errs = defn.validate(GitlabSchema)

        if summary == :client_query
          $stdout.puts " - client query"
        elsif errs.present?
          $stdout.puts " - invalid query".color(:red)
        else
          complexity = defn.complexity(GitlabSchema)
          color = case complexity
                  when 0..GitlabSchema::DEFAULT_MAX_COMPLEXITY
                    :green
                  when GitlabSchema::DEFAULT_MAX_COMPLEXITY..GitlabSchema::AUTHENTICATED_COMPLEXITY
                    :yellow
                  when GitlabSchema::AUTHENTICATED_COMPLEXITY..GitlabSchema::ADMIN_COMPLEXITY
                    :orange
                  else
                    :red
                  end

          $stdout.puts " - complexity: #{complexity}".color(color)
        end

        $stdout.puts ""
      end
    end

    desc 'GitLab | GraphQL | Validate queries'
    task validate: [:environment, :enable_feature_flags] do |t, args|
      queries = if args.to_a.present?
                  args.to_a.flat_map { |path| Gitlab::Graphql::Queries.find(path) }
                else
                  Gitlab::Graphql::Queries.all
                end

      failed = queries.flat_map do |defn|
        summary, errs = defn.validate(GitlabSchema)

        case summary
        when :client_query
          warn("SKIP  #{defn.file}: client query")
        else
          warn("#{'OK'.color(:green)}    #{defn.file}") if errs.empty?
          errs.each do |err|
            warn(<<~MSG)
            #{'ERROR'.color(:red)} #{defn.file}: #{err.message} (at #{err.path.join('.')})
            MSG
          end
        end

        errs.empty? ? [] : [defn.file]
      end

      if failed.present?
        format_output(
          "#{failed.count} GraphQL #{'query'.pluralize(failed.count)} out of #{queries.count} failed validation:",
          *failed.map do |name|
            known_failure = Gitlab::Graphql::Queries.known_failure?(name)
            "- #{name}" + (known_failure ? ' (known failure)' : '')
          end
        )
        abort unless failed.all? { |name| Gitlab::Graphql::Queries.known_failure?(name) }
      end
    end

    desc 'GitLab | GraphQL | Generate GraphQL docs'
    task compile_docs: [:environment, :enable_feature_flags] do
      renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options)

      renderer.write

      puts "Documentation compiled."
    end

    desc 'GitLab | GraphQL | Check if GraphQL docs are up to date'
    task check_docs: [:environment, :enable_feature_flags] do
      renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options)

      doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md'))

      if doc == renderer.contents
        puts "GraphQL documentation is up to date"
      else
        format_output('GraphQL documentation is outdated! Please update it by running `bundle exec rake gitlab:graphql:compile_docs`.')
        abort
      end
    end

    desc 'GitLab | GraphQL | Check if GraphQL schemas are up to date'
    task check_schema: [:environment, :enable_feature_flags] do
      idl_doc = File.read(Rails.root.join(OUTPUT_DIR, 'gitlab_schema.graphql'))
      json_doc = File.read(Rails.root.join(OUTPUT_DIR, 'gitlab_schema.json'))

      if idl_doc == GitlabSchema.to_definition && json_doc == GitlabSchema.to_json
        puts "GraphQL schema is up to date"
      else
        format_output('GraphQL schema is outdated! Please update it by running `bundle exec rake gitlab:graphql:schema:dump`.')
        abort
      end
    end
  end
end

def render_options
  {
    output_dir: OUTPUT_DIR,
    template: Rails.root.join(TEMPLATES_DIR, 'default.md.haml')
  }
end

def format_output(*strs)
  heading = '#' * 10
  puts heading
  puts '#'
  strs.each { |str| puts "# #{str}" }
  puts '#'
  puts heading
end