diff options
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r-- | lib/gitlab/graphql/authorize/authorize_resource.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/graphql/deprecation.rb | 149 | ||||
-rw-r--r-- | lib/gitlab/graphql/deprecations.rb | 45 | ||||
-rw-r--r-- | lib/gitlab/graphql/deprecations/deprecation.rb | 151 | ||||
-rw-r--r-- | lib/gitlab/graphql/markdown_field.rb | 2 | ||||
-rw-r--r-- | lib/gitlab/graphql/queries.rb | 5 |
6 files changed, 202 insertions, 154 deletions
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb index 884fc85c4ec..983bdb9c0a2 100644 --- a/lib/gitlab/graphql/authorize/authorize_resource.rb +++ b/lib/gitlab/graphql/authorize/authorize_resource.rb @@ -67,8 +67,8 @@ module Gitlab self.class.authorization.ok?(object, current_user) end - def raise_resource_not_available_error!(*args) - self.class.raise_resource_not_available_error!(*args) + def raise_resource_not_available_error!(...) + self.class.raise_resource_not_available_error!(...) end end end diff --git a/lib/gitlab/graphql/deprecation.rb b/lib/gitlab/graphql/deprecation.rb deleted file mode 100644 index 9b17962f9ec..00000000000 --- a/lib/gitlab/graphql/deprecation.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - class Deprecation - REASON_RENAMED = :renamed - REASON_ALPHA = :alpha - - REASONS = { - REASON_RENAMED => 'This was renamed.', - REASON_ALPHA => 'This feature is in Alpha. It can be changed or removed at any time.' - }.freeze - - include ActiveModel::Validations - - validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' } - validates :reason, presence: true - validates :reason, - format: { with: /.*[^.]\z/, message: 'must not end with a period' }, - if: :reason_is_string? - validate :milestone_is_string - validate :reason_known_or_string - - def self.parse(alpha: nil, deprecated: nil) - options = alpha || deprecated - return unless options - - if alpha - raise ArgumentError, '`alpha` and `deprecated` arguments cannot be passed at the same time' \ - if deprecated - - options[:reason] = :alpha - end - - new(**options) - end - - def initialize(reason: nil, milestone: nil, replacement: nil) - @reason = reason.presence - @milestone = milestone.presence - @replacement = replacement.presence - end - - def ==(other) - return false unless other.is_a?(self.class) - - [reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr| - other.send(attr) # rubocop: disable GitlabSecurity/PublicSend - end - end - alias_method :eql, :== - - def markdown(context: :inline) - parts = [ - "#{changed_in_milestone(format: :markdown)}.", - reason_text, - replacement_markdown.then { |r| "Use: #{r}." if r } - ].compact - - case context - when :block - ['WARNING:', *parts].join("\n") - when :inline - parts.join(' ') - end - end - - def replacement_markdown - return unless replacement.present? - return "`#{replacement}`" unless replacement.include?('.') # only fully qualified references can be linked - - "[`#{replacement}`](##{replacement.downcase.tr('.', '')})" - end - - def edit_description(original_description) - @original_description = original_description - return unless original_description - - original_description + description_suffix - end - - def original_description - return unless @original_description - return @original_description if @original_description.ends_with?('.') - - "#{@original_description}." - end - - def deprecation_reason - [ - reason_text, - replacement && "Please use `#{replacement}`.", - "#{changed_in_milestone}." - ].compact.join(' ') - end - - def alpha? - reason == REASON_ALPHA - end - - private - - attr_reader :reason, :milestone, :replacement - - def milestone_is_string - return if milestone.is_a?(String) - - errors.add(:milestone, 'must be a string') - end - - def reason_known_or_string - return if REASONS.key?(reason) - return if reason_is_string? - - errors.add(:reason, 'must be a known reason or a string') - end - - def reason_is_string? - reason.is_a?(String) - end - - def reason_text - @reason_text ||= REASONS[reason] || "#{reason.to_s.strip}." - end - - def description_suffix - " #{changed_in_milestone}: #{reason_text}" - end - - # Returns 'Deprecated in <milestone>' for proper deprecations. - # Retruns 'Introduced in <milestone>' for :alpha deprecations. - # Formatted to markdown or plain format. - def changed_in_milestone(format: :plain) - verb = if alpha? - 'Introduced' - else - 'Deprecated' - end - - case format - when :plain - "#{verb} in #{milestone}" - when :markdown - "**#{verb}** in #{milestone}" - end - end - end - end -end diff --git a/lib/gitlab/graphql/deprecations.rb b/lib/gitlab/graphql/deprecations.rb new file mode 100644 index 00000000000..9cd8462f2e8 --- /dev/null +++ b/lib/gitlab/graphql/deprecations.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Concern for handling GraphQL deprecations. +# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-schema-items +module Gitlab + module Graphql + module Deprecations + extend ActiveSupport::Concern + + included do + attr_accessor :deprecation + end + + def visible?(ctx) + super && ctx[:remove_deprecated] == true ? deprecation.nil? : true + end + + private + + # Set deprecation, mutate the arguments + def init_gitlab_deprecation(kwargs) + if kwargs[:deprecation_reason].present? + raise ArgumentError, <<~ERROR + Use `deprecated` property instead of `deprecation_reason`. See + #{Rails.application.routes.url_helpers.help_page_url('development/api_graphql_styleguide', anchor: 'deprecating-schema-items')} + ERROR + end + + # GitLab allows items to be marked as "alpha", which leverages GraphQL deprecations. + # TODO remove + deprecation_args = kwargs.extract!(:alpha, :deprecated) + + self.deprecation = Deprecation.parse(**deprecation_args) + return unless deprecation + + unless deprecation.valid? + raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" + end + + kwargs[:deprecation_reason] = deprecation.deprecation_reason + kwargs[:description] = deprecation.edit_description(kwargs[:description]) + end + end + end +end diff --git a/lib/gitlab/graphql/deprecations/deprecation.rb b/lib/gitlab/graphql/deprecations/deprecation.rb new file mode 100644 index 00000000000..7f4cea7c635 --- /dev/null +++ b/lib/gitlab/graphql/deprecations/deprecation.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Deprecations + class Deprecation + REASON_RENAMED = :renamed + REASON_ALPHA = :alpha # TODO remove support in this class + + REASONS = { + REASON_RENAMED => 'This was renamed.', + REASON_ALPHA => 'This feature is in Alpha. It can be changed or removed at any time.' + }.freeze + + include ActiveModel::Validations + + validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' } + validates :reason, presence: true + validates :reason, + format: { with: /.*[^.]\z/, message: 'must not end with a period' }, + if: :reason_is_string? + validate :milestone_is_string + validate :reason_known_or_string + + def self.parse(alpha: nil, deprecated: nil) + options = alpha || deprecated + return unless options + + if alpha + raise ArgumentError, '`alpha` and `deprecated` arguments cannot be passed at the same time' \ + if deprecated + + options[:reason] = :alpha + end + + new(**options) + end + + def initialize(reason: nil, milestone: nil, replacement: nil) + @reason = reason.presence + @milestone = milestone.presence + @replacement = replacement.presence + end + + def ==(other) + return false unless other.is_a?(self.class) + + [reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr| + other.send(attr) # rubocop: disable GitlabSecurity/PublicSend + end + end + alias_method :eql, :== + + def markdown(context: :inline) + parts = [ + "#{changed_in_milestone(format: :markdown)}.", + reason_text, + replacement_markdown.then { |r| "Use: #{r}." if r } + ].compact + + case context + when :block + ['WARNING:', *parts].join("\n") + when :inline + parts.join(' ') + end + end + + def replacement_markdown + return unless replacement.present? + return "`#{replacement}`" unless replacement.include?('.') # only fully qualified references can be linked + + "[`#{replacement}`](##{replacement.downcase.tr('.', '')})" + end + + def edit_description(original_description) + @original_description = original_description + return unless original_description + + original_description + description_suffix + end + + def original_description + return unless @original_description + return @original_description if @original_description.ends_with?('.') + + "#{@original_description}." + end + + def deprecation_reason + [ + reason_text, + replacement && "Please use `#{replacement}`.", + "#{changed_in_milestone}." + ].compact.join(' ') + end + + def alpha? + reason == REASON_ALPHA + end + + private + + attr_reader :reason, :milestone, :replacement + + def milestone_is_string + return if milestone.is_a?(String) + + errors.add(:milestone, 'must be a string') + end + + def reason_known_or_string + return if REASONS.key?(reason) + return if reason_is_string? + + errors.add(:reason, 'must be a known reason or a string') + end + + def reason_is_string? + reason.is_a?(String) + end + + def reason_text + @reason_text ||= REASONS[reason] || "#{reason.to_s.strip}." + end + + def description_suffix + " #{changed_in_milestone}: #{reason_text}" + end + + # Returns 'Deprecated in <milestone>' for proper deprecations. + # Retruns 'Introduced in <milestone>' for :alpha deprecations. + # Formatted to markdown or plain format. + def changed_in_milestone(format: :plain) + verb = if alpha? + 'Introduced' + else + 'Deprecated' + end + + case format + when :plain + "#{verb} in #{milestone}" + when :markdown + "**#{verb}** in #{milestone}" + end + end + end + end + end +end diff --git a/lib/gitlab/graphql/markdown_field.rb b/lib/gitlab/graphql/markdown_field.rb index 43dddf4c4bc..f7bd1d43995 100644 --- a/lib/gitlab/graphql/markdown_field.rb +++ b/lib/gitlab/graphql/markdown_field.rb @@ -15,7 +15,7 @@ module Gitlab resolver_method = "#{name}_resolver".to_sym kwargs[:resolver_method] = resolver_method - kwargs[:description] ||= "The GitLab Flavored Markdown rendering of `#{method_name}`" + kwargs[:description] ||= "GitLab Flavored Markdown rendering of `#{method_name}`" # Adding complexity to rendered notes since that could cause queries. kwargs[:complexity] ||= 5 diff --git a/lib/gitlab/graphql/queries.rb b/lib/gitlab/graphql/queries.rb index cf06a2729d9..9cdc84ffaa3 100644 --- a/lib/gitlab/graphql/queries.rb +++ b/lib/gitlab/graphql/queries.rb @@ -91,7 +91,8 @@ module Gitlab end def print_field(field, indent: '') - if skips? && field.directives.any? { |d| d.name == 'client' } + if skips? && + (field.directives.any? { |d| d.name == 'client' || d.name == 'persist' } || field.name == '__persist') skipped = self.class.new(false) skipped.print_node(field) @@ -136,7 +137,7 @@ module Gitlab qs = [query] + all_imports(mode: mode).uniq.sort.map { |p| fragment(p).query } t = qs.join("\n\n").gsub(/\n\n+/, "\n\n") - return t unless /@client/.match?(t) + return t unless /(@client)|(persist)/.match?(t) doc = ::GraphQL.parse(t) printer = ClientFieldRedactor.new |