Commit c69f02df authored by Michał Zając's avatar Michał Zając

Handle team-managed (next-gen) Jira projects

Creating Jira issues from Vulnerabilities will fail if you select a
team-manged Jira project (so called next-gen project). This is because
next-gen projects do not have issue type schemes and trying to fetch
issue types that way will yield issue type IDs that are invalid for a
next-gen project.

This change fetches issue types from the project details endpoint

Changelog: changed
EE: true
parent 5bb160d3
...@@ -108,13 +108,20 @@ module EE ...@@ -108,13 +108,20 @@ module EE
def project_issuetype_ids def project_issuetype_ids
strong_memoize(:project_issuetype_ids) do strong_memoize(:project_issuetype_ids) do
if data_fields.deployment_server? if data_fields.deployment_server?
query_url = Addressable::URI.join("#{client.options[:rest_base_path]}/", 'project/', project_key) project_issuetype_ids_from_project_details
client
.get(query_url.to_s)
.fetch('issueTypes', [])
.map { |issue_type| issue_type['id'] }
elsif data_fields.deployment_cloud? elsif data_fields.deployment_cloud?
# According to https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-type-schemes/#api-rest-api-3-issuetypescheme-project-get
# only classic projects has issue type schemes
next project_issuetype_ids_from_project_details if jira_project.style == "next-gen"
project_issuetype_ids_from_issuetypescheme_mappings
else
raise NotImplementedError
end
end
end
def project_issuetype_ids_from_issuetypescheme_mappings
query_url = Addressable::URI.join("#{client.options[:rest_base_path]}/", 'issuetypescheme/', 'mapping') query_url = Addressable::URI.join("#{client.options[:rest_base_path]}/", 'issuetypescheme/', 'mapping')
query_url.query_values = { 'issueTypeSchemeId' => project_issuetype_scheme_ids } query_url.query_values = { 'issueTypeSchemeId' => project_issuetype_scheme_ids }
...@@ -122,13 +129,18 @@ module EE ...@@ -122,13 +129,18 @@ module EE
.get(query_url.to_s) .get(query_url.to_s)
.fetch('values', []) .fetch('values', [])
.map { |schemes| schemes['issueTypeId'] } .map { |schemes| schemes['issueTypeId'] }
else
raise NotImplementedError
end
end end
def project_issuetype_ids_from_project_details
query_url = Addressable::URI.join("#{client.options[:rest_base_path]}/", 'project/', project_key)
client
.get(query_url.to_s)
.fetch('issueTypes', [])
.map { |issue_type| issue_type['id'] }
end end
# Returns list of available Issue tTpes in selected JIRA Project # Returns list of available Issue types in selected JIRA Project
# #
# @return [Array] the array of objects with JIRA Issuetype ID, Name and Description # @return [Array] the array of objects with JIRA Issuetype ID, Name and Description
def issue_types def issue_types
......
...@@ -103,7 +103,77 @@ RSpec.describe Integrations::Jira do ...@@ -103,7 +103,77 @@ RSpec.describe Integrations::Jira do
end end
context 'when deployment type is cloud' do context 'when deployment type is cloud' do
let(:project_info_result) { { 'id' => '10000' } } let(:project_info_result) do
{
'id' => '10000',
'style' => jira_project_style,
'issueTypes' => project_issue_types
}
end
let(:classic_issuetypes) do
[
{
id: '10004',
description: 'A new feature of the product, which has yet to be developed.',
name: 'New Feature',
untranslatedName: 'New Feature',
subtask: false,
avatarId: 10311
},
{
id: '10001',
description: 'Jira Bug',
name: 'Bug',
untranslatedName: 'Bug',
subtask: false,
avatarId: 10303
},
{
id: '10003',
description: 'A small piece of work thats part of a larger task.',
name: 'Sub-task',
untranslatedName: 'Sub-task',
subtask: true,
avatarId: 10316
}
]
end
let(:nextgen_issuetypes) do
[
{
id: '2137',
description: 'Very new, yes',
name: 'Next Gen Issue Type 1',
untranslatedName: 'Next Gen Issue Type 1',
subtask: false,
avatarId: 10311
},
{
id: '2138',
description: 'Something',
name: 'Next Gen Issue Type 2',
untranslatedName: 'Next Gen Issue Type 2',
subtask: false,
avatarId: 10303
},
{
id: '2139',
description: 'Subtasks? Meh.',
name: 'Next Gen Issue Type 3',
untranslatedName: 'Next Gen Issue Type 3',
subtask: true,
avatarId: 10316
}
]
end
let(:issue_types_response) { classic_issuetypes + nextgen_issuetypes }
context 'when JIRA project style is classic' do
let(:jira_project_style) { 'classic' }
let(:project_issue_types) { classic_issuetypes }
let(:issue_type_scheme_response) do let(:issue_type_scheme_response) do
{ {
...@@ -137,38 +207,8 @@ RSpec.describe Integrations::Jira do ...@@ -137,38 +207,8 @@ RSpec.describe Integrations::Jira do
} }
end end
let(:issue_types_response) do
[
{
id: '10004',
description: 'A new feature of the product, which has yet to be developed.',
name: 'New Feature',
untranslatedName: 'New Feature',
subtask: false,
avatarId: 10311
},
{
id: '10001',
description: 'Jira Bug',
name: 'Bug',
untranslatedName: 'Bug',
subtask: false,
avatarId: 10303
},
{
id: '10003',
description: 'A small piece of work thats part of a larger task.',
name: 'Sub-task',
untranslatedName: 'Sub-task',
subtask: true,
avatarId: 10316
}
]
end
before do before do
WebMock.stub_request(:get, %r{api/2/project/GL}).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).to_return(body: project_info_result.to_json ) WebMock.stub_request(:get, %r{api/2/project/GL}).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).to_return(body: project_info_result.to_json )
WebMock.stub_request(:get, %r{api/2/project/GL\z}).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).to_return(body: { 'id' => '10000' }.to_json, headers: headers)
WebMock.stub_request(:get, %r{api/2/issuetype\z}).to_return(body: issue_types_response.to_json, headers: headers) WebMock.stub_request(:get, %r{api/2/issuetype\z}).to_return(body: issue_types_response.to_json, headers: headers)
WebMock.stub_request(:get, %r{api/2/issuetypescheme/project\?projectId\=10000\z}).to_return(body: issue_type_scheme_response.to_json, headers: headers) WebMock.stub_request(:get, %r{api/2/issuetypescheme/project\?projectId\=10000\z}).to_return(body: issue_type_scheme_response.to_json, headers: headers)
WebMock.stub_request(:get, %r{api/2/issuetypescheme/mapping\?issueTypeSchemeId\=10126\z}).to_return(body: issue_type_mapping_response.to_json, headers: headers) WebMock.stub_request(:get, %r{api/2/issuetypescheme/mapping\?issueTypeSchemeId\=10126\z}).to_return(body: issue_type_mapping_response.to_json, headers: headers)
...@@ -177,6 +217,25 @@ RSpec.describe Integrations::Jira do ...@@ -177,6 +217,25 @@ RSpec.describe Integrations::Jira do
it { is_expected.to eq(success: true, result: { jira: true }, data: { issuetypes: [{ id: '10001', name: 'Bug', description: 'Jira Bug' }] }) } it { is_expected.to eq(success: true, result: { jira: true }, data: { issuetypes: [{ id: '10001', name: 'Bug', description: 'Jira Bug' }] }) }
end end
context 'when JIRA project style is next-gen' do
let(:jira_project_style) { 'next-gen' }
let(:project_issue_types) { nextgen_issuetypes }
let(:expected_data) do
{
issuetypes: nextgen_issuetypes.select { |issue_type| !issue_type[:subtask] }.map { |issue_type| issue_type.slice(*%i[id name description]) }
}
end
before do
WebMock.stub_request(:get, %r{api/2/project/GL}).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).to_return(body: project_info_result.to_json, headers: headers)
WebMock.stub_request(:get, %r{api/2/issuetype\z}).to_return(body: issue_types_response.to_json, headers: headers)
end
it { is_expected.to eq(success: true, result: { jira: true }, data: expected_data) }
end
end
context 'when deployment type is server' do context 'when deployment type is server' do
let(:project_info_result) do let(:project_info_result) 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