Commit 2015a951 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '327638-change-status-checks-store-to-support-editing' into 'master'

Setup the status checks store to handle the create/update operations

See merge request gitlab-org/gitlab!61697
parents a69dd751 a92d486c
...@@ -13,9 +13,10 @@ export default function mountProjectSettingsApprovals(el) { ...@@ -13,9 +13,10 @@ export default function mountProjectSettingsApprovals(el) {
} }
const store = createStore(); const store = createStore();
const { statusChecksPath } = el.dataset; const { projectId, statusChecksPath } = el.dataset;
store.dispatch('fetchStatusChecks', { statusChecksPath }).catch((error) => { store.dispatch('setSettings', { projectId, statusChecksPath });
store.dispatch('fetchStatusChecks').catch((error) => {
createFlash({ createFlash({
message: s__('StatusCheck|An error occurred fetching the status checks.'), message: s__('StatusCheck|An error occurred fetching the status checks.'),
captureError: true, captureError: true,
......
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import {
convertObjectPropsToCamelCase,
convertObjectPropsToSnakeCase,
} from '~/lib/utils/common_utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
export const fetchStatusChecks = ({ commit }, { statusChecksPath }) => { export const setSettings = ({ commit }, settings) => {
commit(types.SET_SETTINGS, settings);
};
export const fetchStatusChecks = ({ commit, rootState }) => {
const { statusChecksPath } = rootState.settings;
commit(types.SET_LOADING, true); commit(types.SET_LOADING, true);
return axios.get(statusChecksPath).then(({ data }) => { return axios.get(statusChecksPath).then(({ data }) => {
...@@ -10,3 +19,19 @@ export const fetchStatusChecks = ({ commit }, { statusChecksPath }) => { ...@@ -10,3 +19,19 @@ export const fetchStatusChecks = ({ commit }, { statusChecksPath }) => {
commit(types.SET_LOADING, false); commit(types.SET_LOADING, false);
}); });
}; };
export const putStatusCheck = ({ dispatch, rootState }, statusCheck) => {
const { statusChecksPath } = rootState.settings;
const data = convertObjectPropsToSnakeCase(statusCheck, { deep: true });
return axios
.put(`${statusChecksPath}/${statusCheck.id}`, data)
.then(() => dispatch('fetchStatusChecks'));
};
export const postStatusCheck = ({ dispatch, rootState }, statusCheck) => {
const { statusChecksPath } = rootState.settings;
const data = convertObjectPropsToSnakeCase(statusCheck, { deep: true });
return axios.post(statusChecksPath, data).then(() => dispatch('fetchStatusChecks'));
};
export const SET_LOADING = 'SET_LOADING'; export const SET_LOADING = 'SET_LOADING';
export const SET_SETTINGS = 'SET_SETTINGS';
export const SET_STATUS_CHECKS = 'SET_STATUS_CHECKS'; export const SET_STATUS_CHECKS = 'SET_STATUS_CHECKS';
...@@ -4,6 +4,9 @@ export default { ...@@ -4,6 +4,9 @@ export default {
[types.SET_LOADING](state, isLoading) { [types.SET_LOADING](state, isLoading) {
state.isLoading = isLoading; state.isLoading = isLoading;
}, },
[types.SET_SETTINGS](state, settings) {
state.settings = settings;
},
[types.SET_STATUS_CHECKS](state, statusChecks) { [types.SET_STATUS_CHECKS](state, statusChecks) {
state.statusChecks = statusChecks; state.statusChecks = statusChecks;
}, },
......
export default () => ({ export default () => ({
isLoading: false, isLoading: false,
settings: {},
statusChecks: [], statusChecks: [],
}); });
...@@ -90,6 +90,7 @@ module EE ...@@ -90,6 +90,7 @@ module EE
def status_checks_app_data(project) def status_checks_app_data(project)
{ {
data: { data: {
project_id: project.id,
status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id)) status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id))
} }
} }
......
...@@ -8,12 +8,14 @@ jest.mock('ee/status_checks/store'); ...@@ -8,12 +8,14 @@ jest.mock('ee/status_checks/store');
jest.mock('~/flash'); jest.mock('~/flash');
describe('mountStatusChecks', () => { describe('mountStatusChecks', () => {
const projectId = '12345';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules'; const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const dispatch = jest.fn(); const dispatch = jest.fn();
let el; let el;
const setUpDocument = () => { const setUpDocument = () => {
el = document.createElement('div'); el = document.createElement('div');
el.setAttribute('data-project-id', projectId);
el.setAttribute('data-status-checks-path', statusChecksPath); el.setAttribute('data-status-checks-path', statusChecksPath);
document.body.appendChild(el); document.body.appendChild(el);
...@@ -22,7 +24,7 @@ describe('mountStatusChecks', () => { ...@@ -22,7 +24,7 @@ describe('mountStatusChecks', () => {
}; };
beforeEach(() => { beforeEach(() => {
createStore.mockReturnValue({ dispatch, state: { statusChecks: [] } }); createStore.mockReturnValue({ dispatch, state: { settings: {}, statusChecks: [] } });
setUpDocument(); setUpDocument();
}); });
...@@ -39,12 +41,14 @@ describe('mountStatusChecks', () => { ...@@ -39,12 +41,14 @@ describe('mountStatusChecks', () => {
dispatch.mockResolvedValue({}); dispatch.mockResolvedValue({});
const wrapper = createWrapper(mountStatusChecks(el)); const wrapper = createWrapper(mountStatusChecks(el));
expect(dispatch).toHaveBeenCalledWith('fetchStatusChecks', { statusChecksPath }); expect(dispatch).toHaveBeenCalledWith('setSettings', { projectId, statusChecksPath });
expect(dispatch).toHaveBeenCalledWith('fetchStatusChecks');
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
}); });
it('returns the Vue component with an error if fetchStatusChecks fails', async () => { it('returns the Vue component with an error if fetchStatusChecks fails', async () => {
const error = new Error('Something went wrong'); const error = new Error('Something went wrong');
dispatch.mockResolvedValueOnce({});
dispatch.mockRejectedValueOnce(error); dispatch.mockRejectedValueOnce(error);
const wrapper = createWrapper(mountStatusChecks(el)); const wrapper = createWrapper(mountStatusChecks(el));
......
...@@ -2,10 +2,13 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -2,10 +2,13 @@ import MockAdapter from 'axios-mock-adapter';
import * as actions from 'ee/status_checks/store/actions'; import * as actions from 'ee/status_checks/store/actions';
import * as types from 'ee/status_checks/store/mutation_types'; import * as types from 'ee/status_checks/store/mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules'; const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const rootState = { settings: { statusChecksPath } };
const commit = jest.fn(); const commit = jest.fn();
const dispatch = jest.fn();
let mockAxios; let mockAxios;
describe('Status checks actions', () => { describe('Status checks actions', () => {
...@@ -17,12 +20,23 @@ describe('Status checks actions', () => { ...@@ -17,12 +20,23 @@ describe('Status checks actions', () => {
mockAxios.restore(); mockAxios.restore();
}); });
describe('setSettings', () => {
it('should commit the settings', () => {
const settings = { projectId: '12345', statusChecksPath };
actions.setSettings({ commit }, settings);
expect(commit).toHaveBeenCalledWith(types.SET_SETTINGS, settings);
});
});
describe('fetchStatusChecks', () => {
it(`should commit the API response`, async () => { it(`should commit the API response`, async () => {
const data = [{ name: 'Foo' }, { name: 'Bar' }]; const data = [{ name: 'Foo' }, { name: 'Bar' }];
mockAxios.onGet(statusChecksPath).replyOnce(httpStatusCodes.OK, data); mockAxios.onGet(statusChecksPath).replyOnce(httpStatusCodes.OK, data);
await actions.fetchStatusChecks({ commit }, { statusChecksPath }); await actions.fetchStatusChecks({ commit, rootState });
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true); expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledWith(types.SET_STATUS_CHECKS, data); expect(commit).toHaveBeenCalledWith(types.SET_STATUS_CHECKS, data);
...@@ -32,10 +46,37 @@ describe('Status checks actions', () => { ...@@ -32,10 +46,37 @@ describe('Status checks actions', () => {
it('should error with a failed API response', async () => { it('should error with a failed API response', async () => {
mockAxios.onGet(statusChecksPath).networkError(); mockAxios.onGet(statusChecksPath).networkError();
await expect(actions.fetchStatusChecks({ commit }, { statusChecksPath })).rejects.toThrow( await expect(actions.fetchStatusChecks({ commit, rootState })).rejects.toThrow(
new Error('Network Error'), new Error('Network Error'),
); );
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true); expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledTimes(1); expect(commit).toHaveBeenCalledTimes(1);
}); });
});
describe('when creating and updating a status check', () => {
const defaultData = {
name: 'Foo',
externalUrl: 'https://bar.com',
protectedBranchIds: [1],
};
it.each`
action | axiosMethod | httpMethod | statusCheck | url
${'postStatusCheck'} | ${'onPost'} | ${'post'} | ${defaultData} | ${statusChecksPath}
${'putStatusCheck'} | ${'onPut'} | ${'put'} | ${{ ...defaultData, id: 1 }} | ${`${statusChecksPath}/1`}
`(
'should $httpMethod to the API and then dispatch fetchStatusChecks',
async ({ action, axiosMethod, httpMethod, statusCheck, url }) => {
mockAxios[axiosMethod](url).replyOnce(httpStatusCodes.OK);
await actions[action]({ dispatch, rootState }, statusCheck);
expect(JSON.parse(mockAxios.history[httpMethod][0].data)).toStrictEqual(
convertObjectPropsToSnakeCase(statusCheck, { deep: true }),
);
expect(dispatch).toHaveBeenCalledWith('fetchStatusChecks');
},
);
});
}); });
...@@ -4,6 +4,7 @@ describe('createStore', () => { ...@@ -4,6 +4,7 @@ describe('createStore', () => {
it('creates a new store', () => { it('creates a new store', () => {
expect(createStore().state).toStrictEqual({ expect(createStore().state).toStrictEqual({
isLoading: false, isLoading: false,
settings: {},
statusChecks: [], statusChecks: [],
}); });
}); });
......
...@@ -19,12 +19,24 @@ describe('Status checks mutations', () => { ...@@ -19,12 +19,24 @@ describe('Status checks mutations', () => {
}); });
}); });
describe(types.SET_SETTINGS, () => {
it('sets the settings', () => {
expect(state.settings).toStrictEqual({});
const settings = { projectId: '12345', statusChecksPath: 'foo/bar/baz' };
mutations[types.SET_SETTINGS](state, settings);
expect(state.settings).toStrictEqual(settings);
});
});
describe(types.SET_STATUS_CHECKS, () => { describe(types.SET_STATUS_CHECKS, () => {
it('sets the statusChecks', () => { it('sets the statusChecks', () => {
const statusChecks = [{ name: 'Foo' }, { name: 'Bar' }];
expect(state.statusChecks).toStrictEqual([]); expect(state.statusChecks).toStrictEqual([]);
const statusChecks = [{ name: 'Foo' }, { name: 'Bar' }];
mutations[types.SET_STATUS_CHECKS](state, statusChecks); mutations[types.SET_STATUS_CHECKS](state, statusChecks);
expect(state.statusChecks).toStrictEqual(statusChecks); expect(state.statusChecks).toStrictEqual(statusChecks);
......
...@@ -4,6 +4,7 @@ describe('state', () => { ...@@ -4,6 +4,7 @@ describe('state', () => {
it('returns the expected default state', () => { it('returns the expected default state', () => {
expect(initialState()).toStrictEqual({ expect(initialState()).toStrictEqual({
isLoading: false, isLoading: false,
settings: {},
statusChecks: [], statusChecks: [],
}); });
}); });
......
...@@ -383,7 +383,10 @@ RSpec.describe ProjectsHelper do ...@@ -383,7 +383,10 @@ RSpec.describe ProjectsHelper do
subject { helper.status_checks_app_data(project) } subject { helper.status_checks_app_data(project) }
it 'returns the correct data' do it 'returns the correct data' do
expect(subject[:data]).to eq({ status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id)) }) expect(subject[:data]).to eq({
project_id: project.id,
status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id))
})
end end
end end
end end
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