Commit c4514fcc authored by Alexandru Croitor's avatar Alexandru Croitor

Enable starting Jira import through graphql mutation api

Adds mutation for starting a Jira import on a project.
parent 1da119ac
# frozen_string_literal: true
module Mutations
module JiraImport
class Start < BaseMutation
include Mutations::ResolvesProject
graphql_name 'JiraImportStart'
field :jira_import,
Types::JiraImportType,
null: true,
description: 'The Jira import data after mutation'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project to import the Jira project into'
argument :jira_project_key, GraphQL::STRING_TYPE,
required: true,
description: 'Project key of the importer Jira project'
argument :jira_project_name, GraphQL::STRING_TYPE,
required: false,
description: 'Project name of the importer Jira project'
def resolve(project_path:, jira_project_key:)
project = find_project!(project_path: project_path)
raise_resource_not_available_error! unless project
service_response = ::JiraImport::StartImportService
.new(context[:current_user], project, jira_project_key)
.execute
import_data = service_response.payload[:import_data]
{
jira_import: import_data.errors.blank? ? import_data.projects.last : nil,
errors: errors_on_object(import_data)
}
end
private
def find_project!(project_path:)
return unless project_path.present?
authorized_find!(full_path: project_path)
end
def find_object(full_path:)
resolve_project(full_path: full_path)
end
def authorized_resource?(project)
Ability.allowed?(context[:current_user], :admin_project, project)
end
end
end
end
...@@ -39,6 +39,7 @@ module Types ...@@ -39,6 +39,7 @@ module Types
mount_mutation Mutations::Snippets::Update mount_mutation Mutations::Snippets::Update
mount_mutation Mutations::Snippets::Create mount_mutation Mutations::Snippets::Create
mount_mutation Mutations::Snippets::MarkAsSpam mount_mutation Mutations::Snippets::MarkAsSpam
mount_mutation Mutations::JiraImport::Start
end end
end end
......
---
title: Allow to start Jira import through graphql mutation
merge_request: 27684
author:
type: added
...@@ -4155,6 +4155,51 @@ type JiraImportEdge { ...@@ -4155,6 +4155,51 @@ type JiraImportEdge {
node: JiraImport node: JiraImport
} }
"""
Autogenerated input type of JiraImportStart
"""
input JiraImportStartInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Project key of the importer Jira project
"""
jiraProjectKey: String!
"""
Project name of the importer Jira project
"""
jiraProjectName: String
"""
The project to import the Jira project into
"""
projectPath: ID!
}
"""
Autogenerated return type of JiraImportStart
"""
type JiraImportStartPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Reasons why the mutation failed.
"""
errors: [String!]!
"""
The Jira import data after mutation
"""
jiraImport: JiraImport
}
type Label { type Label {
""" """
Background color of the label Background color of the label
...@@ -5180,6 +5225,7 @@ type Mutation { ...@@ -5180,6 +5225,7 @@ type Mutation {
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
markAsSpamSnippet(input: MarkAsSpamSnippetInput!): MarkAsSpamSnippetPayload markAsSpamSnippet(input: MarkAsSpamSnippetInput!): MarkAsSpamSnippetPayload
mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload
mergeRequestSetLabels(input: MergeRequestSetLabelsInput!): MergeRequestSetLabelsPayload mergeRequestSetLabels(input: MergeRequestSetLabelsInput!): MergeRequestSetLabelsPayload
......
...@@ -11810,6 +11810,132 @@ ...@@ -11810,6 +11810,132 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "JiraImportStartInput",
"description": "Autogenerated input type of JiraImportStart",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project to import the Jira project into",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "jiraProjectKey",
"description": "Project key of the importer Jira project",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "jiraProjectName",
"description": "Project name of the importer Jira project",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "JiraImportStartPayload",
"description": "Autogenerated return type of JiraImportStart",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Reasons why the mutation failed.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "jiraImport",
"description": "The Jira import data after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "JiraImport",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Label", "name": "Label",
...@@ -15290,6 +15416,33 @@ ...@@ -15290,6 +15416,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "jiraImportStart",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "JiraImportStartInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "JiraImportStartPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "markAsSpamSnippet", "name": "markAsSpamSnippet",
"description": null, "description": null,
......
...@@ -614,6 +614,16 @@ Autogenerated return type of IssueSetWeight ...@@ -614,6 +614,16 @@ Autogenerated return type of IssueSetWeight
| `scheduledAt` | Time | Timestamp of when the Jira import was created/started | | `scheduledAt` | Time | Timestamp of when the Jira import was created/started |
| `scheduledBy` | User | User that started the Jira import | | `scheduledBy` | User | User that started the Jira import |
## JiraImportStartPayload
Autogenerated return type of JiraImportStart
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `jiraImport` | JiraImport | The Jira import data after mutation |
## Label ## Label
| Name | Type | Description | | Name | Type | Description |
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Starting a Jira Import' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
let(:jira_project_key) { 'AA' }
let(:project_path) { project.full_path }
let(:mutation) do
variables = {
jira_project_key: jira_project_key,
project_path: project_path
}
graphql_mutation(:jira_import_start, variables)
end
def mutation_response
graphql_mutation_response(:jira_import_start)
end
def jira_import
mutation_response['jiraImport']
end
context 'when the user does not have permission' do
before do
stub_feature_flags(jira_issue_import: true)
end
shared_examples 'Jira import does not start' do
it 'does not start the Jira import' do
post_graphql_mutation(mutation, current_user: current_user)
expect(project.reload.import_state).to be nil
expect(project.reload.import_data).to be nil
end
end
context 'with anonymous user' do
let(:current_user) { nil }
it_behaves_like 'Jira import does not start'
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
context 'with user without permissions' do
let(:current_user) { user }
let(:project_path) { project.full_path }
before do
project.add_developer(current_user)
end
it_behaves_like 'Jira import does not start'
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
end
context 'when the user has permission' do
let(:current_user) { user }
before do
project.add_maintainer(current_user)
end
context 'with project' do
context 'when the project path is invalid' do
let(:project_path) { 'foobar' }
it 'returns an an error' do
post_graphql_mutation(mutation, current_user: current_user)
errors = json_response['errors']
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
end
end
context 'when feature jira_issue_import feature flag is disabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'a mutation that returns errors in the response', errors: ['Jira import feature is disabled.']
end
context 'when feature jira_issue_import feature flag is enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when project has no Jira service' do
it_behaves_like 'a mutation that returns errors in the response', errors: ['Jira integration not configured.']
end
context 'when when project has Jira service' do
let!(:service) { create(:jira_service, project: project) }
before do
project.reload
end
context 'when jira_project_key not provided' do
let(:jira_project_key) { '' }
it_behaves_like 'a mutation that returns errors in the response', errors: ['Unable to find Jira project to import data from.']
end
context 'when jira import successfully scheduled' do
it 'schedules a Jira import' do
post_graphql_mutation(mutation, current_user: current_user)
expect(jira_import['jiraProjectKey']).to eq 'AA'
expect(jira_import['scheduledBy']['username']).to eq current_user.username
expect(project.import_state).not_to be nil
expect(project.import_state.status).to eq 'scheduled'
expect(project.import_data.becomes(JiraImportData).projects.last.scheduled_by['user_id']).to eq current_user.id
end
end
end
end
end
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