Commit 824b40be authored by Tom Quirk's avatar Tom Quirk Committed by Nicolò Maria Mezzopera

Use GlAlert for external issues list errors

Changelog: fixed
EE: true
parent cd1ea9b5
<script> <script>
import { GlButton, GlIcon, GlLink, GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import {
GlButton,
GlIcon,
GlLink,
GlSprintf,
GlSafeHtmlDirective as SafeHtml,
GlAlert,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import createFlash from '~/flash';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { import {
IssuableStates, IssuableStates,
...@@ -30,6 +37,7 @@ export default { ...@@ -30,6 +37,7 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
GlSprintf, GlSprintf,
GlAlert,
IssuableList, IssuableList,
ExternalIssuesListEmptyState, ExternalIssuesListEmptyState,
}, },
...@@ -70,6 +78,7 @@ export default { ...@@ -70,6 +78,7 @@ export default {
[IssuableStates.Closed]: 0, [IssuableStates.Closed]: 0,
[IssuableStates.All]: 0, [IssuableStates.All]: 0,
}, },
errorMessage: null,
}; };
}, },
computed: { computed: {
...@@ -173,11 +182,9 @@ export default { ...@@ -173,11 +182,9 @@ export default {
return filteredSearchValue; return filteredSearchValue;
}, },
onExternalIssuesQueryError(error, message) { onExternalIssuesQueryError(error, message) {
createFlash({ this.errorMessage = message || error.message;
message: message || error.message,
captureError: true, Sentry.captureException(error);
error,
});
}, },
onIssuableListClickTab(selectedIssueState) { onIssuableListClickTab(selectedIssueState) {
this.currentPage = 1; this.currentPage = 1;
...@@ -225,7 +232,11 @@ export default { ...@@ -225,7 +232,11 @@ export default {
</script> </script>
<template> <template>
<gl-alert v-if="errorMessage" class="gl-mt-3" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<issuable-list <issuable-list
v-else
:namespace="projectFullPath" :namespace="projectFullPath"
:tabs="$options.IssuableListTabs" :tabs="$options.IssuableListTabs"
:current-tab="currentState" :current-tab="currentState"
......
import { GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { shallowMount, createLocalVue, mount } from '@vue/test-utils'; import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
...@@ -8,7 +10,6 @@ import jiraIssuesResolver from 'ee/integrations/jira/issues_list/graphql/resolve ...@@ -8,7 +10,6 @@ import jiraIssuesResolver from 'ee/integrations/jira/issues_list/graphql/resolve
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { i18n } from '~/issues_list/constants'; import { i18n } from '~/issues_list/constants';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -61,9 +62,21 @@ describe('ExternalIssuesListRoot', () => { ...@@ -61,9 +62,21 @@ describe('ExternalIssuesListRoot', () => {
const mockLabel = 'ecosystem'; const mockLabel = 'ecosystem';
const findIssuableList = () => wrapper.findComponent(IssuableList); const findIssuableList = () => wrapper.findComponent(IssuableList);
const findAlert = () => wrapper.findComponent(GlAlert);
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 } });
const expectErrorHandling = (expectedRenderedErrorMessage) => {
const issuesList = findIssuableList();
const alert = findAlert();
expect(issuesList.exists()).toBe(false);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(expectedRenderedErrorMessage);
expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error));
};
const createComponent = ({ const createComponent = ({
apolloProvider = createMockApolloProvider(), apolloProvider = createMockApolloProvider(),
provide = mockProvide, provide = mockProvide,
...@@ -300,13 +313,17 @@ describe('ExternalIssuesListRoot', () => { ...@@ -300,13 +313,17 @@ describe('ExternalIssuesListRoot', () => {
}); });
describe('error handling', () => { describe('error handling', () => {
beforeEach(() => {
jest.spyOn(Sentry, 'captureException');
});
describe('when request fails', () => { describe('when request fails', () => {
it.each` it.each`
APIErrors | expectedRenderedErrorMessage APIErrors | expectedRenderedErrorMessage
${['API error']} | ${'API error'} ${['API error']} | ${'API error'}
${undefined} | ${i18n.errorFetchingIssues} ${undefined} | ${i18n.errorFetchingIssues}
`( `(
'calls `createFlash` with "$expectedRenderedErrorMessage" when API responds with "$APIErrors"', 'displays error alert with "$expectedRenderedErrorMessage" when API responds with "$APIErrors"',
async ({ APIErrors, expectedRenderedErrorMessage }) => { async ({ APIErrors, expectedRenderedErrorMessage }) => {
jest.spyOn(axios, 'get'); jest.spyOn(axios, 'get');
mock mock
...@@ -316,17 +333,13 @@ describe('ExternalIssuesListRoot', () => { ...@@ -316,17 +333,13 @@ describe('ExternalIssuesListRoot', () => {
createComponent(); createComponent();
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ expectErrorHandling(expectedRenderedErrorMessage);
message: expectedRenderedErrorMessage,
captureError: true,
error: expect.any(Object),
});
}, },
); );
}); });
describe('when GraphQL network error is encountered', () => { describe('when GraphQL network error is encountered', () => {
it('calls `createFlash` correctly with default error message', async () => { it('displays error alert with default error message', async () => {
createComponent({ createComponent({
apolloProvider: createMockApolloProvider({ apolloProvider: createMockApolloProvider({
Query: { Query: {
...@@ -336,35 +349,24 @@ describe('ExternalIssuesListRoot', () => { ...@@ -336,35 +349,24 @@ describe('ExternalIssuesListRoot', () => {
}); });
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ expectErrorHandling(i18n.errorFetchingIssues);
message: i18n.errorFetchingIssues,
captureError: true,
error: expect.any(Object),
});
}); });
}); });
}); });
describe('pagination', () => { describe('pagination', () => {
it.each` it.each`
scenario | issuesListLoadFailed | issues | shouldShowPaginationControls scenario | issues | shouldShowPaginationControls
${'fails'} | ${true} | ${[]} | ${false} ${'returns no issues'} | ${[]} | ${false}
${'returns no issues'} | ${false} | ${[]} | ${false} ${`returns some issues`} | ${mockExternalIssues} | ${true}
${`returns some issues`} | ${false} | ${mockExternalIssues} | ${true}
`( `(
'sets `showPaginationControls` prop to $shouldShowPaginationControls when request $scenario', 'sets `showPaginationControls` prop to $shouldShowPaginationControls when request $scenario',
async ({ issuesListLoadFailed, issues, shouldShowPaginationControls }) => { async ({ issues, shouldShowPaginationControls }) => {
jest.spyOn(axios, 'get'); jest.spyOn(axios, 'get');
mock mock.onGet(mockProvide.issuesFetchPath).replyOnce(httpStatus.OK, issues, {
.onGet(mockProvide.issuesFetchPath) 'x-page': 1,
.replyOnce( 'x-total': issues.length,
issuesListLoadFailed ? httpStatus.INTERNAL_SERVER_ERROR : httpStatus.OK, });
issues,
{
'x-page': 1,
'x-total': issues.length,
},
);
createComponent(); createComponent();
await waitForPromises(); await waitForPromises();
......
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