Commit 5cf2d56c authored by Nikola Milojevic's avatar Nikola Milojevic

Merge branch 'tomquirk/337996-links-in-jira-error-messages' into 'master'

Add links to Jira-related error messages

See merge request gitlab-org/gitlab!72854
parents deee5630 c2f29f4a
...@@ -67,9 +67,19 @@ module Jira ...@@ -67,9 +67,19 @@ module Jira
ServiceResponse.error(message: error_message(e)) ServiceResponse.error(message: error_message(e))
end end
def auth_docs_link_start
auth_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira', anchor: 'authentication-in-jira')
'<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auth_docs_link_url }
end
def config_docs_link_start
config_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/configure')
'<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: config_docs_link_url }
end
def error_message(error) def error_message(error)
reportable_error_message(error) || reportable_error_message(error) ||
s_('JiraRequest|An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.') s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: '</a>'.html_safe }
end end
# Returns a user-facing error message if possible, otherwise `nil`. # Returns a user-facing error message if possible, otherwise `nil`.
...@@ -93,11 +103,11 @@ module Jira ...@@ -93,11 +103,11 @@ module Jira
def reportable_jira_ruby_error_message(error) def reportable_jira_ruby_error_message(error)
case error.message case error.message
when 'Unauthorized' when 'Unauthorized'
s_('JiraRequest|The credentials for accessing Jira are not valid. Check your Jira integration credentials and try again.') s_('JiraRequest|The credentials for accessing Jira are not valid. Check your %{docs_link_start}Jira integration credentials%{docs_link_end} and try again.').html_safe % { docs_link_start: auth_docs_link_start, docs_link_end: '</a>'.html_safe }
when 'Forbidden' when 'Forbidden'
s_('JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your Jira integration credentials and try again.') s_('JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your %{docs_link_start}Jira integration credentials%{docs_link_end} and try again.').html_safe % { docs_link_start: auth_docs_link_start, docs_link_end: '</a>'.html_safe }
when 'Bad Request' when 'Bad Request'
s_('JiraRequest|An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.') s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: '</a>'.html_safe }
when /errorMessages/ when /errorMessages/
jira_ruby_json_error_message(error.message) jira_ruby_json_error_message(error.message)
end end
...@@ -111,7 +121,7 @@ module Jira ...@@ -111,7 +121,7 @@ module Jira
messages = Rails::Html::FullSanitizer.new.sanitize(messages).presence messages = Rails::Html::FullSanitizer.new.sanitize(messages).presence
return unless messages return unless messages
s_('JiraRequest|An error occurred while requesting data from Jira: %{messages}. Check your Jira integration configuration and try again.') % { messages: messages } s_('JiraRequest|An error occurred while requesting data from Jira: %{messages}. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { messages: messages, docs_link_start: config_docs_link_start, docs_link_end: '</a>'.html_safe }
rescue JSON::ParserError rescue JSON::ParserError
end end
end end
......
...@@ -156,7 +156,6 @@ export default { ...@@ -156,7 +156,6 @@ export default {
}, },
]; ];
}, },
getFilteredSearchValue() { getFilteredSearchValue() {
const { labels, search } = this.filterParams || {}; const { labels, search } = this.filterParams || {};
const filteredSearchValue = []; const filteredSearchValue = [];
...@@ -228,12 +227,13 @@ export default { ...@@ -228,12 +227,13 @@ export default {
this.filterParams = filterParams; this.filterParams = filterParams;
}, },
}, },
alertSafeHtmlConfig: { ALLOW_TAGS: ['a'] },
}; };
</script> </script>
<template> <template>
<gl-alert v-if="errorMessage" class="gl-mt-3" variant="danger" :dismissible="false"> <gl-alert v-if="errorMessage" class="gl-mt-3" variant="danger" :dismissible="false">
{{ errorMessage }} <span v-safe-html:[$options.alertSafeHtmlConfig]="errorMessage"></span>
</gl-alert> </gl-alert>
<issuable-list <issuable-list
v-else v-else
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ExternalIssuesListRoot error handling when request fails displays error alert with "API error" when API responds with "["API <a href=\\"gitlab.com\\">error</a>"]" 1`] = `"<span>API <a href=\\"gitlab.com\\">error</a></span>"`;
exports[`ExternalIssuesListRoot error handling when request fails displays error alert with "API error" when API responds with "["API error"]" 1`] = `"<span>API error</span>"`;
exports[`ExternalIssuesListRoot error handling when request fails displays error alert with "API" when API responds with "["API <script src=\\"hax0r.xyz\\">error</script>"]" 1`] = `"<span>API </span>"`;
exports[`ExternalIssuesListRoot error handling when request fails displays error alert with "An error occurred while loading issues" when API responds with "undefined" 1`] = `"<span>An error occurred while loading issues</span>"`;
exports[`ExternalIssuesListRoot when request succeeds renders issuable-list component with correct props 1`] = ` exports[`ExternalIssuesListRoot when request succeeds renders issuable-list component with correct props 1`] = `
Object { Object {
"currentPage": 1, "currentPage": 1,
......
...@@ -63,6 +63,7 @@ describe('ExternalIssuesListRoot', () => { ...@@ -63,6 +63,7 @@ describe('ExternalIssuesListRoot', () => {
const findIssuableList = () => wrapper.findComponent(IssuableList); const findIssuableList = () => wrapper.findComponent(IssuableList);
const findAlert = () => wrapper.findComponent(GlAlert); const findAlert = () => wrapper.findComponent(GlAlert);
const findAlertMessage = () => findAlert().find('span');
const createLabelFilterEvent = (data) => ({ type: 'labels', value: { data } }); const createLabelFilterEvent = (data) => ({ type: 'labels', value: { data } });
const createSearchFilterEvent = (data) => ({ type: 'filtered-search-term', value: { data } }); const createSearchFilterEvent = (data) => ({ type: 'filtered-search-term', value: { data } });
...@@ -319,12 +320,14 @@ describe('ExternalIssuesListRoot', () => { ...@@ -319,12 +320,14 @@ describe('ExternalIssuesListRoot', () => {
describe('when request fails', () => { describe('when request fails', () => {
it.each` it.each`
APIErrors | expectedRenderedErrorMessage APIErrors | expectedRenderedErrorText
${['API error']} | ${'API error'} ${['API error']} | ${'API error'}
${['API <a href="gitlab.com">error</a>']} | ${'API error'}
${['API <script src="hax0r.xyz">error</script>']} | ${'API'}
${undefined} | ${i18n.errorFetchingIssues} ${undefined} | ${i18n.errorFetchingIssues}
`( `(
'displays error alert with "$expectedRenderedErrorMessage" when API responds with "$APIErrors"', 'displays error alert with "$expectedRenderedErrorText" when API responds with "$APIErrors"',
async ({ APIErrors, expectedRenderedErrorMessage }) => { async ({ APIErrors, expectedRenderedErrorText }) => {
jest.spyOn(axios, 'get'); jest.spyOn(axios, 'get');
mock mock
.onGet(mockProvide.issuesFetchPath) .onGet(mockProvide.issuesFetchPath)
...@@ -333,7 +336,8 @@ describe('ExternalIssuesListRoot', () => { ...@@ -333,7 +336,8 @@ describe('ExternalIssuesListRoot', () => {
createComponent(); createComponent();
await waitForPromises(); await waitForPromises();
expectErrorHandling(expectedRenderedErrorMessage); expectErrorHandling(expectedRenderedErrorText);
expect(findAlertMessage().html()).toMatchSnapshot();
}, },
); );
}); });
......
...@@ -19493,19 +19493,19 @@ msgstr "" ...@@ -19493,19 +19493,19 @@ msgstr ""
msgid "JiraRequest|An SSL error occurred while connecting to Jira: %{message}. Try your request again." msgid "JiraRequest|An SSL error occurred while connecting to Jira: %{message}. Try your request again."
msgstr "" msgstr ""
msgid "JiraRequest|An error occurred while requesting data from Jira. Check your Jira integration configuration and try again." msgid "JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again."
msgstr "" msgstr ""
msgid "JiraRequest|An error occurred while requesting data from Jira: %{messages}. Check your Jira integration configuration and try again." msgid "JiraRequest|An error occurred while requesting data from Jira: %{messages}. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again."
msgstr "" msgstr ""
msgid "JiraRequest|The Jira API URL for connecting to Jira is not valid. Check your Jira integration API URL and try again." msgid "JiraRequest|The Jira API URL for connecting to Jira is not valid. Check your Jira integration API URL and try again."
msgstr "" msgstr ""
msgid "JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your Jira integration credentials and try again." msgid "JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your %{docs_link_start}Jira integration credentials%{docs_link_end} and try again."
msgstr "" msgstr ""
msgid "JiraRequest|The credentials for accessing Jira are not valid. Check your Jira integration credentials and try again." msgid "JiraRequest|The credentials for accessing Jira are not valid. Check your %{docs_link_start}Jira integration credentials%{docs_link_end} and try again."
msgstr "" msgstr ""
msgid "JiraService| on branch %{branch_link}" msgid "JiraService| on branch %{branch_link}"
......
...@@ -90,7 +90,10 @@ RSpec.describe Resolvers::Projects::JiraProjectsResolver do ...@@ -90,7 +90,10 @@ RSpec.describe Resolvers::Projects::JiraProjectsResolver do
end end
it 'raises failure error' do it 'raises failure error' do
expect { resolve_jira_projects }.to raise_error('An error occurred while requesting data from Jira: Some failure. Check your Jira integration configuration and try again.') config_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/configure')
docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: config_docs_link_url }
error_message = 'An error occurred while requesting data from Jira: Some failure. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start }
expect { resolve_jira_projects }.to raise_error(error_message)
end end
end end
end end
......
...@@ -26,11 +26,14 @@ RSpec.shared_examples 'a service that handles Jira API errors' do ...@@ -26,11 +26,14 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
expect(subject).to be_a(ServiceResponse) expect(subject).to be_a(ServiceResponse)
expect(subject).to be_error expect(subject).to be_error
expect(subject.message).to include(expected_message) expect(subject.message).to start_with(expected_message)
end end
end end
context 'when the JSON in JIRA::HTTPError is unsafe' do context 'when the JSON in JIRA::HTTPError is unsafe' do
config_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/configure')
let(:docs_link_start) { '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: config_docs_link_url } }
before do before do
stub_client_and_raise(JIRA::HTTPError, error) stub_client_and_raise(JIRA::HTTPError, error)
end end
...@@ -39,7 +42,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do ...@@ -39,7 +42,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
let(:error) { '{"errorMessages":' } let(:error) { '{"errorMessages":' }
it 'returns the default error message' do it 'returns the default error message' do
expect(subject.message).to eq('An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.') error_message = 'An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start }
expect(subject.message).to eq(error_message)
end end
end end
...@@ -47,7 +51,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do ...@@ -47,7 +51,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
let(:error) { '{"errorMessages":["<script>alert(true)</script>foo"]}' } let(:error) { '{"errorMessages":["<script>alert(true)</script>foo"]}' }
it 'sanitizes it' do it 'sanitizes it' do
expect(subject.message).to eq('An error occurred while requesting data from Jira: foo. Check your Jira integration configuration and try again.') error_message = 'An error occurred while requesting data from Jira: foo. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start }
expect(subject.message).to eq(error_message)
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