Commit 6f7c8654 authored by Miguel Rincon's avatar Miguel Rincon

Improve error messages when blob is not loaded

This change improves usability for users when the yml file
is not defined, it does this by changing the error message
and hides controls that don't work in the editor.
parent 4adbe593
...@@ -3,6 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui'; ...@@ -3,6 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import httpStatusCodes from '~/lib/utils/http_status';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import CiLint from './components/lint/ci_lint.vue'; import CiLint from './components/lint/ci_lint.vue';
...@@ -23,7 +24,6 @@ const COMMIT_FAILURE = 'COMMIT_FAILURE'; ...@@ -23,7 +24,6 @@ const COMMIT_FAILURE = 'COMMIT_FAILURE';
const COMMIT_SUCCESS = 'COMMIT_SUCCESS'; const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
const DEFAULT_FAILURE = 'DEFAULT_FAILURE'; const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE'; const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
const LOAD_FAILURE_NO_REF = 'LOAD_FAILURE_NO_REF';
const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN'; const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export default { export default {
...@@ -125,6 +125,9 @@ export default { ...@@ -125,6 +125,9 @@ export default {
isBlobContentLoading() { isBlobContentLoading() {
return this.$apollo.queries.content.loading; return this.$apollo.queries.content.loading;
}, },
isBlobContentError() {
return this.failureType === LOAD_FAILURE_NO_FILE || this.failureType === LOAD_FAILURE_UNKNOWN;
},
isCiConfigDataLoading() { isCiConfigDataLoading() {
return this.$apollo.queries.ciConfigData.loading; return this.$apollo.queries.ciConfigData.loading;
}, },
...@@ -144,14 +147,11 @@ export default { ...@@ -144,14 +147,11 @@ export default {
}, },
failure() { failure() {
switch (this.failureType) { switch (this.failureType) {
case LOAD_FAILURE_NO_REF:
return {
text: this.$options.alertTexts[LOAD_FAILURE_NO_REF],
variant: 'danger',
};
case LOAD_FAILURE_NO_FILE: case LOAD_FAILURE_NO_FILE:
return { return {
text: this.$options.alertTexts[LOAD_FAILURE_NO_FILE], text: sprintf(this.$options.alertTexts[LOAD_FAILURE_NO_FILE], {
filePath: this.ciConfigPath,
}),
variant: 'danger', variant: 'danger',
}; };
case LOAD_FAILURE_UNKNOWN: case LOAD_FAILURE_UNKNOWN:
...@@ -182,9 +182,8 @@ export default { ...@@ -182,9 +182,8 @@ export default {
[COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
[COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'),
[DEFAULT_FAILURE]: __('Something went wrong on our end.'), [DEFAULT_FAILURE]: __('Something went wrong on our end.'),
[LOAD_FAILURE_NO_FILE]: s__('Pipelines|No CI file found in this repository, please add one.'), [LOAD_FAILURE_NO_FILE]: s__(
[LOAD_FAILURE_NO_REF]: s__( 'Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again.',
'Pipelines|Repository does not have a default branch, please set one.',
), ),
[LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), [LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'),
}, },
...@@ -193,12 +192,13 @@ export default { ...@@ -193,12 +192,13 @@ export default {
const { networkError } = error; const { networkError } = error;
const { response } = networkError; const { response } = networkError;
if (response?.status === 404) { // 404 for missing CI file
// 404 for missing CI file // 400 for blank projects with no repository
if (
response?.status === httpStatusCodes.NOT_FOUND ||
response?.status === httpStatusCodes.BAD_REQUEST
) {
this.reportFailure(LOAD_FAILURE_NO_FILE); this.reportFailure(LOAD_FAILURE_NO_FILE);
} else if (response?.status === 400) {
// 400 for a missing ref when no default branch is set
this.reportFailure(LOAD_FAILURE_NO_REF);
} else { } else {
this.reportFailure(LOAD_FAILURE_UNKNOWN); this.reportFailure(LOAD_FAILURE_UNKNOWN);
} }
...@@ -299,9 +299,9 @@ export default { ...@@ -299,9 +299,9 @@ export default {
<li v-for="reason in failureReasons" :key="reason">{{ reason }}</li> <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
</ul> </ul>
</gl-alert> </gl-alert>
<div class="gl-mt-4"> <gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" /> <div v-else-if="!isBlobContentError" class="gl-mt-4">
<div v-else class="file-editor gl-mb-3"> <div class="file-editor gl-mb-3">
<div class="info-well gl-display-none gl-display-sm-block"> <div class="info-well gl-display-none gl-display-sm-block">
<validation-segment <validation-segment
class="well-segment" class="well-segment"
......
...@@ -20790,9 +20790,6 @@ msgstr "" ...@@ -20790,9 +20790,6 @@ msgstr ""
msgid "Pipelines|More Information" msgid "Pipelines|More Information"
msgstr "" msgstr ""
msgid "Pipelines|No CI file found in this repository, please add one."
msgstr ""
msgid "Pipelines|No triggers have been created yet. Add one using the form above." msgid "Pipelines|No triggers have been created yet. Add one using the form above."
msgstr "" msgstr ""
...@@ -20805,9 +20802,6 @@ msgstr "" ...@@ -20805,9 +20802,6 @@ msgstr ""
msgid "Pipelines|Project cache successfully reset." msgid "Pipelines|Project cache successfully reset."
msgstr "" msgstr ""
msgid "Pipelines|Repository does not have a default branch, please set one."
msgstr ""
msgid "Pipelines|Revoke" msgid "Pipelines|Revoke"
msgstr "" msgstr ""
...@@ -20829,6 +20823,9 @@ msgstr "" ...@@ -20829,6 +20823,9 @@ msgstr ""
msgid "Pipelines|There are currently no pipelines." msgid "Pipelines|There are currently no pipelines."
msgstr "" msgstr ""
msgid "Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again."
msgstr ""
msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team." msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
msgstr "" msgstr ""
......
...@@ -5,6 +5,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -5,6 +5,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
import httpStatusCodes from '~/lib/utils/http_status';
import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
import { import {
mockCiConfigPath, mockCiConfigPath,
...@@ -414,58 +415,81 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -414,58 +415,81 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
}); });
it('no error is shown when data is set', async () => { describe('when file exists', () => {
createComponentWithApollo(); beforeEach(async () => {
createComponentWithApollo();
await waitForPromises(); await waitForPromises();
});
expect(findAlert().exists()).toBe(false); it('shows editor and commit form', () => {
expect(findEditorLite().attributes('value')).toBe(mockCiYml); expect(findEditorLite().exists()).toBe(true);
}); expect(findTextEditor().exists()).toBe(true);
});
it('ci config query is called with correct variables', async () => { it('no error is shown when data is set', async () => {
createComponentWithApollo(); expect(findAlert().exists()).toBe(false);
expect(findEditorLite().attributes('value')).toBe(mockCiYml);
});
await waitForPromises(); it('ci config query is called with correct variables', async () => {
createComponentWithApollo();
expect(mockCiConfigData).toHaveBeenCalledWith({ await waitForPromises();
content: mockCiYml,
projectPath: mockProjectFullPath, expect(mockCiConfigData).toHaveBeenCalledWith({
content: mockCiYml,
projectPath: mockProjectFullPath,
});
}); });
}); });
it('shows a 404 error message', async () => { describe('when no file exists', () => {
mockBlobContentData.mockRejectedValueOnce({ const expectedAlertMsg =
response: { 'There is no .gitlab-ci.yml file in this repository, please add one and visit the Pipeline Editor again.';
status: 404,
}, it('does not show editor or commit form', async () => {
mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
createComponentWithApollo();
await waitForPromises();
expect(findEditorLite().exists()).toBe(false);
expect(findTextEditor().exists()).toBe(false);
}); });
createComponentWithApollo();
await waitForPromises(); it('shows a 404 error message', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
},
});
createComponentWithApollo();
expect(findAlert().text()).toBe('No CI file found in this repository, please add one.'); await waitForPromises();
});
it('shows a 400 error message', async () => { expect(findAlert().text()).toBe(expectedAlertMsg);
mockBlobContentData.mockRejectedValueOnce({
response: {
status: 400,
},
}); });
createComponentWithApollo();
await waitForPromises(); it('shows a 400 error message', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.BAD_REQUEST,
},
});
createComponentWithApollo();
expect(findAlert().text()).toBe('Repository does not have a default branch, please set one.'); await waitForPromises();
});
it('shows a unkown error message', async () => { expect(findAlert().text()).toBe(expectedAlertMsg);
mockBlobContentData.mockRejectedValueOnce(new Error('My error!')); });
createComponentWithApollo();
await waitForPromises(); it('shows a unkown error message', async () => {
mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
createComponentWithApollo();
await waitForPromises();
expect(findAlert().text()).toBe('The CI configuration was not loaded, please try again.'); expect(findAlert().text()).toBe('The CI configuration was not loaded, please try again.');
});
}); });
}); });
}); });
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