Commit c5c647b5 authored by Dmytro Zaporozhets (DZ)'s avatar Dmytro Zaporozhets (DZ)

Merge branch 'nfriend-add-release-asset-link-update-mutation' into 'master'

Add GraphQL mutation to update release asset link

See merge request gitlab-org/gitlab!56265
parents 624dd35b e1d762a1
# 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
...@@ -2,13 +2,23 @@ ...@@ -2,13 +2,23 @@
module Mutations module Mutations
module ReleaseAssetLinks module ReleaseAssetLinks
class Create < Base class Create < BaseMutation
include FindsProject
graphql_name 'ReleaseAssetLinkCreate' graphql_name 'ReleaseAssetLinkCreate'
authorize :create_release authorize :create_release
include Types::ReleaseAssetLinkSharedInputArguments include Types::ReleaseAssetLinkSharedInputArguments
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."
field :link, field :link,
Types::ReleaseAssetLinkType, Types::ReleaseAssetLinkType,
null: true, null: true,
......
# frozen_string_literal: true
module Mutations
module ReleaseAssetLinks
class Update < BaseMutation
graphql_name 'ReleaseAssetLinkUpdate'
authorize :update_release
ReleaseAssetLinkID = ::Types::GlobalIDType[::Releases::Link]
argument :id, ReleaseAssetLinkID,
required: true,
description: 'ID of the release asset link to update.'
argument :name, GraphQL::STRING_TYPE,
required: false,
description: 'Name of the asset link.'
argument :url, GraphQL::STRING_TYPE,
required: false,
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,
description: 'The type of the asset link.'
field :link,
Types::ReleaseAssetLinkType,
null: true,
description: 'The asset link after mutation.'
def ready?(**args)
if args.key?(:link_type) && args[:link_type].nil?
raise Gitlab::Graphql::Errors::ArgumentError,
'if the linkType argument is provided, it cannot be null'
end
super
end
def resolve(id:, **link_attrs)
link = authorized_find!(id)
unless link.update(link_attrs)
return { link: nil, errors: link.errors.full_messages }
end
{ link: link, errors: [] }
end
def find_object(id)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ReleaseAssetLinkID.coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
...@@ -67,6 +67,7 @@ module Types ...@@ -67,6 +67,7 @@ module Types
mount_mutation Mutations::Releases::Update mount_mutation Mutations::Releases::Update
mount_mutation Mutations::Releases::Delete mount_mutation Mutations::Releases::Delete
mount_mutation Mutations::ReleaseAssetLinks::Create mount_mutation Mutations::ReleaseAssetLinks::Create
mount_mutation Mutations::ReleaseAssetLinks::Update
mount_mutation Mutations::Terraform::State::Delete mount_mutation Mutations::Terraform::State::Delete
mount_mutation Mutations::Terraform::State::Lock mount_mutation Mutations::Terraform::State::Lock
mount_mutation Mutations::Terraform::State::Unlock mount_mutation Mutations::Terraform::State::Unlock
......
---
title: Add GraphQL mutation to update existing release asset link
merge_request: 56265
author:
type: added
...@@ -5019,6 +5019,16 @@ An edge in a connection. ...@@ -5019,6 +5019,16 @@ An edge in a connection.
| `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`ReleaseAssetLink`](#releaseassetlink) | The item at the end of the edge. | | `node` | [`ReleaseAssetLink`](#releaseassetlink) | The item at the end of the edge. |
### `ReleaseAssetLinkUpdatePayload`
Autogenerated return type of ReleaseAssetLinkUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `link` | [`ReleaseAssetLink`](#releaseassetlink) | The asset link after mutation. |
### `ReleaseAssets` ### `ReleaseAssets`
A container for all assets associated with a release. A container for all assets associated with a release.
...@@ -8470,6 +8480,12 @@ A `PrometheusServiceID` is a global ID. It is encoded as a string. ...@@ -8470,6 +8480,12 @@ A `PrometheusServiceID` is a global ID. It is encoded as a string.
An example `PrometheusServiceID` is: `"gid://gitlab/PrometheusService/1"`. An example `PrometheusServiceID` is: `"gid://gitlab/PrometheusService/1"`.
### `ReleasesLinkID`
A `ReleasesLinkID` is a global ID. It is encoded as a string.
An example `ReleasesLinkID` is: `"gid://gitlab/Releases::Link/1"`.
### `SnippetID` ### `SnippetID`
A `SnippetID` is a global ID. It is encoded as a string. A `SnippetID` is a global ID. It is encoded as a string.
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::ReleaseAssetLinks::Update 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_it_be(:name) { 'link name' }
let_it_be(:url) { 'https://example.com/url' }
let_it_be(:filepath) { '/permanent/path' }
let_it_be(:link_type) { 'package' }
let_it_be(:release_link) do
create(:release_link,
release: release,
name: name,
url: url,
filepath: filepath,
link_type: link_type)
end
let(:current_user) { developer }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
let(:mutation_arguments) do
{
id: release_link.to_global_id
}
end
shared_examples 'no changes to the link except for the' do |except_for|
it 'does not change other link properties' do
expect(updated_link.name).to eq(name) unless except_for == :name
expect(updated_link.url).to eq(url) unless except_for == :url
expect(updated_link.filepath).to eq(filepath) unless except_for == :filepath
expect(updated_link.link_type).to eq(link_type) unless except_for == :link_type
end
end
shared_examples 'validation error with messages' do |messages|
it 'returns the updated link as nil' do
expect(updated_link).to be_nil
end
it 'returns a validation error' do
expect(subject[:errors]).to match_array(messages)
end
end
describe '#ready?' do
let(:current_user) { developer }
subject(:ready) do
mutation.ready?(**mutation_arguments)
end
context 'when link_type is included as an argument but is passed nil' do
let(:mutation_arguments) { super().merge(link_type: nil) }
it 'raises a validation error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'if the linkType argument is provided, it cannot be null')
end
end
end
describe '#resolve' do
subject(:resolve) do
mutation.resolve(**mutation_arguments)
end
let(:updated_link) { subject[:link] }
context 'when the current user has access to update the link' do
context 'name' do
let(:mutation_arguments) { super().merge(name: updated_name) }
context 'when a new name is provided' do
let(:updated_name) { 'Updated name' }
it 'updates the name' do
expect(updated_link.name).to eq(updated_name)
end
it_behaves_like 'no changes to the link except for the', :name
end
context 'when nil is provided' do
let(:updated_name) { nil }
it_behaves_like 'validation error with messages', ["Name can't be blank"]
end
end
context 'url' do
let(:mutation_arguments) { super().merge(url: updated_url) }
context 'when a new URL is provided' do
let(:updated_url) { 'https://example.com/updated/link' }
it 'updates the url' do
expect(updated_link.url).to eq(updated_url)
end
it_behaves_like 'no changes to the link except for the', :url
end
context 'when nil is provided' do
let(:updated_url) { nil }
it_behaves_like 'validation error with messages', ["Url can't be blank", "Url must be a valid URL"]
end
end
context 'filepath' do
let(:mutation_arguments) { super().merge(filepath: updated_filepath) }
context 'when a new filepath is provided' do
let(:updated_filepath) { '/updated/filepath' }
it 'updates the filepath' do
expect(updated_link.filepath).to eq(updated_filepath)
end
it_behaves_like 'no changes to the link except for the', :filepath
end
context 'when nil is provided' do
let(:updated_filepath) { nil }
it 'updates the filepath to nil' do
expect(updated_link.filepath).to be_nil
end
end
end
context 'link_type' do
let(:mutation_arguments) { super().merge(link_type: updated_link_type) }
context 'when a new link type is provided' do
let(:updated_link_type) { 'image' }
it 'updates the link type' do
expect(updated_link.link_type).to eq(updated_link_type)
end
it_behaves_like 'no changes to the link except for the', :link_type
end
# Test cases not included:
# - when nil is provided, because this validated by #ready?
# - when an invalid type is provided, because this is validated by the GraphQL schema
end
end
context 'when the current user does not have access to update the link' do
let(:current_user) { reporter }
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context "when the link doesn't exist" do
let(:mutation_arguments) { super().merge(id: 'gid://gitlab/Releases::Link/999999') }
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context "when the provided ID is invalid" do
let(:mutation_arguments) { super().merge(id: 'not-a-valid-gid') }
it 'raises an error' do
expect { subject }.to raise_error(::GraphQL::CoercionError)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an existing release asset link' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private, :repository) }
let_it_be(:release) { create(:release, project: project) }
let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
let_it_be(:release_link) do
create(:release_link,
release: release,
name: 'link name',
url: 'https://example.com/url',
filepath: '/permanent/path',
link_type: 'package')
end
let(:current_user) { developer }
let(:mutation_name) { :release_asset_link_update }
let(:mutation_arguments) do
{
id: release_link.to_global_id.to_s,
name: 'updated name',
url: 'https://example.com/updated',
directAssetPath: '/updated/path',
linkType: 'IMAGE'
}
end
let(:mutation) do
graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS)
link {
id
name
url
linkType
directAssetUrl
external
}
errors
FIELDS
end
let(:update_link) { post_graphql_mutation(mutation, current_user: current_user) }
let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access }
it 'updates and existing release asset link and returns the updated link', :aggregate_failures do
update_link
expected_response = {
id: mutation_arguments[:id],
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
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