Add files param to snippet create mutation

In this commit we add the new `files` param
to the snippet create mutation. At some point,
this field will replace the existing `content`
and `file_name` in the same mutation.
parent d3c5b084
......@@ -21,7 +21,7 @@ module Mutations
description: 'File name of the snippet'
argument :content, GraphQL::STRING_TYPE,
required: true,
required: false,
description: 'Content of the snippet'
argument :description, GraphQL::STRING_TYPE,
......@@ -40,6 +40,10 @@ module Mutations
required: false,
description: 'The paths to files uploaded in the snippet description'
argument :files, [Types::Snippets::FileInputType],
description: "The snippet files to create",
required: false
def resolve(args)
project_path = args.delete(:project_path)
......@@ -49,13 +53,9 @@ module Mutations
raise_resource_not_available_error!
end
# We need to rename `uploaded_files` into `files` because
# it's the expected key param
args[:files] = args.delete(:uploaded_files)
service_response = ::Snippets::CreateService.new(project,
context[:current_user],
args).execute
context[:current_user],
create_params(args)).execute
snippet = service_response.payload[:snippet]
......@@ -82,6 +82,18 @@ module Mutations
def can_create_personal_snippet?
Ability.allowed?(context[:current_user], :create_snippet)
end
def create_params(args)
args.tap do |create_args|
# We need to rename `files` into `snippet_files` because
# it's the expected key param
create_args[:snippet_files] = create_args.delete(:files)&.map(&:to_h)
# We need to rename `uploaded_files` into `files` because
# it's the expected key param
create_args[:files] = create_args.delete(:uploaded_files)
end
end
end
end
end
......@@ -12,7 +12,9 @@ module Snippets
super
@uploaded_assets = Array(@params.delete(:files).presence)
@snippet_files = SnippetInputActionCollection.new(Array(@params.delete(:snippet_files).presence))
input_actions = Array(@params.delete(:snippet_files).presence)
@snippet_files = SnippetInputActionCollection.new(input_actions, allowed_actions: restricted_files_actions)
filter_spam_check_params
end
......@@ -79,5 +81,9 @@ module Snippets
def build_actions_from_params(snippet)
raise NotImplementedError
end
def restricted_files_actions
nil
end
end
end
......@@ -100,5 +100,9 @@ module Snippets
def build_actions_from_params(_snippet)
[{ file_path: params[:file_name], content: params[:content] }]
end
def restricted_files_actions
:create
end
end
end
---
title: Add files argument to snippet create mutation
merge_request: 34449
author:
type: changed
......@@ -1824,7 +1824,7 @@ input CreateSnippetInput {
"""
Content of the snippet
"""
content: String!
content: String
"""
Description of the snippet
......@@ -1836,6 +1836,11 @@ input CreateSnippetInput {
"""
fileName: String
"""
The snippet files to create
"""
files: [SnippetFileInputType!]
"""
The project full path the snippet is associated with
"""
......@@ -11710,6 +11715,41 @@ type SnippetEdge {
node: Snippet
}
"""
Type of a snippet file input action
"""
enum SnippetFileInputActionEnum {
create
delete
move
update
}
"""
Represents an action to perform over a snippet file
"""
input SnippetFileInputType {
"""
Type of input action
"""
action: SnippetFileInputActionEnum!
"""
Snippet file content
"""
content: String
"""
Path of the snippet file
"""
filePath: String!
"""
Previous path of the snippet file
"""
previousPath: String
}
type SnippetPermissions {
"""
Indicates the user can perform `admin_snippet` on this resource
......
......@@ -4825,13 +4825,9 @@
"name": "content",
"description": "Content of the snippet",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
......@@ -4887,6 +4883,24 @@
},
"defaultValue": null
},
{
"name": "files",
"description": "The snippet files to create",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "SnippetFileInputType",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
......@@ -34567,6 +34581,100 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "SnippetFileInputActionEnum",
"description": "Type of a snippet file input action",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "create",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "update",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "delete",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "move",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "SnippetFileInputType",
"description": "Represents an action to perform over a snippet file",
"fields": null,
"inputFields": [
{
"name": "action",
"description": "Type of input action",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SnippetFileInputActionEnum",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "previousPath",
"description": "Previous path of the snippet file",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "filePath",
"description": "Path of the snippet file",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "content",
"description": "Snippet file content",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SnippetPermissions",
......@@ -14,9 +14,8 @@ describe 'Creating a Snippet' do
let(:visibility_level) { 'public' }
let(:project_path) { nil }
let(:uploaded_files) { nil }
let(:mutation) do
variables = {
let(:mutation_vars) do
{
content: content,
description: description,
visibility_level: visibility_level,
......@@ -25,8 +24,10 @@ describe 'Creating a Snippet' do
project_path: project_path,
uploaded_files: uploaded_files
}
end
graphql_mutation(:create_snippet, variables)
let(:mutation) do
graphql_mutation(:create_snippet, mutation_vars)
end
def mutation_response
......@@ -137,6 +138,47 @@ describe 'Creating a Snippet' do
end
end
context 'when snippet is created using the files param' do
let(:action) { :create }
let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
let(:mutation_vars) do
{
description: description,
visibility_level: visibility_level,
project_path: project_path,
title: title,
files: actions
}
end
it 'creates the Snippet' do
expect do
subject
end.to change { Snippet.count }.by(1)
end
it 'returns the created Snippet' do
subject
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
end
context 'when action is invalid' do
let(:file_1) { { filePath: 'example_file1' }}
it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet files have invalid data']
it_behaves_like 'does not create snippet'
end
end
context 'when there are ActiveRecord validation errors' do
let(:title) { '' }
......
......@@ -283,6 +283,19 @@ describe Snippets::CreateService do
expect(snippet.repository.exists?).to be_falsey
end
end
context 'when snippet_files contain an action different from "create"' do
let(:snippet_files) { [{ action: 'delete', file_path: 'snippet_file_path.rb' }] }
it 'a validation error is raised' do
response = subject
snippet = response.payload[:snippet]
expect(response).to be_error
expect(snippet.errors.full_messages_for(:snippet_files)).to eq ['Snippet files have invalid data']
expect(snippet.repository.exists?).to be_falsey
end
end
end
context 'when ProjectSnippet' 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