Commit 778a179f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 3ebdb1e9 0e8208cb
query getDagVisData($projectPath: ID!, $iid: ID!) { query getDagVisData($projectPath: ID!, $iid: ID!) {
project(fullPath: $projectPath) { project(fullPath: $projectPath) {
pipeline(iid: $iid) { pipeline(iid: $iid) {
id
stages { stages {
nodes { nodes {
name name
......
...@@ -82,11 +82,16 @@ module Types ...@@ -82,11 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.' argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
end end
field :instance_statistics_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, field :instance_statistics_measurements,
type: Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true, null: true,
description: 'Get statistics on the instance.', description: 'Get statistics on the instance.',
deprecated: { reason: 'This field was renamed. Use the `usageTrendsMeasurements` field instead', milestone: '13.10' }, resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver,
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver deprecated: {
reason: :renamed,
replacement: 'Query.usageTrendsMeasurements',
milestone: '13.10'
}
field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true, null: true,
......
...@@ -7,10 +7,34 @@ module Types ...@@ -7,10 +7,34 @@ module Types
# Deprecated, as we prefer uppercase enums # Deprecated, as we prefer uppercase enums
# https://gitlab.com/groups/gitlab-org/-/epics/1838 # https://gitlab.com/groups/gitlab-org/-/epics/1838
value 'updated_desc', 'Updated at descending order.', value: :updated_desc, deprecated: { reason: 'Use UPDATED_DESC', milestone: '13.5' } value 'updated_desc', 'Updated at descending order.',
value 'updated_asc', 'Updated at ascending order.', value: :updated_asc, deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' } value: :updated_desc,
value 'created_desc', 'Created at descending order.', value: :created_desc, deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' } deprecated: {
value 'created_asc', 'Created at ascending order.', value: :created_asc, deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' } reason: :renamed,
replacement: 'UPDATED_DESC',
milestone: '13.5'
}
value 'updated_asc', 'Updated at ascending order.',
value: :updated_asc,
deprecated: {
reason: :renamed,
replacement: 'UPDATED_ASC',
milestone: '13.5'
}
value 'created_desc', 'Created at descending order.',
value: :created_desc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_DESC',
milestone: '13.5'
}
value 'created_asc', 'Created at ascending order.',
value: :created_asc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_ASC',
milestone: '13.5'
}
value 'UPDATED_DESC', 'Updated at descending order.', value: :updated_desc value 'UPDATED_DESC', 'Updated at descending order.', value: :updated_desc
value 'UPDATED_ASC', 'Updated at ascending order.', value: :updated_asc value 'UPDATED_ASC', 'Updated at ascending order.', value: :updated_asc
......
...@@ -24,7 +24,7 @@ module Types ...@@ -24,7 +24,7 @@ module Types
description: 'State of the user.' description: 'State of the user.'
field :email, GraphQL::STRING_TYPE, null: true, field :email, GraphQL::STRING_TYPE, null: true,
description: 'User email.', method: :public_email, description: 'User email.', method: :public_email,
deprecated: { reason: 'Use public_email', milestone: '13.7' } deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' }
field :public_email, GraphQL::STRING_TYPE, null: true, field :public_email, GraphQL::STRING_TYPE, null: true,
description: "User's public email." description: "User's public email."
field :avatar_url, GraphQL::STRING_TYPE, null: true, field :avatar_url, GraphQL::STRING_TYPE, null: true,
......
...@@ -30,7 +30,7 @@ class ProtectedBranch < ApplicationRecord ...@@ -30,7 +30,7 @@ class ProtectedBranch < ApplicationRecord
end end
def self.allow_force_push?(project, ref_name) def self.allow_force_push?(project, ref_name)
return false unless ::Feature.enabled?(:allow_force_push_to_protected_branches, project) return false unless ::Feature.enabled?(:allow_force_push_to_protected_branches, project, default_enabled: :yaml)
project.protected_branches.allowing_force_push.matching(ref_name).any? project.protected_branches.allowing_force_push.matching(ref_name).any?
end end
......
- @hide_top_links = true - @hide_top_links = true
- page_title _("Merge Requests") - page_title _("Merge requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username) - @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
= render_dashboard_ultimate_trial(current_user) = render_dashboard_ultimate_trial(current_user)
.page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center .page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center
%h1.page-title= _('Merge Requests') %h1.page-title= _('Merge requests')
- if current_user - if current_user
.page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0 .page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%th %th
= s_("ProtectedBranch|Allowed to push") = s_("ProtectedBranch|Allowed to push")
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project) - if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
%th %th
= s_("ProtectedBranch|Allow force push") = s_("ProtectedBranch|Allow force push")
%span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow force push for all users with push access.'), 'aria-hidden': 'true' } %span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow force push for all users with push access.'), 'aria-hidden': 'true' }
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push:"), class: 'col-md-2 text-left text-md-right' = f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push:"), class: 'col-md-2 text-left text-md-right'
.col-md-10 .col-md-10
= yield :push_access_levels = yield :push_access_levels
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project) - if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
.form-group.row .form-group.row
= f.label :allow_force_push, s_("ProtectedBranch|Allow force push:"), class: 'col-md-2 gl-text-left text-md-right' = f.label :allow_force_push, s_("ProtectedBranch|Allow force push:"), class: 'col-md-2 gl-text-left text-md-right'
.col-md-10 .col-md-10
......
...@@ -33,6 +33,6 @@ ...@@ -33,6 +33,6 @@
%p.small %p.small
= _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence } = _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence }
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project) - if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
%td %td
= render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name } = render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name }
---
title: Allow users to enable force push to protected branches
merge_request: 57053
author:
type: added
---
title: Change the way deprecation information is presented in GraphQL documentation
merge_request: 56864
author:
type: changed
---
title: Revert Ignore default_enabled value in Feature.enabled?
merge_request: 57707
author:
type: fixed
...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323431 ...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323431
milestone: '13.10' milestone: '13.10'
type: development type: development
group: group::source code group: group::source code
default_enabled: false default_enabled: true
---
name: prometheus_metrics_method_instrumentation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4304
rollout_issue_url:
milestone: '10.5'
type: ops
group:
default_enabled: false
---
name: prometheus_metrics_view_instrumentation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4304
rollout_issue_url:
milestone: '10.5'
type: ops
group:
default_enabled: false
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -177,13 +177,12 @@ Deleting a protected branch is allowed only by using the web interface; not from ...@@ -177,13 +177,12 @@ Deleting a protected branch is allowed only by using the web interface; not from
This means that you can't accidentally delete a protected branch from your This means that you can't accidentally delete a protected branch from your
command line or a Git client application. command line or a Git client application.
## Allow force push on protected branches **(FREE SELF)** ## Allow force push on protected branches
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15611) in GitLab 13.10. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15611) in GitLab 13.10 behind a disabled feature flag.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default. > - It's enabled on GitLab.com.
> - It's disabled on GitLab.com. > - It's recommended for production use.
> - It's not recommended for production use. > - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-allow-force-push-on-protected-branches).
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-allow-force-push-on-protected-branches).
WARNING: WARNING:
This feature might not be available to you. Check the **version history** note above for details. This feature might not be available to you. Check the **version history** note above for details.
...@@ -253,8 +252,8 @@ for details about the pipelines security model. ...@@ -253,8 +252,8 @@ for details about the pipelines security model.
## Enable or disable allow force push on protected branches **(FREE SELF)** ## Enable or disable allow force push on protected branches **(FREE SELF)**
Allow force push on protected branches is under development and not ready for Allow force push on protected branches is ready for
production use. It is deployed behind a feature flag that is **disabled by default**. production use. It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it. can enable it.
......
...@@ -40,7 +40,7 @@ module EE ...@@ -40,7 +40,7 @@ module EE
null: true, null: true,
description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.", description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.",
resolver: ::Resolvers::VulnerabilitiesHistoryResolver, resolver: ::Resolvers::VulnerabilitiesHistoryResolver,
deprecated: { reason: 'Use `vulnerabilitiesCountByDay`', milestone: '13.3' } deprecated: { reason: :discouraged, replacement: 'Query.vulnerabilitiesCountByDay', milestone: '13.3' }
field :geo_node, ::Types::Geo::GeoNodeType, field :geo_node, ::Types::Geo::GeoNodeType,
null: true, null: true,
......
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
%td %td
= render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.push_access_levels, level_frequencies: access_level_frequencies(protected_branch.push_access_levels), input_basic_name: 'push_access_levels', disabled: !can_unprotect, toggle_class: 'js-allowed-to-push' } = render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.push_access_levels, level_frequencies: access_level_frequencies(protected_branch.push_access_levels), input_basic_name: 'push_access_levels', disabled: !can_unprotect, toggle_class: 'js-allowed-to-push' }
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project) - if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
%td %td
= render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name } = render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name }
...@@ -57,7 +57,7 @@ class Feature ...@@ -57,7 +57,7 @@ class Feature
# use `default_enabled: true` to default the flag to being `enabled` # use `default_enabled: true` to default the flag to being `enabled`
# unless set explicitly. The default is `disabled` # unless set explicitly. The default is `disabled`
# TODO: remove the `default_enabled:` and read it from the `defintion_yaml` # TODO: remove the `default_enabled:` and read it from the `defintion_yaml`
# check: https://gitlab.com/gitlab-org/gitlab/-/issues/271275 # check: https://gitlab.com/gitlab-org/gitlab/-/issues/30228
def enabled?(key, thing = nil, type: :development, default_enabled: false) def enabled?(key, thing = nil, type: :development, default_enabled: false)
if check_feature_flags_definition? if check_feature_flags_definition?
if thing && !thing.respond_to?(:flipper_id) if thing && !thing.respond_to?(:flipper_id)
...@@ -65,11 +65,11 @@ class Feature ...@@ -65,11 +65,11 @@ class Feature
"The thing '#{thing.class.name}' for feature flag '#{key}' needs to include `FeatureGate` or implement `flipper_id`" "The thing '#{thing.class.name}' for feature flag '#{key}' needs to include `FeatureGate` or implement `flipper_id`"
end end
Feature::Definition.valid_usage!(key, type: type, default_enabled: :yaml) Feature::Definition.valid_usage!(key, type: type, default_enabled: default_enabled)
end end
# TODO: Remove rubocop disable comment once `default_enabled` argument is removed https://gitlab.com/gitlab-org/gitlab/-/issues/271275 # If `default_enabled: :yaml` we fetch the value from the YAML definition instead.
default_enabled = Feature::Definition.default_enabled?(key) # rubocop:disable Lint/ShadowedArgument default_enabled = Feature::Definition.default_enabled?(key) if default_enabled == :yaml
# During setup the database does not exist yet. So we haven't stored a value # During setup the database does not exist yet. So we haven't stored a value
# for the feature yet and return the default. # for the feature yet and return the default.
......
...@@ -27,7 +27,10 @@ module Gitlab ...@@ -27,7 +27,10 @@ module Gitlab
MD MD
end end
def render_name_and_description(object, level = 3) # Template methods:
# Methods that return chunks of Markdown for insertion into the document
def render_name_and_description(object, owner: nil, level: 3)
content = [] content = []
content << "#{'#' * level} `#{object[:name]}`" content << "#{'#' * level} `#{object[:name]}`"
...@@ -35,10 +38,22 @@ module Gitlab ...@@ -35,10 +38,22 @@ module Gitlab
if object[:description].present? if object[:description].present?
desc = object[:description].strip desc = object[:description].strip
desc += '.' unless desc.ends_with?('.') desc += '.' unless desc.ends_with?('.')
end
if object[:is_deprecated]
owner = Array.wrap(owner)
deprecation = schema_deprecation(owner, object[:name])
content << (deprecation&.original_description || desc)
content << render_deprecation(object, owner, :block)
else
content << desc content << desc
end end
content.join("\n\n") content.compact.join("\n\n")
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end end
def sorted_by_name(objects) def sorted_by_name(objects)
...@@ -47,39 +62,25 @@ module Gitlab ...@@ -47,39 +62,25 @@ module Gitlab
objects.sort_by { |o| o[:name] } objects.sort_by { |o| o[:name] }
end end
def render_field(field) def render_field(field, owner)
row(render_name(field), render_field_type(field[:type]), render_description(field)) render_row(
render_name(field, owner),
render_field_type(field[:type]),
render_description(field, owner, :inline)
)
end end
def render_enum_value(value) def render_enum_value(enum, value)
row(render_name(value), render_description(value)) render_row(render_name(value, enum[:name]), render_description(value, enum[:name], :inline))
end end
def row(*values) def render_union_member(member)
"| #{values.join(' | ')} |" "- [`#{member}`](##{member.downcase})"
end end
def render_name(object) # QUERIES:
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
end
# Returns the object description. If the object has been deprecated, # Methods that return parts of the schema, or related information:
# the deprecation reason will be returned in place of the description.
def render_description(object)
return object[:description] unless object[:is_deprecated]
"**Deprecated:** #{object[:deprecation_reason]}"
end
def render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end
# We are ignoring connections and built in types for now, # We are ignoring connections and built in types for now,
# they should be added when queries are generated. # they should be added when queries are generated.
...@@ -103,6 +104,83 @@ module Gitlab ...@@ -103,6 +104,83 @@ module Gitlab
!enum_type[:name].in?(%w[__DirectiveLocation __TypeKind]) !enum_type[:name].in?(%w[__DirectiveLocation __TypeKind])
end end
end end
private # DO NOT CALL THESE METHODS IN TEMPLATES
# Template methods
def render_row(*values)
"| #{values.map { |val| val.to_s.squish }.join(' | ')} |"
end
def render_name(object, owner = nil)
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
end
# Returns the object description. If the object has been deprecated,
# the deprecation reason will be returned in place of the description.
def render_description(object, owner = nil, context = :block)
owner = Array.wrap(owner)
return render_deprecation(object, owner, context) if object[:is_deprecated]
return if object[:description].blank?
desc = object[:description].strip
desc += '.' unless desc.ends_with?('.')
desc
end
def render_deprecation(object, owner, context)
deprecation = schema_deprecation(owner, object[:name])
return deprecation.markdown(context: context) if deprecation
reason = object[:deprecation_reason] || 'Use of this is deprecated.'
"**Deprecated:** #{reason}"
end
def render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
# Queries
# returns the deprecation information for a field or argument
# See: Gitlab::Graphql::Deprecation
def schema_deprecation(type_name, field_name)
schema_member(type_name, field_name)&.deprecation
end
# Return a part of the schema.
#
# This queries the Schema by owner and name to find:
#
# - fields (e.g. `schema_member('Query', 'currentUser')`)
# - arguments (e.g. `schema_member(['Query', 'project], 'fullPath')`)
def schema_member(type_name, field_name)
type_name = Array.wrap(type_name)
if type_name.size == 2
arg_name = field_name
type_name, field_name = type_name
else
type_name = type_name.first
arg_name = nil
end
return if type_name.nil? || field_name.nil?
type = schema.types[type_name]
return unless type && type.kind.fields?
field = type.fields[field_name]
return field if arg_name.nil?
args = field.arguments
is_mutation = field.mutation && field.mutation <= ::Mutations::BaseMutation
args = args['input'].type.unwrap.arguments if is_mutation
args[arg_name]
end
end end
end end
end end
......
...@@ -10,17 +10,20 @@ module Gitlab ...@@ -10,17 +10,20 @@ module Gitlab
# It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs. # It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
# #
# Arguments: # Arguments:
# schema - the GraphQL schema definition. For GitLab should be: GitlabSchema.graphql_definition # schema - the GraphQL schema definition. For GitLab should be: GitlabSchema
# output_dir: The folder where the markdown files will be saved # output_dir: The folder where the markdown files will be saved
# template: The path of the haml template to be parsed # template: The path of the haml template to be parsed
class Renderer class Renderer
include Gitlab::Graphql::Docs::Helper include Gitlab::Graphql::Docs::Helper
attr_reader :schema
def initialize(schema, output_dir:, template:) def initialize(schema, output_dir:, template:)
@output_dir = output_dir @output_dir = output_dir
@template = template @template = template
@layout = Haml::Engine.new(File.read(template)) @layout = Haml::Engine.new(File.read(template))
@parsed_schema = GraphQLDocs::Parser.new(schema, {}).parse @parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse
@schema = schema
end end
def contents def contents
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
\ \
- sorted_by_name(queries).each do |query| - sorted_by_name(queries).each do |query|
= render_name_and_description(query) = render_name_and_description(query, owner: 'Query')
\ \
= render_return_type(query) = render_return_type(query)
- unless query[:arguments].empty? - unless query[:arguments].empty?
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
~ "| Name | Type | Description |" ~ "| Name | Type | Description |"
~ "| ---- | ---- | ----------- |" ~ "| ---- | ---- | ----------- |"
- sorted_by_name(query[:arguments]).each do |argument| - sorted_by_name(query[:arguments]).each do |argument|
= render_field(argument) = render_field(argument, query[:type][:name])
\ \
:plain :plain
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
~ "| Field | Type | Description |" ~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |" ~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields]).each do |field| - sorted_by_name(type[:fields]).each do |field|
= render_field(field) = render_field(field, type[:name])
\ \
:plain :plain
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
~ "| Value | Description |" ~ "| Value | Description |"
~ "| ----- | ----------- |" ~ "| ----- | ----------- |"
- sorted_by_name(enum[:values]).each do |value| - sorted_by_name(enum[:values]).each do |value|
= render_enum_value(value) = render_enum_value(enum, value)
\ \
:plain :plain
...@@ -121,12 +121,12 @@ ...@@ -121,12 +121,12 @@
\ \
- graphql_union_types.each do |type| - graphql_union_types.each do |type|
= render_name_and_description(type, 4) = render_name_and_description(type, level: 4)
\ \
One of: One of:
\ \
- type[:possible_types].each do |type_name| - type[:possible_types].each do |member|
~ "- [`#{type_name}`](##{type_name.downcase})" = render_union_member(member)
\ \
:plain :plain
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
\ \
- graphql_interface_types.each do |type| - graphql_interface_types.each do |type|
= render_name_and_description(type, 4) = render_name_and_description(type, level: 4)
\ \
Implementations: Implementations:
\ \
...@@ -144,5 +144,5 @@ ...@@ -144,5 +144,5 @@
~ "| Field | Type | Description |" ~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |" ~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields] + type[:connections]).each do |field| - sorted_by_name(type[:fields] + type[:connections]).each do |field|
= render_field(field) = render_field(field, type[:name])
\ \
...@@ -110,7 +110,7 @@ namespace :gitlab do ...@@ -110,7 +110,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Generate GraphQL docs' desc 'GitLab | GraphQL | Generate GraphQL docs'
task compile_docs: [:environment, :enable_feature_flags] do task compile_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
renderer.write renderer.write
...@@ -119,7 +119,7 @@ namespace :gitlab do ...@@ -119,7 +119,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Check if GraphQL docs are up to date' desc 'GitLab | GraphQL | Check if GraphQL docs are up to date'
task check_docs: [:environment, :enable_feature_flags] do task check_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md')) doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md'))
......
...@@ -24,7 +24,7 @@ RSpec.describe 'Dashboard shortcuts', :js do ...@@ -24,7 +24,7 @@ RSpec.describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'M']) find('body').send_keys([:shift, 'M'])
check_page_title('Merge Requests') check_page_title('Merge requests')
find('body').send_keys([:shift, 'T']) find('body').send_keys([:shift, 'T'])
......
...@@ -15,7 +15,6 @@ RSpec.describe 'Graphql Field feature flags' do ...@@ -15,7 +15,6 @@ RSpec.describe 'Graphql Field feature flags' do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
subject { result } subject { result }
......
...@@ -128,7 +128,6 @@ RSpec.describe Types::BaseField do ...@@ -128,7 +128,6 @@ RSpec.describe Types::BaseField do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
it 'returns false if the feature is not enabled' do it 'returns false if the feature is not enabled' do
......
...@@ -183,7 +183,6 @@ RSpec.describe API::Helpers do ...@@ -183,7 +183,6 @@ RSpec.describe API::Helpers do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
context 'with feature enabled' do context 'with feature enabled' do
......
...@@ -8,7 +8,6 @@ RSpec.describe Feature::Gitaly do ...@@ -8,7 +8,6 @@ RSpec.describe Feature::Gitaly do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
describe ".enabled?" do describe ".enabled?" do
......
...@@ -123,35 +123,12 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -123,35 +123,12 @@ RSpec.describe Feature, stub_feature_flags: false do
end end
describe '.enabled?' do describe '.enabled?' do
let(:disabled_ff_definition) do it 'returns false for undefined feature' do
Feature::Definition.new( expect(described_class.enabled?(:some_random_feature_flag)).to be_falsey
'development/disabled_feature_flag.yml',
name: 'disabled_feature_flag',
type: 'development',
default_enabled: false
)
end end
let(:enabled_ff_definition) do it 'returns true for undefined feature with default_enabled' do
Feature::Definition.new( expect(described_class.enabled?(:some_random_feature_flag, default_enabled: true)).to be_truthy
'development/enabled_feature_flag.yml',
name: 'enabled_feature_flag',
type: 'development',
default_enabled: true
)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{
disabled_ff_definition.key => disabled_ff_definition,
enabled_ff_definition.key => enabled_ff_definition
}
end
end
it 'raises an exception for undefined feature' do
expect { described_class.enabled?(:some_random_feature_flag) }.to raise_error Feature::InvalidFeatureFlagError
end end
it 'returns false for existing disabled feature in the database' do it 'returns false for existing disabled feature in the database' do
...@@ -169,58 +146,39 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -169,58 +146,39 @@ RSpec.describe Feature, stub_feature_flags: false do
it { expect(described_class.send(:l1_cache_backend)).to eq(Gitlab::ProcessMemoryCache.cache_backend) } it { expect(described_class.send(:l1_cache_backend)).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.send(:l2_cache_backend)).to eq(Rails.cache) } it { expect(described_class.send(:l2_cache_backend)).to eq(Rails.cache) }
it 'caches the status in L1 and L2 caches', :request_store, :use_clean_rails_memory_store_caching, :aggregate_failures do it 'caches the status in L1 and L2 caches',
:request_store, :use_clean_rails_memory_store_caching do
described_class.enable(:enabled_feature_flag) described_class.enable(:enabled_feature_flag)
flipper_features_key = 'flipper/v1/features' flipper_key = "flipper/v1/feature/enabled_feature_flag"
flipper_feature_key = 'flipper/v1/feature/enabled_feature_flag'
allow(described_class.send(:l2_cache_backend)).to receive(:fetch).twice.and_call_original expect(described_class.send(:l2_cache_backend))
allow(described_class.send(:l1_cache_backend)).to receive(:fetch).twice.and_call_original .to receive(:fetch)
.once
.with(flipper_key, expires_in: 1.hour)
.and_call_original
expect(described_class.send(:l1_cache_backend))
.to receive(:fetch)
.once
.with(flipper_key, expires_in: 1.minute)
.and_call_original
2.times do 2.times do
expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy
end end
expect(described_class.send(:l2_cache_backend)).to have_received(:fetch).with(flipper_features_key, expires_in: 1.hour)
expect(described_class.send(:l2_cache_backend)).to have_received(:fetch).with(flipper_feature_key, expires_in: 1.hour)
expect(described_class.send(:l1_cache_backend)).to have_received(:fetch).with(flipper_features_key, expires_in: 1.minute)
expect(described_class.send(:l1_cache_backend)).to have_received(:fetch).with(flipper_feature_key, expires_in: 1.minute)
end end
it 'returns the default value when the database does not exist', :aggregate_falures do it 'returns the default value when the database does not exist' do
a_feature = Feature::Definition.new( fake_default = double('fake default')
'development/a_feature.yml',
name: 'a_feature',
type: 'development',
default_enabled: true
)
allow(Feature::Definition).to receive(:definitions) do
{ a_feature.key => a_feature }
end
expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, "No database" } expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, "No database" }
expect(described_class.enabled?(:a_feature)).to eq(true) expect(described_class.enabled?(:a_feature, default_enabled: fake_default)).to eq(fake_default)
end end
context 'cached feature flag', :request_store, :use_clean_rails_memory_store_caching, :aggregate_failures do context 'cached feature flag', :request_store do
let(:flag) { :some_feature_flag } let(:flag) { :some_feature_flag }
let(:some_feature_flag) do
Feature::Definition.new(
"development/#{flag}.yml",
name: flag.to_s,
type: 'development',
default_enabled: true
)
end
before do before do
allow(Feature::Definition).to receive(:definitions) do
{ some_feature_flag.key => some_feature_flag }
end
described_class.send(:flipper).memoize = false described_class.send(:flipper).memoize = false
described_class.enabled?(flag) described_class.enabled?(flag)
end end
...@@ -315,48 +273,63 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -315,48 +273,63 @@ RSpec.describe Feature, stub_feature_flags: false do
.to raise_error(/The `type:` of/) .to raise_error(/The `type:` of/)
end end
it 'reads the default from the YAML definition' do it 'when invalid default_enabled is used' do
expect(described_class.enabled?(:my_feature_flag)).to eq(false) expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
.to raise_error(/The `default_enabled:` of/)
end end
context 'when YAML definition does not exist for an optional type' do context 'when `default_enabled: :yaml` is used in code' do
let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first } it 'reads the default from the YAML definition' do
expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(false)
end
context 'when in dev or test environment' do context 'when default_enabled is true in the YAML definition' do
it 'raises an error for dev' do let(:default_enabled) { true }
expect { described_class.enabled?(:non_existent_flag, type: optional_type) }
.to raise_error( it 'reads the default from the YAML definition' do
Feature::InvalidFeatureFlagError, expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(true)
"The feature flag YAML definition for 'non_existent_flag' does not exist")
end end
end end
context 'when in production' do context 'when YAML definition does not exist for an optional type' do
before do let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first }
allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
context 'when in dev or test environment' do
it 'raises an error for dev' do
expect { described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml) }
.to raise_error(
Feature::InvalidFeatureFlagError,
"The feature flag YAML definition for 'non_existent_flag' does not exist")
end
end end
context 'when database exists' do context 'when in production' do
before do before do
allow(Gitlab::Database).to receive(:exists?).and_return(true) allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
end end
it 'checks the persisted status and returns false' do context 'when database exists' do
expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original before do
allow(Gitlab::Database).to receive(:exists?).and_return(true)
end
expect(described_class.enabled?(:non_existent_flag, type: optional_type)).to eq(false) it 'checks the persisted status and returns false' do
end expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original
end
context 'when database does not exist' do expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
before do end
allow(Gitlab::Database).to receive(:exists?).and_return(false)
end end
it 'returns false without checking the status in the database' do context 'when database does not exist' do
expect(described_class).not_to receive(:get) before do
allow(Gitlab::Database).to receive(:exists?).and_return(false)
end
it 'returns false without checking the status in the database' do
expect(described_class).not_to receive(:get)
expect(described_class.enabled?(:non_existent_flag, type: optional_type)).to eq(false) expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
end
end end
end end
end end
...@@ -364,36 +337,13 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -364,36 +337,13 @@ RSpec.describe Feature, stub_feature_flags: false do
end end
end end
describe '.disabled?' do describe '.disable?' do
let(:disabled_ff_definition) do it 'returns true for undefined feature' do
Feature::Definition.new( expect(described_class.disabled?(:some_random_feature_flag)).to be_truthy
'development/disabled_feature_flag.yml',
name: 'disabled_feature_flag',
type: 'development',
default_enabled: false
)
end
let(:enabled_ff_definition) do
Feature::Definition.new(
'development/enabled_feature_flag.yml',
name: 'enabled_feature_flag',
type: 'development',
default_enabled: true
)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{
disabled_ff_definition.key => disabled_ff_definition,
enabled_ff_definition.key => enabled_ff_definition
}
end
end end
it 'raises an exception for undefined feature' do it 'returns false for undefined feature with default_enabled' do
expect { described_class.disabled?(:some_random_feature_flag) }.to raise_error Feature::InvalidFeatureFlagError expect(described_class.disabled?(:some_random_feature_flag, default_enabled: true)).to be_falsey
end end
it 'returns true for existing disabled feature in the database' do it 'returns true for existing disabled feature in the database' do
......
...@@ -12,7 +12,6 @@ RSpec.describe Gitlab::GonHelper do ...@@ -12,7 +12,6 @@ RSpec.describe Gitlab::GonHelper do
describe '#push_frontend_feature_flag' do describe '#push_frontend_feature_flag' do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
it 'pushes a feature flag to the frontend' do it 'pushes a feature flag to the frontend' do
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do describe '#contents' do
# Returns a Schema that uses the given `type` let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
def mock_schema(type, field_description)
query_type = Class.new(Types::BaseObject) do
graphql_name 'Query'
field :foo, type, null: true do let(:query_type) do
description field_description Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
# this keeps type and field_description in scope.
t.field :foo, type, null: true, description: field_description do
argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.' argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.'
end end
end end
end
GraphQL::Schema.define( let(:mock_schema) do
query: query_type, Class.new(GraphQL::Schema) do
resolve_type: ->(obj, ctx) { raise 'Not a real schema' } def resolve_type(obj, ctx)
) raise 'Not a real schema'
end
end
end end
let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' } let(:field_description) { 'List of objects.' }
subject(:contents) do subject(:contents) do
mock_schema.query(query_type)
described_class.new( described_class.new(
mock_schema(type, field_description).graphql_definition, mock_schema,
output_dir: nil, output_dir: nil,
template: template template: template
).contents ).contents
...@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false, null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' }, deprecated: { reason: 'This is deprecated', milestone: '1.10' },
description: 'A description.' description: 'A description.'
field :foo_with_args,
type: GraphQL::STRING_TYPE,
null: false,
deprecated: { reason: 'Do not use', milestone: '1.10' },
description: 'A description.' do
argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
end
field :bar,
type: GraphQL::STRING_TYPE,
null: false,
description: 'A description.',
deprecated: {
reason: :renamed,
milestone: '1.10',
replacement: 'Query.boom'
}
end end
end end
...@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated:** This is deprecated. Deprecated in 1.10. | | `bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: `Query.boom`. |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
| `fooWithArgs` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. Do not use. |
DOC
is_expected.to include(expectation)
end
end
context 'when a Query.field is deprecated' do
let(:type) { ::GraphQL::INT_TYPE }
before do
query_type.field(
name: :bar,
type: type,
null: true,
description: 'A bar',
deprecated: { reason: :renamed, milestone: '10.11', replacement: 'Query.foo' }
)
end
it 'includes the deprecation' do
expectation = <<~DOC
### `bar`
A bar.
WARNING:
**Deprecated** in 10.11.
This was renamed.
Use: `Query.foo`.
Returns [`Int`](#int).
DOC DOC
is_expected.to include(expectation) is_expected.to include(expectation)
......
...@@ -102,11 +102,6 @@ RSpec.describe Gitlab::Metrics::Methods do ...@@ -102,11 +102,6 @@ RSpec.describe Gitlab::Metrics::Methods do
let(:feature_name) { :some_metric_feature } let(:feature_name) { :some_metric_feature }
let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) } let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
context 'when feature is enabled' do context 'when feature is enabled' do
before do before do
stub_feature_flags(feature_name => true) stub_feature_flags(feature_name => true)
......
...@@ -54,11 +54,6 @@ RSpec.describe Gitlab::Metrics do ...@@ -54,11 +54,6 @@ RSpec.describe Gitlab::Metrics do
end end
describe '.measure' do describe '.measure' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
context 'without a transaction' do context 'without a transaction' do
it 'returns the return value of the block' do it 'returns the return value of the block' do
val = described_class.measure(:foo) { 10 } val = described_class.measure(:foo) { 10 }
......
...@@ -438,8 +438,6 @@ RSpec.describe API::Features, stub_feature_flags: false do ...@@ -438,8 +438,6 @@ RSpec.describe API::Features, stub_feature_flags: false do
context 'when the gate value was set' do context 'when the gate value was set' do
before do before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
Feature.enable(feature_name) Feature.enable(feature_name)
end end
......
...@@ -73,7 +73,6 @@ RSpec.describe API::UsageData do ...@@ -73,7 +73,6 @@ RSpec.describe API::UsageData do
context 'with unknown event' do context 'with unknown event' do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
it 'returns status ok' do it 'returns status ok' do
...@@ -150,7 +149,6 @@ RSpec.describe API::UsageData do ...@@ -150,7 +149,6 @@ RSpec.describe API::UsageData do
context 'with unknown event' do context 'with unknown event' do
before do before do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end end
it 'returns status ok' do it 'returns status ok' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment