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) {
}
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({
message: s__('StatusCheck|An error occurred fetching the status checks.'),
captureError: true,
......
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';
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);
return axios.get(statusChecksPath).then(({ data }) => {
......@@ -10,3 +19,19 @@ export const fetchStatusChecks = ({ commit }, { statusChecksPath }) => {
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_SETTINGS = 'SET_SETTINGS';
export const SET_STATUS_CHECKS = 'SET_STATUS_CHECKS';
......@@ -4,6 +4,9 @@ export default {
[types.SET_LOADING](state, isLoading) {
state.isLoading = isLoading;
},
[types.SET_SETTINGS](state, settings) {
state.settings = settings;
},
[types.SET_STATUS_CHECKS](state, statusChecks) {
state.statusChecks = statusChecks;
},
......
export default () => ({
isLoading: false,
settings: {},
statusChecks: [],
});
......@@ -90,6 +90,7 @@ module EE
def status_checks_app_data(project)
{
data: {
project_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');
jest.mock('~/flash');
describe('mountStatusChecks', () => {
const projectId = '12345';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const dispatch = jest.fn();
let el;
const setUpDocument = () => {
el = document.createElement('div');
el.setAttribute('data-project-id', projectId);
el.setAttribute('data-status-checks-path', statusChecksPath);
document.body.appendChild(el);
......@@ -22,7 +24,7 @@ describe('mountStatusChecks', () => {
};
beforeEach(() => {
createStore.mockReturnValue({ dispatch, state: { statusChecks: [] } });
createStore.mockReturnValue({ dispatch, state: { settings: {}, statusChecks: [] } });
setUpDocument();
});
......@@ -39,12 +41,14 @@ describe('mountStatusChecks', () => {
dispatch.mockResolvedValue({});
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);
});
it('returns the Vue component with an error if fetchStatusChecks fails', async () => {
const error = new Error('Something went wrong');
dispatch.mockResolvedValueOnce({});
dispatch.mockRejectedValueOnce(error);
const wrapper = createWrapper(mountStatusChecks(el));
......
......@@ -2,10 +2,13 @@ import MockAdapter from 'axios-mock-adapter';
import * as actions from 'ee/status_checks/store/actions';
import * as types from 'ee/status_checks/store/mutation_types';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const rootState = { settings: { statusChecksPath } };
const commit = jest.fn();
const dispatch = jest.fn();
let mockAxios;
describe('Status checks actions', () => {
......@@ -17,25 +20,63 @@ describe('Status checks actions', () => {
mockAxios.restore();
});
it(`should commit the API response`, async () => {
const data = [{ name: 'Foo' }, { name: 'Bar' }];
describe('setSettings', () => {
it('should commit the settings', () => {
const settings = { projectId: '12345', statusChecksPath };
mockAxios.onGet(statusChecksPath).replyOnce(httpStatusCodes.OK, data);
actions.setSettings({ commit }, settings);
await actions.fetchStatusChecks({ commit }, { statusChecksPath });
expect(commit).toHaveBeenCalledWith(types.SET_SETTINGS, settings);
});
});
describe('fetchStatusChecks', () => {
it(`should commit the API response`, async () => {
const data = [{ name: 'Foo' }, { name: 'Bar' }];
mockAxios.onGet(statusChecksPath).replyOnce(httpStatusCodes.OK, data);
await actions.fetchStatusChecks({ commit, rootState });
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledWith(types.SET_STATUS_CHECKS, data);
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, false);
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledWith(types.SET_STATUS_CHECKS, data);
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, false);
});
it('should error with a failed API response', async () => {
mockAxios.onGet(statusChecksPath).networkError();
await expect(actions.fetchStatusChecks({ commit, rootState })).rejects.toThrow(
new Error('Network Error'),
);
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledTimes(1);
});
});
it('should error with a failed API response', async () => {
mockAxios.onGet(statusChecksPath).networkError();
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);
await expect(actions.fetchStatusChecks({ commit }, { statusChecksPath })).rejects.toThrow(
new Error('Network Error'),
expect(JSON.parse(mockAxios.history[httpMethod][0].data)).toStrictEqual(
convertObjectPropsToSnakeCase(statusCheck, { deep: true }),
);
expect(dispatch).toHaveBeenCalledWith('fetchStatusChecks');
},
);
expect(commit).toHaveBeenCalledWith(types.SET_LOADING, true);
expect(commit).toHaveBeenCalledTimes(1);
});
});
......@@ -4,6 +4,7 @@ describe('createStore', () => {
it('creates a new store', () => {
expect(createStore().state).toStrictEqual({
isLoading: false,
settings: {},
statusChecks: [],
});
});
......
......@@ -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, () => {
it('sets the statusChecks', () => {
const statusChecks = [{ name: 'Foo' }, { name: 'Bar' }];
expect(state.statusChecks).toStrictEqual([]);
const statusChecks = [{ name: 'Foo' }, { name: 'Bar' }];
mutations[types.SET_STATUS_CHECKS](state, statusChecks);
expect(state.statusChecks).toStrictEqual(statusChecks);
......
......@@ -4,6 +4,7 @@ describe('state', () => {
it('returns the expected default state', () => {
expect(initialState()).toStrictEqual({
isLoading: false,
settings: {},
statusChecks: [],
});
});
......
......@@ -383,7 +383,10 @@ RSpec.describe ProjectsHelper do
subject { helper.status_checks_app_data(project) }
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
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