Commit a00d3061 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'slashmanov/display-error-message-when-failed-to-load-diff-metadata' into 'master'

Display an error within Apply suggestion box when failed to load diff metadata

See merge request gitlab-org/gitlab!83520
parents e82febf9 2af54ffb
...@@ -10,23 +10,14 @@ export function setEndpoints({ commit }, endpoints) { ...@@ -10,23 +10,14 @@ export function setEndpoints({ commit }, endpoints) {
commit(types.SET_ENDPOINTS, endpoints); commit(types.SET_ENDPOINTS, endpoints);
} }
export function setMrMetadata({ commit }, metadata) { export async function fetchMrMetadata({ state, commit }) {
commit(types.SET_MR_METADATA, metadata);
}
export function fetchMrMetadata({ dispatch, state }) {
if (state.endpoints?.metadata) { if (state.endpoints?.metadata) {
axios commit(types.SET_FAILED_TO_LOAD_METADATA, false);
.get(state.endpoints.metadata) try {
.then((response) => { const { data } = await axios.get(state.endpoints.metadata);
dispatch('setMrMetadata', response.data); commit(types.SET_MR_METADATA, data);
}) } catch (error) {
.catch(() => { commit(types.SET_FAILED_TO_LOAD_METADATA, true);
// https://gitlab.com/gitlab-org/gitlab/-/issues/324740 }
// We can't even do a simple console warning here because
// the pipeline will fail. However, the issue above will
// eventually handle errors appropriately.
// console.warn('Failed to load MR Metadata for the Overview tab.');
});
} }
} }
...@@ -7,6 +7,7 @@ export default () => ({ ...@@ -7,6 +7,7 @@ export default () => ({
endpoints: {}, endpoints: {},
activeTab: null, activeTab: null,
mrMetadata: {}, mrMetadata: {},
failedToLoadMetadata: false,
}, },
actions, actions,
getters, getters,
......
...@@ -2,4 +2,5 @@ export default { ...@@ -2,4 +2,5 @@ export default {
SET_ACTIVE_TAB: 'SET_ACTIVE_TAB', SET_ACTIVE_TAB: 'SET_ACTIVE_TAB',
SET_ENDPOINTS: 'SET_ENDPOINTS', SET_ENDPOINTS: 'SET_ENDPOINTS',
SET_MR_METADATA: 'SET_MR_METADATA', SET_MR_METADATA: 'SET_MR_METADATA',
SET_FAILED_TO_LOAD_METADATA: 'SET_FAILED_TO_LOAD_METADATA',
}; };
...@@ -10,4 +10,7 @@ export default { ...@@ -10,4 +10,7 @@ export default {
[types.SET_MR_METADATA](state, metadata) { [types.SET_MR_METADATA](state, metadata) {
Object.assign(state, { mrMetadata: metadata }); Object.assign(state, { mrMetadata: metadata });
}, },
[types.SET_FAILED_TO_LOAD_METADATA](state, value) {
Object.assign(state, { failedToLoadMetadata: value });
},
}; };
...@@ -57,14 +57,15 @@ export default { ...@@ -57,14 +57,15 @@ export default {
computed: { computed: {
...mapGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']), ...mapGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']),
...mapGetters('diffs', ['suggestionCommitMessage']), ...mapGetters('diffs', ['suggestionCommitMessage']),
...mapState({
batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo,
failedToLoadMetadata: (state) => state.page.failedToLoadMetadata,
}),
discussion() { discussion() {
if (!this.note.isDraft) return {}; if (!this.note.isDraft) return {};
return this.getDiscussion(this.note.discussion_id); return this.getDiscussion(this.note.discussion_id);
}, },
...mapState({
batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo,
}),
noteBody() { noteBody() {
return this.note.note; return this.note.note;
}, },
...@@ -165,6 +166,7 @@ export default { ...@@ -165,6 +166,7 @@ export default {
:line-type="lineType" :line-type="lineType"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:default-commit-message="commitMessage" :default-commit-message="commitMessage"
:failed-to-load-metadata="failedToLoadMetadata"
@apply="applySuggestion" @apply="applySuggestion"
@applyBatch="applySuggestionBatch" @applyBatch="applySuggestionBatch"
@addToBatch="addSuggestionToBatch" @addToBatch="addSuggestionToBatch"
......
<script> <script>
import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui'; import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton, GlAlert } from '@gitlab/ui';
import { __, n__ } from '~/locale'; import { __, n__ } from '~/locale';
export default { export default {
components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton }, components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton, GlAlert },
props: { props: {
disabled: { disabled: {
type: Boolean, type: Boolean,
...@@ -19,6 +19,11 @@ export default { ...@@ -19,6 +19,11 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
errorMessage: {
type: String,
required: false,
default: null,
},
}, },
data() { data() {
return { return {
...@@ -55,6 +60,9 @@ export default { ...@@ -55,6 +60,9 @@ export default {
> >
<gl-dropdown-form class="gl-px-4! gl-m-0!"> <gl-dropdown-form class="gl-px-4! gl-m-0!">
<label for="commit-message">{{ __('Commit message') }}</label> <label for="commit-message">{{ __('Commit message') }}</label>
<gl-alert v-if="errorMessage" variant="danger" :dismissible="false" class="gl-mb-4">
{{ errorMessage }}
</gl-alert>
<gl-form-textarea <gl-form-textarea
id="commit-message" id="commit-message"
ref="commitMessage" ref="commitMessage"
......
...@@ -36,6 +36,11 @@ export default { ...@@ -36,6 +36,11 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
failedToLoadMetadata: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
batchSuggestionsCount() { batchSuggestionsCount() {
...@@ -80,6 +85,7 @@ export default { ...@@ -80,6 +85,7 @@ export default {
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:default-commit-message="defaultCommitMessage" :default-commit-message="defaultCommitMessage"
:inapplicable-reason="suggestion.inapplicable_reason" :inapplicable-reason="suggestion.inapplicable_reason"
:failed-to-load-metadata="failedToLoadMetadata"
@apply="applySuggestion" @apply="applySuggestion"
@applyBatch="applySuggestionBatch" @applyBatch="applySuggestionBatch"
@addToBatch="addSuggestionToBatch" @addToBatch="addSuggestionToBatch"
......
...@@ -4,6 +4,10 @@ import { isLoggedIn } from '~/lib/utils/common_utils'; ...@@ -4,6 +4,10 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import ApplySuggestion from './apply_suggestion.vue'; import ApplySuggestion from './apply_suggestion.vue';
const APPLY_SUGGESTION_ERROR_MESSAGE = __(
'Unable to fully load the default commit message. You can still apply this suggestion and the commit message will be correct.',
);
export default { export default {
components: { GlBadge, GlIcon, GlButton, GlLoadingIcon, ApplySuggestion }, components: { GlBadge, GlIcon, GlButton, GlLoadingIcon, ApplySuggestion },
directives: { 'gl-tooltip': GlTooltipDirective }, directives: { 'gl-tooltip': GlTooltipDirective },
...@@ -52,6 +56,11 @@ export default { ...@@ -52,6 +56,11 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
failedToLoadMetadata: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -94,6 +103,9 @@ export default { ...@@ -94,6 +103,9 @@ export default {
return true; return true;
}, },
applySuggestionErrorMessage() {
return this.failedToLoadMetadata ? APPLY_SUGGESTION_ERROR_MESSAGE : null;
},
}, },
methods: { methods: {
apply(message) { apply(message) {
...@@ -171,6 +183,7 @@ export default { ...@@ -171,6 +183,7 @@ export default {
:disabled="isDisableButton" :disabled="isDisableButton"
:default-commit-message="defaultCommitMessage" :default-commit-message="defaultCommitMessage"
:batch-suggestions-count="batchSuggestionsCount" :batch-suggestions-count="batchSuggestionsCount"
:error-message="applySuggestionErrorMessage"
class="gl-ml-3" class="gl-ml-3"
@apply="apply" @apply="apply"
/> />
......
...@@ -47,6 +47,11 @@ export default { ...@@ -47,6 +47,11 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
failedToLoadMetadata: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -60,6 +65,9 @@ export default { ...@@ -60,6 +65,9 @@ export default {
noteHtml() { noteHtml() {
this.reset(); this.reset();
}, },
failedToLoadMetadata() {
this.reset();
},
}, },
mounted() { mounted() {
this.renderSuggestions(); this.renderSuggestions();
...@@ -105,6 +113,7 @@ export default { ...@@ -105,6 +113,7 @@ export default {
helpPagePath, helpPagePath,
defaultCommitMessage, defaultCommitMessage,
suggestionsCount, suggestionsCount,
failedToLoadMetadata,
} = this; } = this;
const suggestion = const suggestion =
suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {}; suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {};
...@@ -117,6 +126,7 @@ export default { ...@@ -117,6 +126,7 @@ export default {
helpPagePath, helpPagePath,
defaultCommitMessage, defaultCommitMessage,
suggestionsCount, suggestionsCount,
failedToLoadMetadata,
}, },
}); });
......
...@@ -40102,6 +40102,9 @@ msgstr "" ...@@ -40102,6 +40102,9 @@ msgstr ""
msgid "Unable to find Jira project to import data from." msgid "Unable to find Jira project to import data from."
msgstr "" msgstr ""
msgid "Unable to fully load the default commit message. You can still apply this suggestion and the commit message will be correct."
msgstr ""
msgid "Unable to generate new instance ID" msgid "Unable to generate new instance ID"
msgstr "" msgstr ""
......
...@@ -379,4 +379,41 @@ RSpec.describe 'User comments on a diff', :js do ...@@ -379,4 +379,41 @@ RSpec.describe 'User comments on a diff', :js do
end end
end end
end end
context 'failed to load metadata' do
let(:dummy_controller) do
Class.new(Projects::MergeRequests::DiffsController) do
def diffs_metadata
render json: '', status: :internal_server_error
end
end
end
before do
stub_const('Projects::MergeRequests::DiffsController', dummy_controller)
click_diff_line(find_by_scrolling("[id='#{sample_compare.changes[1][:line_code]}']"))
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Add comment now')
end
wait_for_requests
visit(project_merge_request_path(project, merge_request))
wait_for_requests
end
it 'displays an error' do
page.within('.discussion-notes') do
click_button('Apply suggestion')
wait_for_requests
expect(page).to have_content('Unable to fully load the default commit message. You can still apply this suggestion and the commit message will be correct.')
end
end
end
end end
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { setEndpoints, setMrMetadata, fetchMrMetadata } from '~/mr_notes/stores/actions'; import { createStore } from '~/mr_notes/stores';
import mutationTypes from '~/mr_notes/stores/mutation_types';
describe('MR Notes Mutator Actions', () => { describe('MR Notes Mutator Actions', () => {
let store;
beforeEach(() => {
store = createStore();
});
describe('setEndpoints', () => { describe('setEndpoints', () => {
it('should trigger the SET_ENDPOINTS state mutation', (done) => { it('sets endpoints', async () => {
const endpoints = { endpointA: 'a' }; const endpoints = { endpointA: 'a' };
testAction( await store.dispatch('setEndpoints', endpoints);
setEndpoints,
endpoints,
{},
[
{
type: mutationTypes.SET_ENDPOINTS,
payload: endpoints,
},
],
[],
done,
);
});
});
describe('setMrMetadata', () => { expect(store.state.page.endpoints).toEqual(endpoints);
it('should trigger the SET_MR_METADATA state mutation', async () => {
const mrMetadata = { propA: 'a', propB: 'b' };
await testAction(
setMrMetadata,
mrMetadata,
{},
[
{
type: mutationTypes.SET_MR_METADATA,
payload: mrMetadata,
},
],
[],
);
}); });
}); });
describe('fetchMrMetadata', () => { describe('fetchMrMetadata', () => {
const mrMetadata = { meta: true, data: 'foo' }; const mrMetadata = { meta: true, data: 'foo' };
const state = { const metadata = 'metadata';
endpoints: { const endpoints = { metadata };
metadata: 'metadata',
},
};
let mock; let mock;
beforeEach(() => { beforeEach(async () => {
await store.dispatch('setEndpoints', endpoints);
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(state.endpoints.metadata).reply(200, mrMetadata); mock.onGet(metadata).reply(200, mrMetadata);
}); });
afterEach(() => { afterEach(() => {
...@@ -66,27 +39,26 @@ describe('MR Notes Mutator Actions', () => { ...@@ -66,27 +39,26 @@ describe('MR Notes Mutator Actions', () => {
}); });
it('should fetch the data from the API', async () => { it('should fetch the data from the API', async () => {
await fetchMrMetadata({ state, dispatch: () => {} }); await store.dispatch('fetchMrMetadata');
await axios.waitForAll(); await axios.waitForAll();
expect(mock.history.get).toHaveLength(1); expect(mock.history.get).toHaveLength(1);
expect(mock.history.get[0].url).toBe(state.endpoints.metadata); expect(mock.history.get[0].url).toBe(metadata);
});
it('should set the fetched data into state', async () => {
await store.dispatch('fetchMrMetadata');
expect(store.state.page.mrMetadata).toEqual(mrMetadata);
}); });
it('should set the fetched data into state', () => { it('should set failedToLoadMetadata flag when request fails', async () => {
return testAction( mock.onGet(metadata).reply(500);
fetchMrMetadata,
{}, await store.dispatch('fetchMrMetadata');
state,
[], expect(store.state.page.failedToLoadMetadata).toBe(true);
[
{
type: 'setMrMetadata',
payload: mrMetadata,
},
],
);
}); });
}); });
}); });
import { GlDropdown, GlFormTextarea, GlButton } from '@gitlab/ui'; import { GlDropdown, GlFormTextarea, GlButton, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue'; import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue';
...@@ -10,9 +10,10 @@ describe('Apply Suggestion component', () => { ...@@ -10,9 +10,10 @@ describe('Apply Suggestion component', () => {
wrapper = shallowMount(ApplySuggestionComponent, { propsData: { ...propsData, ...props } }); wrapper = shallowMount(ApplySuggestionComponent, { propsData: { ...propsData, ...props } });
}; };
const findDropdown = () => wrapper.find(GlDropdown); const findDropdown = () => wrapper.findComponent(GlDropdown);
const findTextArea = () => wrapper.find(GlFormTextarea); const findTextArea = () => wrapper.findComponent(GlFormTextarea);
const findApplyButton = () => wrapper.find(GlButton); const findApplyButton = () => wrapper.findComponent(GlButton);
const findAlert = () => wrapper.findComponent(GlAlert);
beforeEach(() => createWrapper()); beforeEach(() => createWrapper());
...@@ -53,6 +54,20 @@ describe('Apply Suggestion component', () => { ...@@ -53,6 +54,20 @@ describe('Apply Suggestion component', () => {
}); });
}); });
describe('error', () => {
it('displays an error message', () => {
const errorMessage = 'Error message';
createWrapper({ errorMessage });
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.props('variant')).toBe('danger');
expect(alert.props('dismissible')).toBe(false);
expect(alert.text()).toBe(errorMessage);
});
});
describe('apply suggestion', () => { describe('apply suggestion', () => {
it('emits an apply event with no message if no message was added', () => { it('emits an apply event with no message if no message was added', () => {
findTextArea().vm.$emit('input', null); findTextArea().vm.$emit('input', null);
......
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