Commit aaaf3ac5 authored by Etienne Baqué's avatar Etienne Baqué

Merge branch '294155-backend-jira-issue-detail' into 'master'

Jira Issue detail serializer and entity

See merge request gitlab-org/gitlab!53075
parents 86a5baf4 39bc1501
...@@ -159,8 +159,8 @@ class JiraService < IssueTrackerService ...@@ -159,8 +159,8 @@ class JiraService < IssueTrackerService
# support any events. # support any events.
end end
def find_issue(issue_key) def find_issue(issue_key, options = {})
jira_request { client.Issue.find(issue_key) } jira_request { client.Issue.find(issue_key, options) }
end end
def close_issue(entity, external_issue, current_user) def close_issue(entity, external_issue, current_user)
......
...@@ -65,27 +65,8 @@ module Projects ...@@ -65,27 +65,8 @@ module Projects
end end
def issue_json def issue_json
{ ::Integrations::Jira::IssueDetailSerializer.new
title_html: '<a href="https://jira.reali.sh:8080/projects/FE/issues/FE-2">FE-2</a> The second FE issue on Jira', .represent(project.jira_service.find_issue(params[:id], { expand: 'renderedFields' }), project: project)
description_html: '<a href="https://jira.reali.sh:8080/projects/FE/issues/FE-2">FE-2</a> The second FE issue on Jira',
created_at: 2.hours.ago,
author: {
id: 2,
username: 'justin_ho',
name: 'Justin Ho',
web_url: 'http://127.0.0.1:3000/root',
avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90'
},
labels: [
{
title: 'In Progress',
description: 'Work that is still in progress',
color: '#EBECF0',
text_color: '#283856'
}
],
state: 'opened'
}
end end
def finder def finder
......
# frozen_string_literal: true
module Integrations
module Jira
class IssueDetailEntity < ::Integrations::Jira::IssueEntity
expose :description_html do |jira_issue|
Banzai::Pipeline::GfmPipeline.call(jira_issue.renderedFields['description'], project: nil)[:output].to_html
end
expose :state do |jira_issue|
jira_issue.resolutiondate ? 'closed' : 'opened'
end
end
end
end
# frozen_string_literal: true
module Integrations
module Jira
class IssueDetailSerializer < BaseSerializer
entity ::Integrations::Jira::IssueDetailEntity
end
end
end
...@@ -33,7 +33,6 @@ module Integrations ...@@ -33,7 +33,6 @@ module Integrations
name: name, name: name,
color: '#EBECF0', color: '#EBECF0',
text_color: '#283856' text_color: '#283856'
} }
end end
end end
...@@ -41,7 +40,8 @@ module Integrations ...@@ -41,7 +40,8 @@ module Integrations
expose :author do |jira_issue| expose :author do |jira_issue|
{ {
name: jira_issue.reporter.displayName, name: jira_issue.reporter.displayName,
web_url: author_web_url(jira_issue) web_url: author_web_url(jira_issue),
avatar_url: jira_issue.reporter.avatarUrls['48x48']
} }
end end
......
...@@ -195,6 +195,8 @@ RSpec.describe Projects::Integrations::Jira::IssuesController do ...@@ -195,6 +195,8 @@ RSpec.describe Projects::Integrations::Jira::IssuesController do
end end
context 'when `jira_issues_show_integration` feature is enabled' do context 'when `jira_issues_show_integration` feature is enabled' do
let(:jira_issue) { {} }
before do before do
stub_feature_flags(jira_issues_show_integration: true) stub_feature_flags(jira_issues_show_integration: true)
end end
...@@ -207,15 +209,15 @@ RSpec.describe Projects::Integrations::Jira::IssuesController do ...@@ -207,15 +209,15 @@ RSpec.describe Projects::Integrations::Jira::IssuesController do
end end
it 'returns JSON response' do it 'returns JSON response' do
get :show, params: { namespace_id: project.namespace, project_id: project, id: 1, format: :json } expect_next_found_instance_of(JiraService) do |service|
expect(service).to receive(:find_issue).with('1', { expand: 'renderedFields' }).and_return(jira_issue)
end
expect(response).to have_gitlab_http_status(:ok) expect_next_instance_of(Integrations::Jira::IssueDetailSerializer) do |serializer|
expect(json_response).to include( expect(serializer).to receive(:represent).with(jira_issue, project: project)
'title_html', end
'description_html',
'author', get :show, params: { namespace_id: project.namespace, project_id: project, id: 1, format: :json }
'labels'
)
end end
end end
end end
......
...@@ -20,7 +20,7 @@ RSpec.describe Resolvers::ExternalIssueResolver do ...@@ -20,7 +20,7 @@ RSpec.describe Resolvers::ExternalIssueResolver do
status: double(name: 'To Do'), status: double(name: 'To Do'),
key: 'GV-1', key: 'GV-1',
labels: [], labels: [],
reporter: double(displayName: 'User', accountId: '10000'), reporter: double(displayName: 'User', accountId: '10000', avatarUrls: { '48x48' => 'http://reporter.avatar' }),
assignee: nil, assignee: nil,
client: double(options: { site: 'http://jira.com/' }) client: double(options: { site: 'http://jira.com/' })
) )
...@@ -37,7 +37,8 @@ RSpec.describe Resolvers::ExternalIssueResolver do ...@@ -37,7 +37,8 @@ RSpec.describe Resolvers::ExternalIssueResolver do
'labels' => [], 'labels' => [],
'author' => { 'author' => {
'name' => 'User', 'name' => 'User',
'web_url' => 'http://jira.com/people/10000' 'web_url' => 'http://jira.com/people/10000',
'avatar_url' => 'http://reporter.avatar'
}, },
'assignees' => [], 'assignees' => [],
'web_url' => 'http://jira.com/browse/GV-1', 'web_url' => 'http://jira.com/browse/GV-1',
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::Jira::IssueDetailEntity do
let(:project) { build(:project) }
let(:jira_client) { double(options: { site: 'http://jira.com/' }) }
let(:reporter) do
double(
'displayName' => 'reporter',
'avatarUrls' => { '48x48' => 'http://reporter.avatar' },
'name' => double # Default to Jira Server issue response, Jira Cloud replaces name with accountId,
)
end
let(:jira_issue) do
double(
renderedFields: { 'description' => '<p>Description</p>' },
summary: 'Title',
created: '2020-06-25T15:39:30.000+0000',
updated: '2020-06-26T15:38:32.000+0000',
resolutiondate: '2020-06-27T13:23:51.000+0000',
labels: ['backend'],
reporter: reporter,
assignee: double('displayName' => 'assignee'),
project: double(key: 'GL'),
key: 'GL-5',
client: jira_client,
status: double(name: 'To Do')
)
end
subject { described_class.new(jira_issue, project: project).as_json }
it 'returns the Jira issues attributes' do
expect(subject).to include(
project_id: project.id,
title: 'Title',
description_html: "<p dir=\"auto\">Description</p>",
created_at: '2020-06-25T15:39:30.000+0000'.to_datetime.utc,
updated_at: '2020-06-26T15:38:32.000+0000'.to_datetime.utc,
closed_at: '2020-06-27T13:23:51.000+0000'.to_datetime.utc,
status: 'To Do',
state: 'closed',
labels: [
{
name: 'backend',
color: '#EBECF0',
text_color: '#283856'
}
],
author: hash_including(
name: 'reporter',
avatar_url: 'http://reporter.avatar'
),
assignees: [
{ name: 'assignee' }
],
web_url: 'http://jira.com/browse/GL-5',
references: { relative: 'GL-5' },
external_tracker: 'jira'
)
end
context 'with Jira Server configuration' do
before do
allow(reporter).to receive(:name).and_return('reporter@reporter.com')
end
it 'returns the Jira Server profile URL' do
expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter@reporter.com')
end
context 'and context_path' do
let(:jira_client) { double(options: { site: 'http://jira.com/', context_path: '/jira-sub-path' }) }
it 'returns URLs including context path' do
expect(subject[:author]).to include(web_url: 'http://jira.com/jira-sub-path/secure/ViewProfile.jspa?name=reporter@reporter.com')
expect(subject[:web_url]).to eq('http://jira.com/jira-sub-path/browse/GL-5')
end
end
end
context 'with Jira Cloud configuration' do
before do
allow(reporter).to receive(:accountId).and_return('12345')
end
it 'returns the Jira Cloud profile URL' do
expect(subject[:author]).to include(web_url: 'http://jira.com/people/12345')
end
end
context 'without assignee' do
before do
allow(jira_issue).to receive(:assignee).and_return(nil)
end
it 'returns an empty array' do
expect(subject).to include(assignees: [])
end
end
context 'without labels' do
before do
allow(jira_issue).to receive(:labels).and_return([])
end
it 'returns an empty array' do
expect(subject).to include(labels: [])
end
end
context 'without resolution date' do
before do
allow(jira_issue).to receive(:resolutiondate).and_return(nil)
end
it "returns 'Open' state" do
expect(subject).to include(state: 'opened')
end
end
end
...@@ -8,6 +8,7 @@ RSpec.describe Integrations::Jira::IssueEntity do ...@@ -8,6 +8,7 @@ RSpec.describe Integrations::Jira::IssueEntity do
let(:reporter) do let(:reporter) do
double( double(
'displayName' => 'reporter', 'displayName' => 'reporter',
'avatarUrls' => { '48x48' => 'http://reporter.avatar' },
'name' => double # Default to Jira Server issue response, Jira Cloud replaces name with accountId 'name' => double # Default to Jira Server issue response, Jira Cloud replaces name with accountId
) )
end end
...@@ -47,7 +48,10 @@ RSpec.describe Integrations::Jira::IssueEntity do ...@@ -47,7 +48,10 @@ RSpec.describe Integrations::Jira::IssueEntity do
text_color: '#283856' text_color: '#283856'
} }
], ],
author: hash_including(name: 'reporter'), author: hash_including(
name: 'reporter',
avatar_url: 'http://reporter.avatar'
),
assignees: [ assignees: [
{ name: 'assignee' } { name: 'assignee' }
], ],
......
...@@ -458,6 +458,16 @@ RSpec.describe JiraService do ...@@ -458,6 +458,16 @@ RSpec.describe JiraService do
expect(WebMock).to have_requested(:get, issue_url) expect(WebMock).to have_requested(:get, issue_url)
end end
context 'with options' do
let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields" }
it 'calls the Jira API with the options to get the issue' do
jira_service.find_issue(issue_key, { expand: 'renderedFields' })
expect(WebMock).to have_requested(:get, issue_url)
end
end
end end
describe '#close_issue' do describe '#close_issue' 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