Commit 82c334fa authored by Nathan Friend's avatar Nathan Friend Committed by Etienne Baqué

Add GraphQL mutation to create release asset link

This commit adds a new GraphQL mutation - `releaseAssetLinkCreate` -
that creates a new asset link and associates with an existing release.
parent ded6ed7d
# 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
# 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
......@@ -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
......
......@@ -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
# 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
---
title: Add GraphQL mutation to create release asset link
merge_request: 54605
author:
type: added
......@@ -3491,6 +3491,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.
......
......@@ -24731,6 +24731,9 @@ msgstr ""
msgid "Release title"
msgstr ""
msgid "Release with tag \"%{tag}\" was not found"
msgstr ""
msgid "ReleaseAssetLinkType|Image"
msgstr ""
......
# 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
# 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
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