Skip to content
代码片段 群组 项目
提交 11fefb3c 编辑于 作者: Alexandru Croitor's avatar Alexandru Croitor
浏览文件

Add Jira issue finder service

Adding a service for quring Jira REST API and fetching issues to
be presented for jira issues integration.
上级 5d0b22da
No related branches found
No related tags found
无相关合并请求
显示
397 个添加43 个删除
# frozen_string_literal: true
module Projects
module Integrations
module Jira
IntegrationError = Class.new(StandardError)
RequestError = Class.new(StandardError)
class IssuesFinder
attr_reader :issues, :total_count
def initialize(project, params = {})
@project = project
@jira_service = project.jira_service
@page = params[:page].presence || 1
@params = params
end
def execute
return [] unless Feature.enabled?(:jira_integration, project)
raise IntegrationError, _('Jira service not configured.') unless jira_service&.active?
project_key = jira_service.project_key
raise IntegrationError, _('Jira project key is not configured') if project_key.blank?
fetch_issues(project_key)
end
private
attr_reader :project, :jira_service, :page, :params
# rubocop: disable CodeReuse/ServiceClass
def fetch_issues(project_key)
jql = ::Jira::JqlBuilderService.new(project_key, params).execute
response = ::Jira::Requests::Issues::ListService.new(jira_service, { jql: jql, page: page }).execute
if response.success?
@total_count = response.payload[:total_count]
@issues = response.payload[:issues]
else
raise RequestError, response.message
end
end
# rubocop: enable CodeReuse/ServiceClass
end
end
end
end
......@@ -37,7 +37,7 @@ def project
def jira_projects(name:)
args = { query: name }.compact
return Jira::Requests::Projects.new(project.jira_service, args).execute
return Jira::Requests::Projects::ListService.new(project.jira_service, args).execute
end
end
end
......
......@@ -23,7 +23,7 @@ class JiraService < IssueTrackerService
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
data_field :username, :password, :url, :api_url, :jira_issue_transition_id
data_field :username, :password, :url, :api_url, :jira_issue_transition_id, :project_key
before_update :reset_password
......
# frozen_string_literal: true
module Jira
class JqlBuilderService
DEFAULT_SORT = "created"
DEFAULT_SORT_DIRECTION = "DESC"
def initialize(jira_project_key, params = {})
@jira_project_key = jira_project_key
@sort = params[:sort] || DEFAULT_SORT
@sort_direction = params[:sort_direction] || DEFAULT_SORT_DIRECTION
end
def execute
[by_project, order_by].join(' ')
end
private
attr_reader :jira_project_key, :sort, :sort_direction
def by_project
"project = #{jira_project_key}"
end
def order_by
"order by #{sort} #{sort_direction}"
end
end
end
......@@ -5,12 +5,11 @@ module Requests
class Base
include ProjectServicesLoggable
attr_reader :jira_service, :project, :query
JIRA_API_VERSION = 2
def initialize(jira_service, query: nil)
def initialize(jira_service, params = {})
@project = jira_service&.project
@jira_service = jira_service
@query = query
end
def execute
......@@ -19,8 +18,19 @@ def execute
request
end
def base_api_url
"/rest/api/#{api_version}"
end
private
attr_reader :jira_service, :project
# override this method in the specific request class implementation if a differnt API version is required
def api_version
JIRA_API_VERSION
end
def client
@client ||= jira_service.client
end
......
# frozen_string_literal: true
module Jira
module Requests
module Issues
class ListService < Base
extend ::Gitlab::Utils::Override
PER_PAGE = 100
def initialize(jira_service, params = {})
super(jira_service, params)
@jql = params[:jql].to_s
@page = params[:page].to_i || 1
end
private
attr_reader :jql, :page
override :url
def url
"#{base_api_url}/search?jql=#{CGI.escape(jql)}&startAt=#{start_at}&maxResults=#{PER_PAGE}&fields=*all"
end
override :build_service_response
def build_service_response(response)
return ServiceResponse.success(payload: empty_payload) if response.blank? || response["issues"].blank?
ServiceResponse.success(payload: {
issues: map_issues(response["issues"]),
is_last: last?(response),
total_count: response["total"].to_i
})
end
def map_issues(response)
response.map { |v| JIRA::Resource::Issue.build(client, v) }
end
def empty_payload
{ issues: [], is_last: true, total_count: 0 }
end
def last?(response)
response["total"].to_i <= response["startAt"].to_i + response["issues"].size
end
def start_at
(page - 1) * PER_PAGE
end
end
end
end
end
# frozen_string_literal: true
module Jira
module Requests
class Projects < Base
extend ::Gitlab::Utils::Override
private
override :url
def url
'/rest/api/2/project'
end
override :build_service_response
def build_service_response(response)
return ServiceResponse.success(payload: empty_payload) unless response.present?
ServiceResponse.success(payload: { projects: map_projects(response), is_last: true })
end
def map_projects(response)
response.map { |v| JIRA::Resource::Project.build(client, v) }.select(&method(:match_query?))
end
def match_query?(jira_project)
query = self.query.to_s.downcase
jira_project&.key&.downcase&.include?(query) || jira_project&.name&.downcase&.include?(query)
end
def empty_payload
{ projects: [], is_last: true }
end
end
end
end
# frozen_string_literal: true
module Jira
module Requests
module Projects
class ListService < Base
extend ::Gitlab::Utils::Override
def initialize(jira_service, params: {})
super(jira_service, params)
@query = params[:query]
end
private
attr_reader :query
override :url
def url
"#{base_api_url}/project"
end
override :build_service_response
def build_service_response(response)
return ServiceResponse.success(payload: empty_payload) unless response.present?
ServiceResponse.success(payload: { projects: map_projects(response), is_last: true })
end
def map_projects(response)
response.map { |v| JIRA::Resource::Project.build(client, v) }.select(&method(:match_query?))
end
def match_query?(jira_project)
query = query.to_s.downcase
jira_project&.key&.downcase&.include?(query) || jira_project&.name&.downcase&.include?(query)
end
def empty_payload
{ projects: [], is_last: true }
end
end
end
end
end
......@@ -12725,6 +12725,9 @@ msgstr ""
msgid "Jira integration not configured."
msgstr ""
 
msgid "Jira project key is not configured"
msgstr ""
msgid "Jira project: %{importProject}"
msgstr ""
 
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Integrations::Jira::IssuesFinder do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:jira_service, reload: true) { create(:jira_service, project: project) }
let(:params) { {} }
let(:service) { described_class.new(project, params) }
describe '#execute' do
subject(:issues) { service.execute }
context 'when jira_integration feature flag is not enabled' do
before do
stub_feature_flags(jira_integration: false)
end
it 'exits early and returns no issues' do
expect(issues.size).to eq 0
expect(service.total_count).to be_nil
end
end
context 'when jira service integration does not have project_key' do
it 'raises error' do
expect { subject }.to raise_error(Projects::Integrations::Jira::IntegrationError, 'Jira project key is not configured')
end
end
context 'when jira service integration is not active' do
before do
jira_service.update!(active: false)
end
it 'raises error' do
expect { subject }.to raise_error(Projects::Integrations::Jira::IntegrationError, 'Jira service not configured.')
end
end
context 'when jira service integration has project_key' do
let(:params) { {} }
let(:client) { double(options: { site: 'https://jira.example.com' }) }
before do
jira_service.update!(project_key: 'TEST')
expect_next_instance_of(Jira::Requests::Issues::ListService) do |instance|
expect(instance).to receive(:client).at_least(:once).and_return(client)
end
end
context 'when Jira API request fails' do
before do
expect(client).to receive(:get).and_raise(Timeout::Error)
end
it 'raises error', :aggregate_failures do
expect { subject }.to raise_error(Projects::Integrations::Jira::RequestError)
end
end
context 'when Jira API request succeeds' do
before do
expect(client).to receive(:get).and_return(
{
"total" => 375,
"startAt" => 0,
"issues" => [{ "key" => 'TEST-1' }, { "key" => 'TEST-2' }]
}
)
end
it 'return service response with issues', :aggregate_failures do
expect(issues.size).to eq 2
expect(service.total_count).to eq 375
expect(issues.map(&:key)).to eq(%w[TEST-1 TEST-2])
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Jira::JqlBuilderService do
describe '#execute' do
subject { described_class.new('PROJECT_KEY', params).execute }
context 'when no params' do
let(:params) { {} }
it 'builds jql with default ordering' do
expect(subject).to eq("project = PROJECT_KEY order by created DESC")
end
end
context 'with sort params' do
let(:params) { { sort: 'updated', sort_direction: 'ASC' } }
it 'builds jql' do
expect(subject).to eq("project = PROJECT_KEY order by updated ASC")
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Jira::Requests::Issues::ListService do
let(:jira_service) { create(:jira_service) }
let(:params) { {} }
describe '#execute' do
let(:service) { described_class.new(jira_service, params) }
subject { service.execute }
context 'without jira_service' do
before do
jira_service.update!(active: false)
end
it 'returns an error response' do
expect(subject.error?).to be_truthy
expect(subject.message).to eq('Jira service not configured.')
end
end
context 'when jira_service is nil' do
let(:jira_service) { nil }
it 'returns an error response' do
expect(subject.error?).to be_truthy
expect(subject.message).to eq('Jira service not configured.')
end
end
context 'with jira_service' do
context 'when validations and params are ok' do
let(:client) { double(options: { site: 'https://jira.example.com' }) }
before do
expect(service).to receive(:client).at_least(:once).and_return(client)
end
context 'when the request to Jira returns an error' do
before do
expect(client).to receive(:get).and_raise(Timeout::Error)
end
it 'returns an error response' do
expect(subject.error?).to be_truthy
expect(subject.message).to eq('Jira request error: Timeout::Error')
end
end
context 'when the request does not return any values' do
before do
expect(client).to receive(:get).and_return([])
end
it 'returns a paylod with no issues' do
payload = subject.payload
expect(subject.success?).to be_truthy
expect(payload[:issues]).to be_empty
expect(payload[:is_last]).to be_truthy
end
end
context 'when the request returns values' do
before do
expect(client).to receive(:get).and_return(
{
"total" => 375,
"startAt" => 0,
"issues" => [{ "key" => 'TST-1' }, { "key" => 'TST-2' }]
}
)
end
it 'returns a paylod with jira issues' do
payload = subject.payload
expect(subject.success?).to be_truthy
expect(payload[:issues].map(&:key)).to eq(%w[TST-1 TST-2])
expect(payload[:is_last]).to be_falsy
end
end
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Jira::Requests::Projects do
RSpec.describe Jira::Requests::Projects::ListService do
let(:jira_service) { create(:jira_service) }
let(:params) { {} }
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册