diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 4a8f83458f48533be5fc6427789271155f614910..f6d6004ba967aa0cf94a3119a1445d627dfb40ab 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -2,12 +2,13 @@ import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql'; import { createAlert } from '~/flash'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { __ } from '~/locale'; import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT, COMMIT_BATCH_SIZE, + GITALY_UNAVAILABLE_CODE, + i18n, } from '../constants'; import getRefMixin from '../mixins/get_ref'; import projectPathQuery from '../queries/project_path.query.graphql'; @@ -17,6 +18,7 @@ import FilePreview from './preview/index.vue'; import FileTable from './table/index.vue'; export default { + i18n, components: { FileTable, FilePreview, @@ -142,10 +144,19 @@ export default { } }) .catch((error) => { + let gitalyUnavailableError; + if (error.graphQLErrors) { + gitalyUnavailableError = error.graphQLErrors.find( + (e) => e?.extensions?.code === GITALY_UNAVAILABLE_CODE, + ); + } + const message = gitalyUnavailableError + ? this.$options.i18n.gitalyError + : this.$options.i18n.generalError; createAlert({ - message: __('An error occurred while fetching folder content.'), + message, + captureError: true, }); - throw error; }); }, normalizeData(key, data) { diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index e194bddcc5616ba77cd4c7b6c16a00e014b8d173..5098053c4f763bb20f2b84c5fa78d7cbf3595a7c 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -1,5 +1,6 @@ import { __ } from '~/locale'; +export const GITALY_UNAVAILABLE_CODE = 'unavailable'; export const TREE_PAGE_LIMIT = 1000; // the maximum amount of items per page export const TREE_PAGE_SIZE = 100; // the amount of items to be fetched per (batch) request export const TREE_INITIAL_FETCH_COUNT = TREE_PAGE_LIMIT / TREE_PAGE_SIZE; // the amount of (batch) requests to make @@ -100,3 +101,8 @@ export const LEGACY_FILE_TYPES = [ 'cargo_toml', 'go_mod', ]; + +export const i18n = { + generalError: __('An error occurred while fetching folder content.'), + gitalyError: __('Error: Gitaly is unavailable. Contact your administrator.'), +}; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 610503697eb7715c60abe608f130ef38d3453510..aec6f2944b159b1ac95ce19c9f87eff1f3bd1eee 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16068,6 +16068,9 @@ msgstr "" msgid "Error: Couldn't load some or all of the changes." msgstr "" +msgid "Error: Gitaly is unavailable. Contact your administrator." +msgstr "" + msgid "Error: No AWS credentials were supplied" msgstr "" diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js index 6eea66f1a7d12295ece5949d27ac9e612bbeb461..f694c8e916638d7f0d6cad543d62e3a2e05c7799 100644 --- a/spec/frontend/repository/components/tree_content_spec.js +++ b/spec/frontend/repository/components/tree_content_spec.js @@ -5,19 +5,25 @@ import FilePreview from '~/repository/components/preview/index.vue'; import FileTable from '~/repository/components/table/index.vue'; import TreeContent from 'jh_else_ce/repository/components/tree_content.vue'; import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createAlert } from '~/flash'; +import { i18n } from '~/repository/constants'; +import { graphQLErrors } from '../mock_data'; jest.mock('~/repository/commits_service', () => ({ loadCommits: jest.fn(() => Promise.resolve()), isRequested: jest.fn(), resetRequestedCommits: jest.fn(), })); +jest.mock('~/flash'); let vm; let $apollo; +const mockResponse = jest.fn().mockReturnValue(Promise.resolve({ data: {} })); -function factory(path, data = () => ({})) { +function factory(path, appoloMockResponse = mockResponse) { $apollo = { - query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })), + query: appoloMockResponse, }; vm = shallowMount(TreeContent, { @@ -222,4 +228,17 @@ describe('Repository table component', () => { expect(loadCommits.mock.calls).toEqual([['', path, '', 0]]); }); }); + + describe('error handling', () => { + const gitalyError = { graphQLErrors }; + it.each` + error | message + ${gitalyError} | ${i18n.gitalyError} + ${'Error'} | ${i18n.generalError} + `('should show an expected error', async ({ error, message }) => { + factory('/', jest.fn().mockRejectedValue(error)); + await waitForPromises(); + expect(createAlert).toHaveBeenCalledWith({ message, captureError: true }); + }); + }); }); diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js index c1b5f89c37f3bdb44292efdf1df610690573f1e1..28dd19c29e6cfb210fbe042319a31a77c99d8f7f 100644 --- a/spec/frontend/repository/mock_data.js +++ b/spec/frontend/repository/mock_data.js @@ -108,3 +108,12 @@ export const blobControlsDataMock = { }, }, }; + +export const graphQLErrors = [ + { + message: '14:failed to connect to all addresses.', + locations: [{ line: 16, column: 7 }], + path: ['project', 'repository', 'paginatedTree'], + extensions: { code: 'unavailable', gitaly_code: 14, service: 'git' }, + }, +];