Commit 92c176f1 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '285458-group-request-approval-update-action' into 'master'

Allow users to save group MR approval settings

See merge request gitlab-org/gitlab!53975
parents 67bd9a30 5b4c4ce4
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
}, },
computed: { computed: {
...mapState({ ...mapState({
preventAuthorApproval: (state) => state.approvals.preventAuthorApproval, settings: (state) => state.approvals.settings,
isLoading: (state) => state.approvals.isLoading, isLoading: (state) => state.approvals.isLoading,
}), }),
}, },
...@@ -29,7 +29,10 @@ export default { ...@@ -29,7 +29,10 @@ export default {
this.fetchSettings(this.approvalSettingsPath); this.fetchSettings(this.approvalSettingsPath);
}, },
methods: { methods: {
...mapActions(['fetchSettings', 'updatePreventAuthorApproval']), ...mapActions(['fetchSettings', 'updateSettings']),
onSubmit() {
this.updateSettings(this.approvalSettingsPath);
},
}, },
links: { links: {
preventAuthorApprovalDocsPath: helpPagePath( preventAuthorApprovalDocsPath: helpPagePath(
...@@ -48,12 +51,11 @@ export default { ...@@ -48,12 +51,11 @@ export default {
</script> </script>
<template> <template>
<gl-form> <gl-form @submit.prevent="onSubmit">
<gl-form-group> <gl-form-group>
<gl-form-checkbox <gl-form-checkbox
:checked="preventAuthorApproval" v-model="settings.preventAuthorApproval"
data-testid="prevent-author-approval" data-testid="prevent-author-approval"
@input="updatePreventAuthorApproval"
> >
{{ $options.i18n.authorApprovalLabel }} {{ $options.i18n.authorApprovalLabel }}
<gl-link :href="$options.links.preventAuthorApprovalDocsPath" target="_blank"> <gl-link :href="$options.links.preventAuthorApprovalDocsPath" target="_blank">
......
...@@ -34,7 +34,7 @@ export default { ...@@ -34,7 +34,7 @@ export default {
</script> </script>
<template> <template>
<settings-block :default-expanded="defaultExpanded"> <settings-block :default-expanded="defaultExpanded" data-testid="merge-request-approval-settings">
<template #title> {{ $options.i18n.groupSettingsHeader }}</template> <template #title> {{ $options.i18n.groupSettingsHeader }}</template>
<template #description> <template #description>
<gl-sprintf :message="$options.i18n.groupSettingsDescription"> <gl-sprintf :message="$options.i18n.groupSettingsDescription">
......
...@@ -23,6 +23,26 @@ export const fetchSettings = ({ commit }, endpoint) => { ...@@ -23,6 +23,26 @@ export const fetchSettings = ({ commit }, endpoint) => {
}); });
}; };
export const updatePreventAuthorApproval = ({ commit }, preventAuthorApproval) => { export const updateSettings = ({ commit, state }, endpoint) => {
commit(types.UPDATE_PREVENT_AUTHOR_APPROVAL, preventAuthorApproval); const payload = {
allow_author_approval: !state.settings.preventAuthorApproval,
};
commit(types.REQUEST_UPDATE_SETTINGS);
return axios
.put(endpoint, payload)
.then(({ data }) => {
commit(types.UPDATE_SETTINGS_SUCCESS, data);
})
.catch(({ response }) => {
const error = response?.data?.message;
commit(types.UPDATE_SETTINGS_ERROR, error);
createFlash({
message: __('There was an error updating merge request approval settings.'),
captureError: true,
error,
});
});
}; };
...@@ -3,4 +3,6 @@ export * from '../base/mutation_types'; ...@@ -3,4 +3,6 @@ export * from '../base/mutation_types';
export const REQUEST_SETTINGS = 'REQUEST_SETTINGS'; export const REQUEST_SETTINGS = 'REQUEST_SETTINGS';
export const RECEIVE_SETTINGS_SUCCESS = 'RECEIVE_SETTINGS_SUCCESS'; export const RECEIVE_SETTINGS_SUCCESS = 'RECEIVE_SETTINGS_SUCCESS';
export const RECEIVE_SETTINGS_ERROR = 'RECEIVE_SETTINGS_ERROR'; export const RECEIVE_SETTINGS_ERROR = 'RECEIVE_SETTINGS_ERROR';
export const UPDATE_PREVENT_AUTHOR_APPROVAL = 'UPDATE_PREVENT_AUTHOR_APPROVAL'; export const REQUEST_UPDATE_SETTINGS = 'REQUEST_UPDATE_SETTINGS';
export const UPDATE_SETTINGS_SUCCESS = 'UPDATE_SETTINGS_SUCCESS';
export const UPDATE_SETTINGS_ERROR = 'UPDATE_SETTINGS_ERROR';
...@@ -5,13 +5,20 @@ export default { ...@@ -5,13 +5,20 @@ export default {
state.isLoading = true; state.isLoading = true;
}, },
[types.RECEIVE_SETTINGS_SUCCESS](state, data) { [types.RECEIVE_SETTINGS_SUCCESS](state, data) {
state.preventAuthorApproval = !data.allow_author_approval; state.settings.preventAuthorApproval = !data.allow_author_approval;
state.isLoading = false; state.isLoading = false;
}, },
[types.RECEIVE_SETTINGS_ERROR](state) { [types.RECEIVE_SETTINGS_ERROR](state) {
state.isLoading = false; state.isLoading = false;
}, },
[types.UPDATE_PREVENT_AUTHOR_APPROVAL](state, value) { [types.REQUEST_UPDATE_SETTINGS](state) {
state.preventAuthorApproval = value; state.isLoading = true;
},
[types.UPDATE_SETTINGS_SUCCESS](state, data) {
state.settings.preventAuthorApproval = !data.allow_author_approval;
state.isLoading = false;
},
[types.UPDATE_SETTINGS_ERROR](state) {
state.isLoading = false;
}, },
}; };
export default () => ({ export default () => ({
preventAuthorApproval: true, settings: {},
isLoading: false, isLoading: false,
}); });
...@@ -5,13 +5,16 @@ require 'spec_helper' ...@@ -5,13 +5,16 @@ require 'spec_helper'
RSpec.describe 'Edit group settings' do RSpec.describe 'Edit group settings' do
include Select2Helper include Select2Helper
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let(:group) { create(:group, path: 'foo') } let_it_be(:group) { create(:group, path: 'foo') }
before do before_all do
group.add_owner(user) group.add_owner(user)
group.add_developer(developer) group.add_developer(developer)
end
before do
sign_in(user) sign_in(user)
end end
...@@ -275,16 +278,38 @@ RSpec.describe 'Edit group settings' do ...@@ -275,16 +278,38 @@ RSpec.describe 'Edit group settings' do
end end
describe 'merge request approval settings', :js do describe 'merge request approval settings', :js do
let_it_be(:approval_settings) do
create(:group_merge_request_approval_setting, group: group, allow_author_approval: false)
end
context 'when feature flag is enabled and group is licensed' do context 'when feature flag is enabled and group is licensed' do
before do before do
stub_feature_flags(group_merge_request_approval_settings_feature_flag: true) stub_feature_flags(group_merge_request_approval_settings_feature_flag: true)
stub_licensed_features(group_merge_request_approval_settings: true) stub_licensed_features(group_merge_request_approval_settings: true)
end end
it 'is visible' do it 'allows to save settings' do
visit edit_group_path(group) visit edit_group_path(group)
wait_for_all_requests
expect(page).to have_content('Merge request approvals') expect(page).to have_content('Merge request approvals')
within('[data-testid="merge-request-approval-settings"]') do
click_button 'Expand'
expect(find('[data-testid="prevent-author-approval"]')).to be_checked
find('[data-testid="prevent-author-approval"]').set(false)
click_button 'Save changes'
wait_for_all_requests
end
visit edit_group_path(group)
wait_for_all_requests
within('[data-testid="merge-request-approval-settings"]') do
click_button 'Expand'
expect(find('[data-testid="prevent-author-approval"]')).not_to be_checked
end
end end
end end
......
import { GlButton } from '@gitlab/ui'; import { GlButton, GlForm } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
...@@ -24,6 +24,7 @@ describe('ApprovalSettings', () => { ...@@ -24,6 +24,7 @@ describe('ApprovalSettings', () => {
}); });
}; };
const findForm = () => wrapper.findComponent(GlForm);
const findPreventAuthorApproval = () => wrapper.find('[data-testid="prevent-author-approval"]'); const findPreventAuthorApproval = () => wrapper.find('[data-testid="prevent-author-approval"]');
const findSaveButton = () => wrapper.findComponent(GlButton); const findSaveButton = () => wrapper.findComponent(GlButton);
...@@ -31,6 +32,7 @@ describe('ApprovalSettings', () => { ...@@ -31,6 +32,7 @@ describe('ApprovalSettings', () => {
store = createStoreOptions(groupSettingsModule()); store = createStoreOptions(groupSettingsModule());
jest.spyOn(store.modules.approvals.actions, 'fetchSettings').mockImplementation(); jest.spyOn(store.modules.approvals.actions, 'fetchSettings').mockImplementation();
jest.spyOn(store.modules.approvals.actions, 'updateSettings').mockImplementation();
({ actions } = store.modules.approvals); ({ actions } = store.modules.approvals);
}); });
...@@ -52,8 +54,7 @@ describe('ApprovalSettings', () => { ...@@ -52,8 +54,7 @@ describe('ApprovalSettings', () => {
const input = findPreventAuthorApproval(); const input = findPreventAuthorApproval();
await input.vm.$emit('input', false); await input.vm.$emit('input', false);
expect(input.vm.$attrs.checked).toBe(false); expect(store.modules.approvals.state.settings.preventAuthorApproval).toBe(false);
expect(store.modules.approvals.state.preventAuthorApproval).toBe(false);
}); });
}); });
...@@ -74,4 +75,14 @@ describe('ApprovalSettings', () => { ...@@ -74,4 +75,14 @@ describe('ApprovalSettings', () => {
expect(findSaveButton().props('disabled')).toBe(true); expect(findSaveButton().props('disabled')).toBe(true);
}); });
}); });
describe('form submission', () => {
it('update settings via API', async () => {
createWrapper();
await findForm().vm.$emit('submit', { preventDefault: () => {} });
expect(actions.updateSettings).toHaveBeenCalledWith(expect.any(Object), approvalSettingsPath);
});
});
}); });
...@@ -69,17 +69,55 @@ describe('EE approvals group settings module actions', () => { ...@@ -69,17 +69,55 @@ describe('EE approvals group settings module actions', () => {
}); });
}); });
describe('updatePreventAuthorApproval', () => { describe('updateSettings', () => {
it('updates payload', () => { beforeEach(() => {
const value = false; state = {
settings: {
preventAuthorApproval: false,
},
};
});
describe('on success', () => {
it('dispatches the request and updates payload', () => {
const data = { allow_author_approval: true };
mock.onPut(approvalSettingsPath).replyOnce(httpStatus.OK, data);
return testAction( return testAction(
actions.updatePreventAuthorApproval, actions.updateSettings,
value, approvalSettingsPath,
state, state,
[{ type: types.UPDATE_PREVENT_AUTHOR_APPROVAL, payload: value }], [
{ type: types.REQUEST_UPDATE_SETTINGS },
{ type: types.UPDATE_SETTINGS_SUCCESS, payload: data },
],
[], [],
); );
}); });
}); });
describe('on error', () => {
it('dispatches the request, updates payload and sets error message', () => {
const data = { message: 'Internal Server Error' };
mock.onPut(approvalSettingsPath).replyOnce(httpStatus.INTERNAL_SERVER_ERROR, data);
return testAction(
actions.updateSettings,
approvalSettingsPath,
state,
[
{ type: types.REQUEST_UPDATE_SETTINGS },
{ type: types.UPDATE_SETTINGS_ERROR, payload: data.message },
],
[],
).then(() => {
expect(createFlash).toHaveBeenCalledWith({
message: 'There was an error updating merge request approval settings.',
captureError: true,
error: 'Internal Server Error',
});
});
});
});
});
}); });
...@@ -20,7 +20,7 @@ describe('Group settings store mutations', () => { ...@@ -20,7 +20,7 @@ describe('Group settings store mutations', () => {
it('updates settings', () => { it('updates settings', () => {
mutations.RECEIVE_SETTINGS_SUCCESS(state, { allow_author_approval: true }); mutations.RECEIVE_SETTINGS_SUCCESS(state, { allow_author_approval: true });
expect(state.preventAuthorApproval).toBe(false); expect(state.settings.preventAuthorApproval).toBe(false);
expect(state.isLoading).toBe(false); expect(state.isLoading).toBe(false);
}); });
}); });
...@@ -33,11 +33,28 @@ describe('Group settings store mutations', () => { ...@@ -33,11 +33,28 @@ describe('Group settings store mutations', () => {
}); });
}); });
describe('UPDATE_PREVENT_AUTHOR_APPROVAL', () => { describe('REQUEST_UPDATE_SETTINGS', () => {
it('updates setting', () => { it('sets loading state', () => {
mutations.UPDATE_PREVENT_AUTHOR_APPROVAL(state, false); mutations.REQUEST_UPDATE_SETTINGS(state);
expect(state.isLoading).toBe(true);
});
});
describe('UPDATE_SETTINGS_SUCCESS', () => {
it('updates settings', () => {
mutations.UPDATE_SETTINGS_SUCCESS(state, { allow_author_approval: true });
expect(state.preventAuthorApproval).toBe(false); expect(state.settings.preventAuthorApproval).toBe(false);
expect(state.isLoading).toBe(false);
});
});
describe('UPDATE_SETTINGS_ERROR', () => {
it('sets loading state', () => {
mutations.UPDATE_SETTINGS_ERROR(state);
expect(state.isLoading).toBe(false);
}); });
}); });
}); });
...@@ -29701,6 +29701,9 @@ msgstr "" ...@@ -29701,6 +29701,9 @@ msgstr ""
msgid "There was an error trying to validate your query" msgid "There was an error trying to validate your query"
msgstr "" msgstr ""
msgid "There was an error updating merge request approval settings."
msgstr ""
msgid "There was an error updating the Geo Settings" msgid "There was an error updating the Geo Settings"
msgstr "" msgstr ""
......
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