Commit 5bf41b99 authored by Kerri Miller's avatar Kerri Miller

Merge branch '323487-add-referrer-property-to-jira-links-inside-gitlab' into 'master'

Add Atlassian referrer to Jira links inside GitLab

See merge request gitlab-org/gitlab!63900
parents 46104879 cb4de493
...@@ -12,6 +12,9 @@ module Integrations ...@@ -12,6 +12,9 @@ module Integrations
PROJECTS_PER_PAGE = 50 PROJECTS_PER_PAGE = 50
JIRA_CLOUD_HOST = '.atlassian.net' JIRA_CLOUD_HOST = '.atlassian.net'
ATLASSIAN_REFERRER_GITLAB_COM = { atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ' }.freeze
ATLASSIAN_REFERRER_SELF_MANAGED = { atlOrigin: 'eyJpIjoiYjM0MTA4MzUyYTYxNDVkY2IwMzVjOGQ3ZWQ3NzMwM2QiLCJwIjoianN3LWdpdGxhYlNNLWludCJ9' }.freeze
validates :url, public_url: true, presence: true, if: :activated? validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true validates :api_url, public_url: true, allow_blank: true
validates :username, presence: true, if: :activated? validates :username, presence: true, if: :activated?
...@@ -38,8 +41,6 @@ module Integrations ...@@ -38,8 +41,6 @@ module Integrations
all_details: 2 all_details: 2
} }
alias_method :project_url, :url
# When these are false GitLab does not create cross reference # When these are false GitLab does not create cross reference
# comments on Jira except when an issue gets transitioned. # comments on Jira except when an issue gets transitioned.
def self.supported_events def self.supported_events
...@@ -82,8 +83,8 @@ module Integrations ...@@ -82,8 +83,8 @@ module Integrations
{ {
username: username&.strip, username: username&.strip,
password: password, password: password,
site: URI.join(url, '/').to_s, # Intended to find the root site: URI.join(url, '/').to_s.delete_suffix('/'), # Intended to find the root
context_path: url.path, context_path: (url.path.presence || '/').delete_suffix('/'),
auth_type: :basic, auth_type: :basic,
read_timeout: 120, read_timeout: 120,
use_cookies: true, use_cookies: true,
...@@ -153,12 +154,37 @@ module Integrations ...@@ -153,12 +154,37 @@ module Integrations
] ]
end end
def web_url(path = nil, **params)
return unless url.present?
if Gitlab.com?
params.merge!(ATLASSIAN_REFERRER_GITLAB_COM) unless Gitlab.staging?
else
params.merge!(ATLASSIAN_REFERRER_SELF_MANAGED) unless Gitlab.dev_or_test_env?
end
url = Addressable::URI.parse(self.url)
url.path = url.path.delete_suffix('/')
url.path << "/#{path.delete_prefix('/').delete_suffix('/')}" if path.present?
url.query_values = (url.query_values || {}).merge(params)
url.query_values = nil if url.query_values.empty?
url.to_s
end
override :project_url
def project_url
web_url
end
override :issues_url
def issues_url def issues_url
"#{url}/browse/:id" web_url('browse/:id')
end end
override :new_issue_url
def new_issue_url def new_issue_url
"#{url}/secure/CreateIssue!default.jspa" web_url('secure/CreateIssue!default.jspa')
end end
alias_method :original_url, :url alias_method :original_url, :url
......
...@@ -38,7 +38,11 @@ module EE ...@@ -38,7 +38,11 @@ module EE
def new_issue_url_with_predefined_fields(summary, description) def new_issue_url_with_predefined_fields(summary, description)
escaped_summary = CGI.escape(summary) escaped_summary = CGI.escape(summary)
escaped_description = CGI.escape(description) escaped_description = CGI.escape(description)
"#{url}/secure/CreateIssueDetails!init.jspa?pid=#{jira_project_id}&issuetype=#{vulnerabilities_issuetype}&summary=#{escaped_summary}&description=#{escaped_description}"[0..MAX_URL_LENGTH]
# Put summary and description at the end of the URL in case we need to trim it
web_url('secure/CreateIssueDetails!init.jspa', pid: jira_project_id, issuetype: vulnerabilities_issuetype)
.concat("&summary=#{escaped_summary}&description=#{escaped_description}")
.slice(0..MAX_URL_LENGTH)
end end
def create_issue(summary, description, current_user) def create_issue(summary, description, current_user)
......
...@@ -55,7 +55,7 @@ module Integrations ...@@ -55,7 +55,7 @@ module Integrations
end end
expose :web_url do |jira_issue| expose :web_url do |jira_issue|
"#{base_web_url}/browse/#{jira_issue.key}" project.jira_service.issue_url(jira_issue.key)
end end
expose :gitlab_web_url do |jira_issue| expose :gitlab_web_url do |jira_issue|
...@@ -87,16 +87,12 @@ module Integrations ...@@ -87,16 +87,12 @@ module Integrations
# accountId is only available on Jira Cloud. # accountId is only available on Jira Cloud.
# https://community.atlassian.com/t5/Jira-Questions/How-to-find-account-id-on-jira-on-premise/qaq-p/1168652 # https://community.atlassian.com/t5/Jira-Questions/How-to-find-account-id-on-jira-on-premise/qaq-p/1168652
if user['accountId'].present? if user['accountId'].present?
"#{base_web_url}/people/#{user['accountId']}" project.jira_service.web_url("people/#{user['accountId']}")
else else
"#{base_web_url}/secure/ViewProfile.jspa?name=#{user['name']}" project.jira_service.web_url('secure/ViewProfile.jspa', name: user['name'])
end end
end end
def base_web_url
@base_web_url ||= project.jira_service.url
end
def project def project
@project ||= options[:project] @project ||= options[:project]
end end
......
...@@ -14,7 +14,7 @@ module Banzai ...@@ -14,7 +14,7 @@ module Banzai
doc.xpath(XPATH).each do |img| doc.xpath(XPATH).each do |img|
next unless img['src'].start_with?(PRIVATE_IMAGE_PATH) next unless img['src'].start_with?(PRIVATE_IMAGE_PATH)
img_link = "#{project.jira_service.url}#{img['src']}" img_link = project.jira_service.web_url(img['src'])
link = "<a class=\"#{CSS_WITH_ATTACHMENT_ICON}\" href=\"#{img_link}\">#{img_link}</a>" link = "<a class=\"#{CSS_WITH_ATTACHMENT_ICON}\" href=\"#{img_link}\">#{img_link}</a>"
img.replace(link) img.replace(link)
......
...@@ -19,7 +19,7 @@ RSpec.describe 'Jira issues list' do ...@@ -19,7 +19,7 @@ RSpec.describe 'Jira issues list' do
stub_licensed_features(jira_issues_integration: false) stub_licensed_features(jira_issues_integration: false)
end end
it 'renders "Create new issue" button' do it 'does not render "Create new issue" button' do
visit project_integrations_jira_issues_path(project) visit project_integrations_jira_issues_path(project)
expect(page).to have_gitlab_http_status(:not_found) expect(page).to have_gitlab_http_status(:not_found)
...@@ -30,6 +30,21 @@ RSpec.describe 'Jira issues list' do ...@@ -30,6 +30,21 @@ RSpec.describe 'Jira issues list' do
it 'renders "Create new issue" button', :js do it 'renders "Create new issue" button', :js do
visit project_integrations_jira_issues_path(project) visit project_integrations_jira_issues_path(project)
expect(page).to have_link('Create new issue in Jira') expect(page).to have_link('Create new issue in Jira', href: "#{jira_integration.url}/secure/CreateIssue!default.jspa")
end
context 'on gitlab.com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
it 'includes the Atlassian referrer in Jira links', :js do
visit project_integrations_jira_issues_path(project)
referrer = Integrations::Jira::ATLASSIAN_REFERRER_GITLAB_COM.to_query
expect(page).to have_link('Open Jira', href: "#{jira_integration.url}?#{referrer}")
expect(page).to have_link('Create new issue in Jira', href: "#{jira_integration.url}/secure/CreateIssue!default.jspa?#{referrer}"), count: 2
end
end end
end end
...@@ -11,12 +11,18 @@ RSpec.describe Banzai::Filter::JiraPrivateImageLinkFilter do ...@@ -11,12 +11,18 @@ RSpec.describe Banzai::Filter::JiraPrivateImageLinkFilter do
context 'with a Jira private image' do context 'with a Jira private image' do
let(:img_link) { '/secure/attachment/10017/10017_jira-logo.jpg' } let(:img_link) { '/secure/attachment/10017/10017_jira-logo.jpg' }
let(:doc) { filter("<img src=\"#{img_link}\">", context) }
it 'replaces the Jira private images with the link to the image' do it 'replaces the Jira private images with the link to the image' do
doc = filter("<img src=\"#{img_link}\">", context)
expect(doc.to_html).to eq("<a class=\"with-attachment-icon\" href=\"#{jira_service.url}#{img_link}\">#{jira_service.url}#{img_link}</a>") expect(doc.to_html).to eq("<a class=\"with-attachment-icon\" href=\"#{jira_service.url}#{img_link}\">#{jira_service.url}#{img_link}</a>")
end end
it 'includes the Atlassian referrer on gitlab.com' do
allow(Gitlab).to receive(:com?).and_return(true)
referrer = Integrations::Jira::ATLASSIAN_REFERRER_GITLAB_COM.to_query
expect(doc.to_html).to include("?#{referrer}")
end
end end
context 'with other image' do context 'with other image' do
......
...@@ -366,7 +366,7 @@ RSpec.describe Integrations::Jira do ...@@ -366,7 +366,7 @@ RSpec.describe Integrations::Jira do
allow(jira_service).to receive(:vulnerabilities_issuetype).and_return('10001') allow(jira_service).to receive(:vulnerabilities_issuetype).and_return('10001')
end end
let(:expected_new_issue_url) { "#{jira_service.url}/secure/CreateIssueDetails!init.jspa?pid=11223&issuetype=10001&summary=Special+Summary%21%3F&description=%2AID%2A%3A+2%0A_Issue_%3A+%21" } let(:expected_new_issue_url) { "#{jira_service.url}/secure/CreateIssueDetails!init.jspa?issuetype=10001&pid=11223&summary=Special+Summary%21%3F&description=%2AID%2A%3A+2%0A_Issue_%3A+%21" }
subject(:new_issue_url) { jira_service.new_issue_url_with_predefined_fields("Special Summary!?", "*ID*: 2\n_Issue_: !") } subject(:new_issue_url) { jira_service.new_issue_url_with_predefined_fields("Special Summary!?", "*ID*: 2\n_Issue_: !") }
......
...@@ -122,9 +122,9 @@ RSpec.describe Integrations::JiraSerializers::IssueDetailEntity do ...@@ -122,9 +122,9 @@ RSpec.describe Integrations::JiraSerializers::IssueDetailEntity do
context 'with Jira Server configuration' do context 'with Jira Server configuration' do
it 'returns the Jira Server profile URL' do 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') expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter%40reporter.com')
expect(subject[:assignees].first).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=assignee@assignee.com') expect(subject[:assignees].first).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=assignee%40assignee.com')
expect(subject[:comments].first[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=comment@author.com') expect(subject[:comments].first[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=comment%40author.com')
end end
context 'with only url' do context 'with only url' do
...@@ -134,7 +134,7 @@ RSpec.describe Integrations::JiraSerializers::IssueDetailEntity do ...@@ -134,7 +134,7 @@ RSpec.describe Integrations::JiraSerializers::IssueDetailEntity do
end end
it 'returns URLs with the web url' do it 'returns URLs with the web url' do
expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter@reporter.com') expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter%40reporter.com')
expect(subject[:web_url]).to eq('http://jira.com/browse/GL-5') expect(subject[:web_url]).to eq('http://jira.com/browse/GL-5')
end end
end end
......
...@@ -78,8 +78,17 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do ...@@ -78,8 +78,17 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do
context 'with Jira Server configuration' do context 'with Jira Server configuration' do
it 'returns the Jira Server profile URL' do 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') expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter%40reporter.com')
expect(subject[:assignees].first).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=assignee@assignee.com') expect(subject[:assignees].first).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=assignee%40assignee.com')
end
it 'includes the Atlassian referrer on gitlab.com' do
allow(Gitlab).to receive(:com?).and_return(true)
referrer = Integrations::Jira::ATLASSIAN_REFERRER_GITLAB_COM.to_query
expect(subject[:web_url]).to eq("http://jira.com/browse/GL-5?#{referrer}")
expect(subject[:author]).to include(web_url: "http://jira.com/secure/ViewProfile.jspa?#{referrer}&name=reporter%40reporter.com")
expect(subject[:assignees].first).to include(web_url: "http://jira.com/secure/ViewProfile.jspa?#{referrer}&name=assignee%40assignee.com")
end end
context 'with only url' do context 'with only url' do
...@@ -89,7 +98,7 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do ...@@ -89,7 +98,7 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do
end end
it 'returns URLs with the web url' do it 'returns URLs with the web url' do
expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter@reporter.com') expect(subject[:author]).to include(web_url: 'http://jira.com/secure/ViewProfile.jspa?name=reporter%40reporter.com')
expect(subject[:web_url]).to eq('http://jira.com/browse/GL-5') expect(subject[:web_url]).to eq('http://jira.com/browse/GL-5')
end end
end end
...@@ -105,6 +114,15 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do ...@@ -105,6 +114,15 @@ RSpec.describe Integrations::JiraSerializers::IssueEntity do
expect(subject[:author]).to include(web_url: 'http://jira.com/people/12345') expect(subject[:author]).to include(web_url: 'http://jira.com/people/12345')
expect(subject[:assignees].first).to include(web_url: 'http://jira.com/people/67890') expect(subject[:assignees].first).to include(web_url: 'http://jira.com/people/67890')
end end
it 'includes the Atlassian referrer on gitlab.com' do
allow(Gitlab).to receive(:com?).and_return(true)
referrer = Integrations::Jira::ATLASSIAN_REFERRER_GITLAB_COM.to_query
expect(subject[:web_url]).to eq("http://jira.com/browse/GL-5?#{referrer}")
expect(subject[:author]).to include(web_url: "http://jira.com/people/12345?#{referrer}")
expect(subject[:assignees].first).to include(web_url: "http://jira.com/people/67890?#{referrer}")
end
end end
context 'without assignee' do context 'without assignee' do
......
...@@ -35,20 +35,42 @@ RSpec.describe Integrations::Jira do ...@@ -35,20 +35,42 @@ RSpec.describe Integrations::Jira do
username: 'username', username: 'username',
password: 'test', password: 'test',
jira_issue_transition_id: 24, jira_issue_transition_id: 24,
url: 'http://jira.test.com/path/' url: 'http://jira.test.com:1234/path/'
} }
end end
let(:service) { described_class.create!(options) } let(:integration) { described_class.create!(options) }
it 'sets the URL properly' do it 'sets the URL properly' do
# jira-ruby gem parses the URI and handles trailing slashes fine: # jira-ruby gem parses the URI and handles trailing slashes fine:
# https://github.com/sumoheavy/jira-ruby/blob/v1.7.0/lib/jira/http_client.rb#L62 # https://github.com/sumoheavy/jira-ruby/blob/v1.7.0/lib/jira/http_client.rb#L62
expect(service.options[:site]).to eq('http://jira.test.com/') expect(integration.options[:site]).to eq('http://jira.test.com:1234')
end end
it 'leaves out trailing slashes in context' do it 'leaves out trailing slashes in context' do
expect(service.options[:context_path]).to eq('/path') expect(integration.options[:context_path]).to eq('/path')
end
context 'URL without a path' do
before do
integration.url = 'http://jira.test.com/'
end
it 'leaves out trailing slashes in context' do
expect(integration.options[:site]).to eq('http://jira.test.com')
expect(integration.options[:context_path]).to eq('')
end
end
context 'URL with query string parameters' do
before do
integration.url << '?nosso&foo=bar'
end
it 'removes query string parameters' do
expect(integration.options[:site]).to eq('http://jira.test.com:1234')
expect(integration.options[:context_path]).to eq('/path')
end
end end
context 'username with trailing whitespaces' do context 'username with trailing whitespaces' do
...@@ -57,13 +79,13 @@ RSpec.describe Integrations::Jira do ...@@ -57,13 +79,13 @@ RSpec.describe Integrations::Jira do
end end
it 'leaves out trailing whitespaces in username' do it 'leaves out trailing whitespaces in username' do
expect(service.options[:username]).to eq('username') expect(integration.options[:username]).to eq('username')
end end
end end
it 'provides additional cookies to allow basic auth with oracle webgate' do it 'provides additional cookies to allow basic auth with oracle webgate' do
expect(service.options[:use_cookies]).to eq(true) expect(integration.options[:use_cookies]).to eq(true)
expect(service.options[:additional_cookies]).to eq(['OBBasicAuth=fromDialog']) expect(integration.options[:additional_cookies]).to eq(['OBBasicAuth=fromDialog'])
end end
context 'using api URL' do context 'using api URL' do
...@@ -72,7 +94,7 @@ RSpec.describe Integrations::Jira do ...@@ -72,7 +94,7 @@ RSpec.describe Integrations::Jira do
end end
it 'leaves out trailing slashes in context' do it 'leaves out trailing slashes in context' do
expect(service.options[:context_path]).to eq('/api_path') expect(integration.options[:context_path]).to eq('/api_path')
end end
end end
end end
...@@ -990,17 +1012,51 @@ RSpec.describe Integrations::Jira do ...@@ -990,17 +1012,51 @@ RSpec.describe Integrations::Jira do
end end
context 'generating external URLs' do context 'generating external URLs' do
let(:service) { described_class.new(url: 'http://jira.test.com/path/') } let(:integration) { described_class.new(url: 'http://jira.test.com/path/') }
describe '#web_url' do
it 'handles paths, slashes, and query string' do
expect(integration.web_url).to eq(integration.url)
expect(integration.web_url('subpath/')).to eq('http://jira.test.com/path/subpath')
expect(integration.web_url('/subpath/')).to eq('http://jira.test.com/path/subpath')
expect(integration.web_url('subpath', foo: :bar)).to eq('http://jira.test.com/path/subpath?foo=bar')
end
it 'preserves existing query string' do
integration.url = 'http://jira.test.com/path/?nosso&foo=bar%20bar'
expect(integration.web_url).to eq("http://jira.test.com/path?foo=bar%20bar&nosso")
expect(integration.web_url('subpath/')).to eq('http://jira.test.com/path/subpath?foo=bar%20bar&nosso')
expect(integration.web_url('/subpath/')).to eq('http://jira.test.com/path/subpath?foo=bar%20bar&nosso')
expect(integration.web_url('subpath', bar: 'baz baz')).to eq('http://jira.test.com/path/subpath?bar=baz%20baz&foo=bar%20bar&nosso')
end
it 'includes Atlassian referrer for gitlab.com' do
allow(Gitlab).to receive(:com?).and_return(true)
expect(integration.web_url).to eq("http://jira.test.com/path?#{described_class::ATLASSIAN_REFERRER_GITLAB_COM.to_query}")
allow(Gitlab).to receive(:staging?).and_return(true)
expect(integration.web_url).to eq(integration.url)
end
it 'includes Atlassian referrer for self-managed' do
allow(Gitlab).to receive(:dev_or_test_env?).and_return(false)
expect(integration.web_url).to eq("http://jira.test.com/path?#{described_class::ATLASSIAN_REFERRER_SELF_MANAGED.to_query}")
end
end
describe '#issues_url' do describe '#issues_url' do
it 'handles trailing slashes' do it 'returns the correct URL' do
expect(service.issues_url).to eq('http://jira.test.com/path/browse/:id') expect(integration.issues_url).to eq('http://jira.test.com/path/browse/:id')
end end
end end
describe '#new_issue_url' do describe '#new_issue_url' do
it 'handles trailing slashes' do it 'returns the correct URL' do
expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa') expect(integration.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa')
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