diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-25 09:10:45 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-25 09:10:45 +0000 |
| commit | bcfab67c0f33aeda96041f341f92cf0ff1e062d3 (patch) | |
| tree | 2d3a9c5ccd7693112ed48d410a9a940f6a1fa8de | |
| parent | c1ccb69fc9b1f07a00d3310f5fbd2e4622db9482 (diff) | |
| download | gitlab-ce-bcfab67c0f33aeda96041f341f92cf0ff1e062d3.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
47 files changed, 481 insertions, 106 deletions
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 0ae326b5d94..782c8c293fd 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -52,7 +52,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def bulk_restore - TodoService.new.restore_todos(current_user.todos.for_ids(params[:ids]), current_user) + TodoService.new.restore_todos(current_user.todos.id_in(params[:ids]), current_user) render json: todos_counts end diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb index 1702783b10f..25ec7ab1335 100644 --- a/app/controllers/projects/pipelines/tests_controller.rb +++ b/app/controllers/projects/pipelines/tests_controller.rb @@ -32,7 +32,7 @@ module Projects # rubocop: disable CodeReuse/ActiveRecord def builds - @builds ||= pipeline.latest_builds.for_ids(build_ids).presence || render_404 + @builds ||= pipeline.latest_builds.id_in(build_ids).presence || render_404 end def build_ids diff --git a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb index 2d4983f0d6e..ba644eff36c 100644 --- a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb +++ b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb @@ -5,6 +5,7 @@ module Mutations # and optionally support the workflow to allow clients to display and solve CAPTCHAs. module CanMutateSpammable extend ActiveSupport::Concern + include Spam::Concerns::HasSpamActionResponseFields # NOTE: The arguments and fields are intentionally named with 'captcha' instead of 'recaptcha', # so that they can be applied to future alternative CAPTCHA implementations other than @@ -59,25 +60,5 @@ module Mutations request: context[:request] } end - - # with_spam_action_fields(spammable) { {other_fields: true} } -> hash - # - # Takes a Spammable and a block as arguments. - # - # The block passed should be a hash, which the spam action fields will be merged into. - def with_spam_action_fields(spammable) - spam_action_fields = { - spam: spammable.spam?, - # NOTE: These fields are intentionally named with 'captcha' instead of 'recaptcha', so - # that they can be applied to future alternative CAPTCHA implementations other than - # reCAPTCHA (such as FriendlyCaptcha) without having to change the response field name - # in the API. - needs_captcha_response: spammable.render_recaptcha?, - spam_log_id: spammable.spam_log&.id, - captcha_site_key: Gitlab::CurrentSettings.recaptcha_site_key - } - - yield.merge(spam_action_fields) - end end end diff --git a/app/graphql/mutations/release_asset_links/base.rb b/app/graphql/mutations/release_asset_links/base.rb new file mode 100644 index 00000000000..3e71cec183b --- /dev/null +++ b/app/graphql/mutations/release_asset_links/base.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Mutations + module ReleaseAssetLinks + class Base < BaseMutation + include FindsProject + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Full path of the project the asset link is associated with.' + + argument :tag_name, GraphQL::STRING_TYPE, + required: true, as: :tag, + description: "Name of the associated release's tag." + end + end +end diff --git a/app/graphql/mutations/release_asset_links/create.rb b/app/graphql/mutations/release_asset_links/create.rb new file mode 100644 index 00000000000..cd9acf3f4cb --- /dev/null +++ b/app/graphql/mutations/release_asset_links/create.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Mutations + module ReleaseAssetLinks + class Create < Base + graphql_name 'ReleaseAssetLinkCreate' + + authorize :create_release + + include Types::ReleaseAssetLinkSharedInputArguments + + field :link, + Types::ReleaseAssetLinkType, + null: true, + description: 'The asset link after mutation.' + + def resolve(project_path:, tag:, **link_attrs) + project = authorized_find!(project_path) + release = project.releases.find_by_tag(tag) + + if release.nil? + message = _('Release with tag "%{tag}" was not found') % { tag: tag } + return { link: nil, errors: [message] } + end + + new_link = release.links.create(link_attrs) + + unless new_link.persisted? + return { link: nil, errors: new_link.errors.full_messages } + end + + { link: new_link, errors: [] } + end + end + end +end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 73eac9f0f3b..7f2dd448b8b 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -56,7 +56,7 @@ module Mutations end snippet = service_response.payload[:snippet] - with_spam_action_fields(snippet) do + with_spam_action_response_fields(snippet) do { snippet: service_response.success? ? snippet : nil, errors: errors_on_object(snippet) diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index af8e6f384b7..9f9f8bca848 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -45,7 +45,7 @@ module Mutations end snippet = service_response.payload[:snippet] - with_spam_action_fields(snippet) do + with_spam_action_response_fields(snippet) do { snippet: service_response.success? ? snippet : snippet.reset, errors: errors_on_object(snippet) diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb index dc02ffadada..41ccbd77aa6 100644 --- a/app/graphql/mutations/todos/restore_many.rb +++ b/app/graphql/mutations/todos/restore_many.rb @@ -60,7 +60,7 @@ module Mutations def authorized_find_all_pending_by_current_user(ids) return Todo.none if ids.blank? || current_user.nil? - Todo.for_ids(ids).for_user(current_user).done + Todo.id_in(ids).for_user(current_user).done end def restore(todos) diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb index 11774d0b59d..4ad9e8c0e40 100644 --- a/app/graphql/types/base_argument.rb +++ b/app/graphql/types/base_argument.rb @@ -6,7 +6,6 @@ module Types def initialize(*args, **kwargs, &block) kwargs = gitlab_deprecation(kwargs) - kwargs.delete(:deprecation_reason) super(*args, **kwargs, &block) end diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb index b367aa946ae..f215aa255de 100644 --- a/app/graphql/types/board_list_type.rb +++ b/app/graphql/types/board_list_type.rb @@ -8,6 +8,8 @@ module Types graphql_name 'BoardList' description 'Represents a list for an issue board' + alias_method :list, :object + field :id, GraphQL::ID_TYPE, null: false, description: 'ID (global ID) of the list.' field :title, GraphQL::STRING_TYPE, null: false, @@ -37,12 +39,10 @@ module Types def metadata strong_memoize(:metadata) do - list = self.object - user = context[:current_user] params = (context[:issue_filters] || {}).merge(board_id: list.board_id, id: list.id) ::Boards::Issues::ListService - .new(list.board.resource_parent, user, params) + .new(list.board.resource_parent, current_user, params) .metadata end end diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb index 9f087f3812d..ad195354930 100644 --- a/app/graphql/types/concerns/gitlab_style_deprecations.rb +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -10,7 +10,7 @@ module GitlabStyleDeprecations def gitlab_deprecation(kwargs) if kwargs[:deprecation_reason].present? raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \ - 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-and-enum-values' + 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values' end deprecation = kwargs.delete(:deprecated) diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index cf583131ed0..d1b168d0890 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -71,6 +71,7 @@ module Types mount_mutation Mutations::Releases::Create mount_mutation Mutations::Releases::Update mount_mutation Mutations::Releases::Delete + mount_mutation Mutations::ReleaseAssetLinks::Create mount_mutation Mutations::Terraform::State::Delete mount_mutation Mutations::Terraform::State::Lock mount_mutation Mutations::Terraform::State::Unlock diff --git a/app/graphql/types/release_asset_link_input_type.rb b/app/graphql/types/release_asset_link_input_type.rb index 242d19b683f..17a190aba4e 100644 --- a/app/graphql/types/release_asset_link_input_type.rb +++ b/app/graphql/types/release_asset_link_input_type.rb @@ -6,20 +6,6 @@ module Types graphql_name 'ReleaseAssetLinkInput' description 'Fields that are available when modifying a release asset link' - argument :name, GraphQL::STRING_TYPE, - required: true, - description: 'Name of the asset link.' - - argument :url, GraphQL::STRING_TYPE, - required: true, - description: 'URL of the asset link.' - - argument :direct_asset_path, GraphQL::STRING_TYPE, - required: false, as: :filepath, - description: 'Relative path for a direct asset link.' - - argument :link_type, Types::ReleaseAssetLinkTypeEnum, - required: false, default_value: 'other', - description: 'The type of the asset link.' + include Types::ReleaseAssetLinkSharedInputArguments end end diff --git a/app/graphql/types/release_asset_link_shared_input_arguments.rb b/app/graphql/types/release_asset_link_shared_input_arguments.rb new file mode 100644 index 00000000000..4aa247e47cc --- /dev/null +++ b/app/graphql/types/release_asset_link_shared_input_arguments.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module ReleaseAssetLinkSharedInputArguments + extend ActiveSupport::Concern + + included do + argument :name, GraphQL::STRING_TYPE, + required: true, + description: 'Name of the asset link.' + + argument :url, GraphQL::STRING_TYPE, + required: true, + description: 'URL of the asset link.' + + argument :direct_asset_path, GraphQL::STRING_TYPE, + required: false, as: :filepath, + description: 'Relative path for a direct asset link.' + + argument :link_type, Types::ReleaseAssetLinkTypeEnum, + required: false, default_value: 'other', + description: 'The type of the asset link.' + end + end +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index a7acc0cd7db..4ad4fcf0f6e 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -406,7 +406,7 @@ module SearchHelper # Closed is considered "danger" for MR so we need to handle separately if issuable.is_a?(::MergeRequest) if issuable.merged? - :primary + :info elsif issuable.closed? :danger else diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index f87eccecf9f..8a49d476ba7 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.25.0' + VERSION = '0.26.0' self.table_name = 'clusters_applications_runners' diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index ea2f425c5f6..781b3456c87 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -52,7 +52,6 @@ class CommitStatus < ApplicationRecord scope :before_stage, -> (index) { where('stage_idx < ?', index) } scope :for_stage, -> (index) { where(stage_idx: index) } scope :after_stage, -> (index) { where('stage_idx > ?', index) } - scope :for_ids, -> (ids) { where(id: ids) } scope :for_ref, -> (ref) { where(ref: ref) } scope :by_name, -> (name) { where(name: name) } scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) } diff --git a/app/models/todo.rb b/app/models/todo.rb index 12dc9ce0fe6..176d5e56fc0 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -55,7 +55,6 @@ class Todo < ApplicationRecord validates :project, presence: true, unless: :group_id validates :group, presence: true, unless: :project_id - scope :for_ids, -> (ids) { where(id: ids) } scope :pending, -> { with_state(:pending) } scope :done, -> { with_state(:done) } scope :for_action, -> (action) { where(action: action) } diff --git a/app/services/boards/base_items_list_service.rb b/app/services/boards/base_items_list_service.rb index 851120ef597..5aebf216460 100644 --- a/app/services/boards/base_items_list_service.rb +++ b/app/services/boards/base_items_list_service.rb @@ -11,8 +11,24 @@ module Boards ordered_items end + # rubocop: disable CodeReuse/ActiveRecord + def metadata + issuables = item_model.arel_table + keys = metadata_fields.keys + # TODO: eliminate need for SQL literal fragment + columns = Arel.sql(metadata_fields.values_at(*keys).join(', ')) + results = item_model.where(id: items.select(issuables[:id])).pluck(columns) + + Hash[keys.zip(results.flatten)] + end + # rubocop: enable CodeReuse/ActiveRecord + private + def metadata_fields + { size: 'COUNT(*)' } + end + def ordered_items raise NotImplementedError end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 27d59e052c7..c6855f29af0 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -9,18 +9,6 @@ module Boards IssuesFinder.valid_params end - # rubocop: disable CodeReuse/ActiveRecord - def metadata - issues = Issue.arel_table - keys = metadata_fields.keys - # TODO: eliminate need for SQL literal fragment - columns = Arel.sql(metadata_fields.values_at(*keys).join(', ')) - results = Issue.where(id: items.select(issues[:id])).pluck(columns) - - Hash[keys.zip(results.flatten)] - end - # rubocop: enable CodeReuse/ActiveRecord - private def ordered_items @@ -35,10 +23,6 @@ module Boards @board ||= parent.boards.find(params[:board_id]) end - def metadata_fields - { size: 'COUNT(*)' } - end - def filter_params set_scope set_non_archived diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb index a23d5d8941a..bc6c805f86c 100644 --- a/app/services/ci/pipeline_processing/atomic_processing_service.rb +++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb @@ -53,7 +53,7 @@ module Ci end def update_processables!(ids) - created_processables = pipeline.processables.for_ids(ids) + created_processables = pipeline.processables.id_in(ids) .with_project_preload .created .latest diff --git a/app/services/spam/spam_action_service.rb b/app/services/spam/spam_action_service.rb index ff32bc32d93..185b9e39070 100644 --- a/app/services/spam/spam_action_service.rb +++ b/app/services/spam/spam_action_service.rb @@ -9,7 +9,9 @@ module Spam # after the spammable is created/updated based on the remaining parameters. # # Takes a hash of parameters from an incoming request to modify a model (via a controller, - # service, or GraphQL mutation). + # service, or GraphQL mutation). The parameters will either be camelCase (if they are + # received directly via controller params) or underscore_case (if they have come from + # a GraphQL mutation which has converted them to underscore) # # Deletes the parameters which are related to spam and captcha processing, and returns # them in a SpamParams parameters object. See: @@ -18,12 +20,12 @@ module Spam # NOTE: The 'captcha_response' field can be expanded to multiple fields when we move to future # alternative captcha implementations such as FriendlyCaptcha. See # https://gitlab.com/gitlab-org/gitlab/-/issues/273480 - captcha_response = params.delete(:captcha_response) + captcha_response = params.delete(:captcha_response) || params.delete(:captchaResponse) SpamParams.new( api: params.delete(:api), captcha_response: captcha_response, - spam_log_id: params.delete(:spam_log_id) + spam_log_id: params.delete(:spam_log_id) || params.delete(:spamLogId) ) end diff --git a/changelogs/unreleased/284392-use-info-style-for-merged-instead-of-primary.yml b/changelogs/unreleased/284392-use-info-style-for-merged-instead-of-primary.yml new file mode 100644 index 00000000000..d307bbdcd58 --- /dev/null +++ b/changelogs/unreleased/284392-use-info-style-for-merged-instead-of-primary.yml @@ -0,0 +1,5 @@ +--- +title: Use info colour for merged search results instead of primary +merge_request: 55008 +author: +type: changed diff --git a/changelogs/unreleased/322099-sql-definition-for-template_repositories.yml b/changelogs/unreleased/322099-sql-definition-for-template_repositories.yml new file mode 100644 index 00000000000..3d8ca3577d2 --- /dev/null +++ b/changelogs/unreleased/322099-sql-definition-for-template_repositories.yml @@ -0,0 +1,5 @@ +--- +title: Hardened "add" arithmetic for usage data +merge_request: 54794 +author: +type: performance diff --git a/changelogs/unreleased/ld-graphql-version-bump-gem-for-argument-deprecations.yml b/changelogs/unreleased/ld-graphql-version-bump-gem-for-argument-deprecations.yml new file mode 100644 index 00000000000..170eef47f71 --- /dev/null +++ b/changelogs/unreleased/ld-graphql-version-bump-gem-for-argument-deprecations.yml @@ -0,0 +1,7 @@ +--- +title: Mark `startDate` and `endDate` arguments as deprecated in the GraphQL schema for `Project.milestones` and `Group.milestones` + (FOSS and EE), and `Project.iterations`, `Project.milestones`, `Group.epic`, `Group.epics`, `Group.iterations`, `Group.milestones`, + `BoardEpic.children`, and `Epic.children` fields (EE-only). Previously these arguments were marked as deprecated only in their descriptions +merge_request: 45229 +author: +type: changed diff --git a/changelogs/unreleased/nfriend-add-release-asset-link-create-mutation.yml b/changelogs/unreleased/nfriend-add-release-asset-link-create-mutation.yml new file mode 100644 index 00000000000..0ec1c4a04f3 --- /dev/null +++ b/changelogs/unreleased/nfriend-add-release-asset-link-create-mutation.yml @@ -0,0 +1,5 @@ +--- +title: Add GraphQL mutation to create release asset link +merge_request: 54605 +author: +type: added diff --git a/changelogs/unreleased/ph-284212-moveCodeownersTipToVueApp.yml b/changelogs/unreleased/ph-284212-moveCodeownersTipToVueApp.yml new file mode 100644 index 00000000000..a73782b0975 --- /dev/null +++ b/changelogs/unreleased/ph-284212-moveCodeownersTipToVueApp.yml @@ -0,0 +1,5 @@ +--- +title: Moved CODEOWNERS tip into approvals Vue app +merge_request: 54830 +author: +type: changed diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-26-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-26-0.yml new file mode 100644 index 00000000000..d3fac44001f --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-26-0.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.26.0 +merge_request: 54863 +author: +type: other diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index aba9825efc5..b092a5d9fb4 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1913,6 +1913,7 @@ Relationship between an epic and an issue. | `author` | User! | User that created the issue. | | `blocked` | Boolean! | Indicates the issue is blocked. | | `blockedByCount` | Int | Count of issues blocking this issue. | +| `blockedByIssues` | IssueConnection | Issues blocking this issue. | | `closedAt` | Time | Timestamp of when the issue was closed. | | `confidential` | Boolean! | Indicates the issue is confidential. | | `createNoteEmail` | String | User specific email address for the issue. | @@ -1973,6 +1974,7 @@ Represents an epic board list. | ----- | ---- | ----------- | | `collapsed` | Boolean | Indicates if this list is collapsed for this user. | | `epics` | EpicConnection | List epics. | +| `epicsCount` | Int | Count of epics in the list. | | `id` | BoardsEpicListID! | Global ID of the board list. | | `label` | Label | Label of the list. | | `listType` | String! | Type of the list. | @@ -2293,6 +2295,7 @@ A block of time for which a participant is on-call. | `author` | User! | User that created the issue. | | `blocked` | Boolean! | Indicates the issue is blocked. | | `blockedByCount` | Int | Count of issues blocking this issue. | +| `blockedByIssues` | IssueConnection | Issues blocking this issue. | | `closedAt` | Time | Timestamp of when the issue was closed. | | `confidential` | Boolean! | Indicates the issue is confidential. | | `createNoteEmail` | String | User specific email address for the issue. | @@ -3491,6 +3494,16 @@ Represents an asset link associated with a release. | `name` | String | Name of the link. | | `url` | String | URL of the link. | +### ReleaseAssetLinkCreatePayload + +Autogenerated return type of ReleaseAssetLinkCreate. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | +| `link` | ReleaseAssetLink | The asset link after mutation. | + ### ReleaseAssets A container for all assets associated with a release. diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index f32aaf6ffa8..54e2cb9c68d 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -490,15 +490,18 @@ def foo end ``` -## Deprecating fields and enum values +## Deprecating fields, arguments, and enum values The GitLab GraphQL API is versionless, which means we maintain backwards -compatibility with older versions of the API with every change. Rather -than removing a field or [enum value](#enums), we need to _deprecate_ it instead. +compatibility with older versions of the API with every change. + +Rather than removing fields, arguments, or [enum values](#enums), they +must be _deprecated_ instead. + The deprecated parts of the schema can then be removed in a future release in accordance with the [GitLab deprecation process](../api/graphql/index.md#deprecation-process). -Fields and enum values are deprecated using the `deprecated` property. +Fields, arguments, and enum values are deprecated using the `deprecated` property. The value of the property is a `Hash` of: - `reason` - Reason for the deprecation. @@ -518,14 +521,15 @@ is appended to the `description`. ### Deprecation reason style guide -Where the reason for deprecation is due to the field or enum value being -replaced, the `reason` must be: +Where the reason for deprecation is due to the field, argument, or enum value being +replaced, the `reason` must indicate the replacement. For example, the +following is a `reason` for a replaced field: ```plaintext Use `otherFieldName` ``` -Example: +Examples: ```ruby field :designs, ::Types::DesignManagement::DesignCollectionType, null: true, @@ -544,8 +548,8 @@ module Types end ``` -If the field is not being replaced by another field, a descriptive -deprecation `reason` should be given. +If the field, argument, or enum value being deprecated is not being replaced, +a descriptive deprecation `reason` should be given. See also [Aliasing and deprecating mutations](#aliasing-and-deprecating-mutations). @@ -594,7 +598,7 @@ end ``` Enum values can be deprecated using the -[`deprecated` keyword](#deprecating-fields-and-enum-values). +[`deprecated` keyword](#deprecating-fields-arguments-and-enum-values). ### Defining GraphQL enums dynamically from Rails enums @@ -1567,7 +1571,7 @@ mount_aliased_mutation 'BarMutation', Mutations::FooMutation ``` This allows us to rename a mutation and continue to support the old name, -when coupled with the [`deprecated`](#deprecating-fields-and-enum-values) +when coupled with the [`deprecated`](#deprecating-fields-arguments-and-enum-values) argument. Example: diff --git a/lib/gitlab/usage_data_queries.rb b/lib/gitlab/usage_data_queries.rb index b275bdbacde..c00e7a2aa13 100644 --- a/lib/gitlab/usage_data_queries.rb +++ b/lib/gitlab/usage_data_queries.rb @@ -32,6 +32,10 @@ module Gitlab raw_sql(relation, column, :distinct) end + def add(*args) + 'SELECT ' + args.map {|arg| "(#{arg})" }.join(' + ') + end + private def raw_sql(relation, column, distinct = nil) diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb index 871046da72a..8f1979dfa5e 100644 --- a/lib/gitlab/utils/usage_data.rb +++ b/lib/gitlab/utils/usage_data.rb @@ -87,6 +87,14 @@ module Gitlab FALLBACK end + def add(*args) + return -1 if args.any?(&:negative?) + + args.sum + rescue StandardError + FALLBACK + end + def alt_usage_data(value = nil, fallback: FALLBACK, &block) if block_given? yield diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb index 45cfa9b373d..ad9de067375 100644 --- a/lib/quality/test_level.rb +++ b/lib/quality/test_level.rb @@ -43,6 +43,7 @@ module Quality serializers services sidekiq + spam support_specs tasks uploaders diff --git a/lib/spam/concerns/has_spam_action_response_fields.rb b/lib/spam/concerns/has_spam_action_response_fields.rb new file mode 100644 index 00000000000..d49f5cd6454 --- /dev/null +++ b/lib/spam/concerns/has_spam_action_response_fields.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Spam + module Concerns + # This concern is shared by the controller and GraphQL layer to handle + # addition of spam/CAPTCHA related fields in the response. + module HasSpamActionResponseFields + extend ActiveSupport::Concern + + # spam_action_response_fields(spammable) -> hash + # + # Takes a Spammable as an argument and returns response fields necessary to display a CAPTCHA on + # the client. + def spam_action_response_fields(spammable) + { + spam: spammable.spam?, + # NOTE: These fields are intentionally named with 'captcha' instead of 'recaptcha', so + # that they can be applied to future alternative CAPTCHA implementations other than + # reCAPTCHA (such as FriendlyCaptcha) without having to change the response field name + # in the API. + needs_captcha_response: spammable.render_recaptcha?, + spam_log_id: spammable.spam_log&.id, + captcha_site_key: Gitlab::CurrentSettings.recaptcha_site_key + } + end + + # with_spam_action_response_fields(spammable) { {other_fields: true} } -> hash + # + # Takes a Spammable and a block as arguments. + # + # The block passed should be a hash, which the spam_action_fields will be merged into. + def with_spam_action_response_fields(spammable) + yield.merge(spam_action_response_fields(spammable)) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 92ee07da74c..b7e01348ab6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24722,6 +24722,9 @@ msgstr "" msgid "Release title" msgstr "" +msgid "Release with tag \"%{tag}\" was not found" +msgstr "" + msgid "ReleaseAssetLinkType|Image" msgstr "" @@ -30967,6 +30970,9 @@ msgstr "" msgid "Tip: add a" msgstr "" +msgid "Tip: add a %{linkStart}CODEOWNERS%{linkEnd} to automatically add approvers based on file paths and file types." +msgstr "" + msgid "Title" msgstr "" diff --git a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb index ee8db7a1f31..8d1fce406fa 100644 --- a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Mutations::CanMutateSpammable do end it 'merges in spam action fields from spammable' do - result = subject.send(:with_spam_action_fields, spammable) do + result = subject.send(:with_spam_action_response_fields, spammable) do { other_field: true } end expect(result) diff --git a/spec/graphql/mutations/release_asset_links/create_spec.rb b/spec/graphql/mutations/release_asset_links/create_spec.rb new file mode 100644 index 00000000000..e7bac6b3287 --- /dev/null +++ b/spec/graphql/mutations/release_asset_links/create_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::ReleaseAssetLinks::Create do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private, :repository) } + let_it_be(:release) { create(:release, project: project, tag: 'v13.10') } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + + let(:current_user) { developer } + let(:context) { { current_user: current_user } } + let(:project_path) { project.full_path } + let(:tag) { release.tag } + let(:name) { 'awesome-app.dmg' } + let(:url) { 'https://example.com/download/awesome-app.dmg' } + let(:filepath) { '/binaries/awesome-app.dmg' } + + let(:args) do + { + project_path: project_path, + tag: tag, + name: name, + filepath: filepath, + url: url + } + end + + let(:last_release_link) { release.links.last } + + describe '#resolve' do + subject do + resolve(described_class, obj: project, args: args, ctx: context) + end + + context 'when the user has access and no validation errors occur' do + it 'creates a new release asset link', :aggregate_failures do + expect(subject).to eq({ + link: release.reload.links.first, + errors: [] + }) + + expect(release.links.length).to be(1) + + expect(last_release_link.name).to eq(args[:name]) + expect(last_release_link.url).to eq(args[:url]) + expect(last_release_link.filepath).to eq(args[:filepath]) + end + end + + context "when the user doesn't have access to the project" do + let(:current_user) { reporter } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context "when the project doesn't exist" do + let(:project_path) { 'project/that/does/not/exist' } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context "when a validation errors occur" do + shared_examples 'returns errors-as-data' do |expected_messages| + it { expect(subject[:errors]).to eq(expected_messages) } + end + + context "when the release doesn't exist" do + let(:tag) { "nonexistent-tag" } + + it_behaves_like 'returns errors-as-data', ['Release with tag "nonexistent-tag" was not found'] + end + + context 'when the URL is badly formatted' do + let(:url) { 'badly-formatted-url' } + + it_behaves_like 'returns errors-as-data', ["Url is blocked: Only allowed schemes are http, https, ftp"] + end + + context 'when the name is not provided' do + let(:name) { '' } + + it_behaves_like 'returns errors-as-data', ["Name can't be blank"] + end + + context 'when the link already exists' do + let!(:existing_release_link) do + create(:release_link, release: release, name: name, url: url, filepath: filepath) + end + + it_behaves_like 'returns errors-as-data', [ + "Url has already been taken", + "Name has already been taken", + "Filepath has already been taken" + ] + end + end + end +end diff --git a/spec/graphql/types/base_argument_spec.rb b/spec/graphql/types/base_argument_spec.rb new file mode 100644 index 00000000000..61e0179ff21 --- /dev/null +++ b/spec/graphql/types/base_argument_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::BaseArgument do + include_examples 'Gitlab-style deprecations' do + let_it_be(:field) do + Types::BaseField.new(name: 'field', type: String, null: true) + end + + let(:base_args) { { name: 'test', type: String, required: false, owner: field } } + + def subject(args = {}) + described_class.new(**base_args.merge(args)) + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index a977f2c88c6..2b79fd6a5ef 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -580,7 +580,7 @@ RSpec.describe SearchHelper do describe '#issuable_state_to_badge_class' do context 'with merge request' do it 'returns correct badge based on status' do - expect(issuable_state_to_badge_class(build(:merge_request, :merged))).to eq(:primary) + expect(issuable_state_to_badge_class(build(:merge_request, :merged))).to eq(:info) expect(issuable_state_to_badge_class(build(:merge_request, :closed))).to eq(:danger) expect(issuable_state_to_badge_class(build(:merge_request, :opened))).to eq(:success) end diff --git a/spec/lib/gitlab/usage_data_queries_spec.rb b/spec/lib/gitlab/usage_data_queries_spec.rb index 7fc77593265..12eac643383 100644 --- a/spec/lib/gitlab/usage_data_queries_spec.rb +++ b/spec/lib/gitlab/usage_data_queries_spec.rb @@ -38,4 +38,12 @@ RSpec.describe Gitlab::UsageDataQueries do expect(described_class.sum(Issue, :weight)).to eq('SELECT SUM("issues"."weight") FROM "issues"') end end + + describe '.add' do + it 'returns the combined raw SQL with an inner query' do + expect(described_class.add('SELECT COUNT("users"."id") FROM "users"', + 'SELECT COUNT("issues"."id") FROM "issues"')) + .to eq('SELECT (SELECT COUNT("users"."id") FROM "users") + (SELECT COUNT("issues"."id") FROM "issues")') + end + end end diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb index b493576735e..26eadc7a7b3 100644 --- a/spec/lib/gitlab/utils/usage_data_spec.rb +++ b/spec/lib/gitlab/utils/usage_data_spec.rb @@ -183,6 +183,24 @@ RSpec.describe Gitlab::Utils::UsageData do end end + describe '#add' do + it 'adds given values' do + expect(described_class.add(1, 3)).to eq(4) + end + + it 'adds given values' do + expect(described_class.add).to eq(0) + end + + it 'returns the fallback value when adding fails' do + expect(described_class.add(nil, 3)).to eq(-1) + end + + it 'returns the fallback value one of the arguments is negative' do + expect(described_class.add(-1, 1)).to eq(-1) + end + end + describe '#alt_usage_data' do it 'returns the fallback when it gets an error' do expect(described_class.alt_usage_data { raise StandardError } ).to eq(-1) diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb index 2232d47234f..32960cd571b 100644 --- a/spec/lib/quality/test_level_spec.rb +++ b/spec/lib/quality/test_level_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") + .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") end end @@ -103,7 +103,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|support_specs|tasks|uploaders|validators|views|workers|tooling)}) + .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)}) end end diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index a9c4c6680cd..855b1b0f3f7 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -363,23 +363,6 @@ RSpec.describe Todo do end end - describe '.for_ids' do - it 'returns the expected todos' do - todo1 = create(:todo) - todo2 = create(:todo) - todo3 = create(:todo) - create(:todo) - - expect(described_class.for_ids([todo2.id, todo1.id, todo3.id])).to contain_exactly(todo1, todo2, todo3) - end - - it 'returns an empty collection when no ids are given' do - create(:todo) - - expect(described_class.for_ids([])).to be_empty - end - end - describe '.for_user' do it 'returns the expected todos' do user1 = create(:user) diff --git a/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb new file mode 100644 index 00000000000..c7a4cb1ebce --- /dev/null +++ b/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creation of a new release asset link' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private, :repository) } + let_it_be(:release) { create(:release, project: project, tag: 'v13.10') } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + + let(:current_user) { developer } + + let(:mutation_name) { :release_asset_link_create } + + let(:mutation_arguments) do + { + projectPath: project.full_path, + tagName: release.tag, + name: 'awesome-app.dmg', + url: 'https://example.com/download/awesome-app.dmg', + directAssetPath: '/binaries/awesome-app.dmg', + linkType: 'PACKAGE' + } + end + + let(:mutation) do + graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS) + link { + id + name + url + linkType + directAssetUrl + external + } + errors + FIELDS + end + + let(:create_link) { post_graphql_mutation(mutation, current_user: current_user) } + let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access } + + it 'creates and returns a new asset link associated to the provided release', :aggregate_failures do + create_link + + expected_response = { + id: start_with("gid://gitlab/Releases::Link/"), + name: mutation_arguments[:name], + url: mutation_arguments[:url], + linkType: mutation_arguments[:linkType], + directAssetUrl: end_with(mutation_arguments[:directAssetPath]), + external: true + }.with_indifferent_access + + expect(mutation_response[:link]).to include(expected_response) + expect(mutation_response[:errors]).to eq([]) + end +end diff --git a/spec/spam/concerns/has_spam_action_response_fields_spec.rb b/spec/spam/concerns/has_spam_action_response_fields_spec.rb new file mode 100644 index 00000000000..4d5f8d9d431 --- /dev/null +++ b/spec/spam/concerns/has_spam_action_response_fields_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Spam::Concerns::HasSpamActionResponseFields do + subject do + klazz = Class.new + klazz.include described_class + klazz.new + end + + describe '#with_spam_action_response_fields' do + let(:spam_log) { double(:spam_log, id: 1) } + let(:spammable) { double(:spammable, spam?: true, render_recaptcha?: true, spam_log: spam_log) } + let(:recaptcha_site_key) { 'abc123' } + + before do + allow(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { recaptcha_site_key } + end + + it 'merges in spam action fields from spammable' do + result = subject.send(:with_spam_action_response_fields, spammable) do + { other_field: true } + end + expect(result) + .to eq({ + spam: true, + needs_captcha_response: true, + spam_log_id: 1, + captcha_site_key: recaptcha_site_key, + other_field: true + }) + end + end +end diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb index d294f034d2e..bb4270d7db6 100644 --- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb @@ -21,14 +21,14 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do end end - describe "#with_spam_action_fields" do + describe "#with_spam_action_response_fields" do it 'resolves with spam action fields' do subject # NOTE: We do not need to assert on the specific values of spam action fields here, we only need - # to verify that #with_spam_action_fields was invoked and that the fields are present in the - # response. The specific behavior of #with_spam_action_fields is covered in the - # CanMutateSpammable unit tests. + # to verify that #with_spam_action_response_fields was invoked and that the fields are present in the + # response. The specific behavior of #with_spam_action_response_fields is covered in the + # HasSpamActionResponseFields unit tests. expect(mutation_response.keys) .to include('spam', 'spamLogId', 'needsCaptchaResponse', 'captchaSiteKey') end diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb index 269e9170906..bc091a678e2 100644 --- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb @@ -6,7 +6,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do expect { subject(deprecation_reason: 'foo') }.to raise_error( ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \ - 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-and-enum-values' + 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values' ) end |
