Commit 07e7fe04 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'dependency-list-work-around-missing-pagination-headers-ee' into 'master'

Work around missing pagination headers

See merge request gitlab-org/gitlab-ee!14142
parents 9cfb979f a6f5bc54
...@@ -55,10 +55,12 @@ export default { ...@@ -55,10 +55,12 @@ export default {
}, },
created() { created() {
this.setDependenciesEndpoint(this.endpoint); this.setDependenciesEndpoint(this.endpoint);
this.fetchDependencies(); this.fetchDependenciesPagination()
.then(() => this.fetchDependencies())
.catch(() => {});
}, },
methods: { methods: {
...mapActions(['setDependenciesEndpoint', 'fetchDependencies']), ...mapActions(['setDependenciesEndpoint', 'fetchDependenciesPagination', 'fetchDependencies']),
fetchPage(page) { fetchPage(page) {
this.fetchDependencies({ page }); this.fetchDependencies({ page });
}, },
......
...@@ -9,7 +9,43 @@ import { __ } from '~/locale'; ...@@ -9,7 +9,43 @@ import { __ } from '~/locale';
export const setDependenciesEndpoint = ({ commit }, endpoint) => export const setDependenciesEndpoint = ({ commit }, endpoint) =>
commit(types.SET_DEPENDENCIES_ENDPOINT, endpoint); commit(types.SET_DEPENDENCIES_ENDPOINT, endpoint);
export const requestDependencies = ({ commit }) => commit(types.REQUEST_DEPENDENCIES); export const requestDependenciesPagination = ({ commit }) =>
commit(types.REQUEST_DEPENDENCIES_PAGINATION);
export const receiveDependenciesPaginationSuccess = ({ commit }, payload) =>
commit(types.RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS, payload);
export const receiveDependenciesPaginationError = ({ commit }) =>
commit(types.RECEIVE_DEPENDENCIES_PAGINATION_ERROR);
export const fetchDependenciesPagination = ({ state, dispatch }) => {
const onError = error => {
dispatch('receiveDependenciesPaginationError', error);
createFlash(FETCH_ERROR_MESSAGE);
throw error;
};
dispatch('requestDependenciesPagination');
if (!state.endpoint) {
return Promise.reject(new Error('endpoint_missing')).catch(onError);
}
return axios
.get(state.endpoint)
.then(response => {
if (isValidResponse(response)) {
const total = response.data.dependencies.length;
dispatch('receiveDependenciesPaginationSuccess', total);
} else {
throw new Error(__('Invalid server response'));
}
})
.catch(onError);
};
export const requestDependencies = ({ commit }, payload) =>
commit(types.REQUEST_DEPENDENCIES, payload);
export const receiveDependenciesSuccess = ({ commit }, { headers, data }) => { export const receiveDependenciesSuccess = ({ commit }, { headers, data }) => {
const normalizedHeaders = normalizeHeaders(headers); const normalizedHeaders = normalizeHeaders(headers);
...@@ -27,14 +63,15 @@ export const fetchDependencies = ({ state, dispatch }, params = {}) => { ...@@ -27,14 +63,15 @@ export const fetchDependencies = ({ state, dispatch }, params = {}) => {
return; return;
} }
dispatch('requestDependencies'); const page = params.page || state.pageInfo.page || 1;
dispatch('requestDependencies', { page });
axios axios
.get(state.endpoint, { .get(state.endpoint, {
params: { params: {
sort_by: state.sortField, sort_by: state.sortField,
sort: state.sortOrder, sort: state.sortOrder,
page: state.pageInfo.page || 1, page,
...params, ...params,
}, },
}) })
......
...@@ -21,3 +21,5 @@ export const REPORT_STATUS = { ...@@ -21,3 +21,5 @@ export const REPORT_STATUS = {
export const FETCH_ERROR_MESSAGE = __( export const FETCH_ERROR_MESSAGE = __(
'Error fetching the dependency list. Please check your network connection and try again.', 'Error fetching the dependency list. Please check your network connection and try again.',
); );
export const DEPENDENCIES_PER_PAGE = 20;
export const SET_DEPENDENCIES_ENDPOINT = 'SET_DEPENDENCIES_ENDPOINT'; export const SET_DEPENDENCIES_ENDPOINT = 'SET_DEPENDENCIES_ENDPOINT';
export const REQUEST_DEPENDENCIES_PAGINATION = 'REQUEST_DEPENDENCIES_PAGINATION';
export const RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS = 'RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS';
export const RECEIVE_DEPENDENCIES_PAGINATION_ERROR = 'RECEIVE_DEPENDENCIES_PAGINATION_ERROR';
export const REQUEST_DEPENDENCIES = 'REQUEST_DEPENDENCIES'; export const REQUEST_DEPENDENCIES = 'REQUEST_DEPENDENCIES';
export const RECEIVE_DEPENDENCIES_SUCCESS = 'RECEIVE_DEPENDENCIES_SUCCESS'; export const RECEIVE_DEPENDENCIES_SUCCESS = 'RECEIVE_DEPENDENCIES_SUCCESS';
export const RECEIVE_DEPENDENCIES_ERROR = 'RECEIVE_DEPENDENCIES_ERROR'; export const RECEIVE_DEPENDENCIES_ERROR = 'RECEIVE_DEPENDENCIES_ERROR';
......
...@@ -5,13 +5,30 @@ export default { ...@@ -5,13 +5,30 @@ export default {
[types.SET_DEPENDENCIES_ENDPOINT](state, payload) { [types.SET_DEPENDENCIES_ENDPOINT](state, payload) {
state.endpoint = payload; state.endpoint = payload;
}, },
[types.REQUEST_DEPENDENCIES](state) { [types.REQUEST_DEPENDENCIES_PAGINATION](state) {
state.isLoading = true;
},
[types.RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS](state, total) {
state.pageInfo.total = total;
},
[types.RECEIVE_DEPENDENCIES_PAGINATION_ERROR](state) {
state.isLoading = false;
state.errorLoading = true;
state.dependencies = [];
state.pageInfo = {};
state.reportInfo = {
status: REPORT_STATUS.ok,
jobPath: '',
};
state.initialized = true;
},
[types.REQUEST_DEPENDENCIES](state, { page }) {
state.isLoading = true; state.isLoading = true;
state.errorLoading = false; state.errorLoading = false;
state.pageInfo.page = page;
}, },
[types.RECEIVE_DEPENDENCIES_SUCCESS](state, { dependencies, reportInfo, pageInfo }) { [types.RECEIVE_DEPENDENCIES_SUCCESS](state, { dependencies, reportInfo }) {
state.dependencies = dependencies; state.dependencies = dependencies;
state.pageInfo = pageInfo;
state.isLoading = false; state.isLoading = false;
state.errorLoading = false; state.errorLoading = false;
state.reportInfo.status = reportInfo.status; state.reportInfo.status = reportInfo.status;
......
import { REPORT_STATUS, SORT_FIELDS, SORT_ORDER } from './constants'; import { REPORT_STATUS, SORT_FIELDS, SORT_ORDER, DEPENDENCIES_PER_PAGE } from './constants';
export default () => ({ export default () => ({
endpoint: '', endpoint: '',
...@@ -6,7 +6,11 @@ export default () => ({ ...@@ -6,7 +6,11 @@ export default () => ({
isLoading: false, isLoading: false,
errorLoading: false, errorLoading: false,
dependencies: [], dependencies: [],
pageInfo: {}, pageInfo: {
page: 1,
perPage: DEPENDENCIES_PER_PAGE,
total: 0,
},
reportInfo: { reportInfo: {
status: REPORT_STATUS.ok, status: REPORT_STATUS.ok,
jobPath: '', jobPath: '',
......
...@@ -22,7 +22,7 @@ describe('DependenciesApp component', () => { ...@@ -22,7 +22,7 @@ describe('DependenciesApp component', () => {
const localVue = createLocalVue(); const localVue = createLocalVue();
store = createStore(); store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = shallowMount(DependenciesApp, { wrapper = shallowMount(DependenciesApp, {
localVue, localVue,
...@@ -52,6 +52,7 @@ describe('DependenciesApp component', () => { ...@@ -52,6 +52,7 @@ describe('DependenciesApp component', () => {
it('dispatches the correct initial actions', () => { it('dispatches the correct initial actions', () => {
expect(store.dispatch.mock.calls).toEqual([ expect(store.dispatch.mock.calls).toEqual([
['setDependenciesEndpoint', basicAppProps.endpoint], ['setDependenciesEndpoint', basicAppProps.endpoint],
['fetchDependenciesPagination'],
['fetchDependencies'], ['fetchDependencies'],
]); ]);
}); });
......
...@@ -52,15 +52,108 @@ describe('Dependencies actions', () => { ...@@ -52,15 +52,108 @@ describe('Dependencies actions', () => {
)); ));
}); });
describe('requestDependenciesPagination', () => {
it('commits the REQUEST_DEPENDENCIES_PAGINATION mutation', () =>
testAction(
actions.requestDependenciesPagination,
undefined,
getInitialState(),
[
{
type: types.REQUEST_DEPENDENCIES_PAGINATION,
},
],
[],
));
});
describe('receiveDependenciesPaginationSuccess', () => {
const total = 123;
it('commits the RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS mutation', () =>
testAction(
actions.receiveDependenciesPaginationSuccess,
total,
getInitialState(),
[
{
type: types.RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS,
payload: total,
},
],
[],
));
});
describe('receiveDependenciesPaginationError', () => {
it('commits the RECEIVE_DEPENDENCIES_PAGINATION_ERROR mutation', () =>
testAction(
actions.receiveDependenciesPaginationError,
undefined,
getInitialState(),
[
{
type: types.RECEIVE_DEPENDENCIES_PAGINATION_ERROR,
},
],
[],
));
});
describe('fetchDependenciesPagination', () => {
let mock;
let state;
beforeEach(() => {
state = getInitialState();
state.endpoint = `${TEST_HOST}/dependencies`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('on success', () => {
beforeEach(() => {
mock.onGet(state.endpoint).replyOnce(200, mockDependenciesResponse);
});
it('dispatches the correct actions', () =>
testAction(
actions.fetchDependenciesPagination,
undefined,
state,
[],
[
{
type: 'requestDependenciesPagination',
},
{
type: 'receiveDependenciesPaginationSuccess',
payload: mockDependenciesResponse.dependencies.length,
},
],
));
});
/**
* Tests for error conditions are in
* `ee/spec/javascripts/dependencies/store/actions_spec.js`. They cannot be
* tested here due to https://gitlab.com/gitlab-org/gitlab-ce/issues/63225.
*/
});
describe('requestDependencies', () => { describe('requestDependencies', () => {
const page = 4;
it('commits the REQUEST_DEPENDENCIES mutation', () => it('commits the REQUEST_DEPENDENCIES mutation', () =>
testAction( testAction(
actions.requestDependencies, actions.requestDependencies,
undefined, { page },
getInitialState(), getInitialState(),
[ [
{ {
type: types.REQUEST_DEPENDENCIES, type: types.REQUEST_DEPENDENCIES,
payload: { page },
}, },
], ],
[], [],
...@@ -134,10 +227,11 @@ describe('Dependencies actions', () => { ...@@ -134,10 +227,11 @@ describe('Dependencies actions', () => {
describe('on success', () => { describe('on success', () => {
describe('given no params', () => { describe('given no params', () => {
let paramsDefault;
beforeEach(() => { beforeEach(() => {
state.pageInfo = { ...pageInfo }; state.pageInfo = { ...pageInfo };
const paramsDefault = { paramsDefault = {
sort_by: state.sortField, sort_by: state.sortField,
sort: state.sortOrder, sort: state.sortOrder,
page: state.pageInfo.page, page: state.pageInfo.page,
...@@ -157,6 +251,7 @@ describe('Dependencies actions', () => { ...@@ -157,6 +251,7 @@ describe('Dependencies actions', () => {
[ [
{ {
type: 'requestDependencies', type: 'requestDependencies',
payload: { page: paramsDefault.page },
}, },
{ {
type: 'receiveDependenciesSuccess', type: 'receiveDependenciesSuccess',
...@@ -184,6 +279,7 @@ describe('Dependencies actions', () => { ...@@ -184,6 +279,7 @@ describe('Dependencies actions', () => {
[ [
{ {
type: 'requestDependencies', type: 'requestDependencies',
payload: { page: paramsGiven.page },
}, },
{ {
type: 'receiveDependenciesSuccess', type: 'receiveDependenciesSuccess',
...@@ -199,7 +295,9 @@ describe('Dependencies actions', () => { ...@@ -199,7 +295,9 @@ describe('Dependencies actions', () => {
${'an invalid response'} | ${[200, { foo: 'bar' }]} ${'an invalid response'} | ${[200, { foo: 'bar' }]}
${'a response error'} | ${[500]} ${'a response error'} | ${[500]}
`('given $responseType', ({ responseDetails }) => { `('given $responseType', ({ responseDetails }) => {
let page;
beforeEach(() => { beforeEach(() => {
({ page } = state.pageInfo);
mock.onGet(state.endpoint).replyOnce(...responseDetails); mock.onGet(state.endpoint).replyOnce(...responseDetails);
}); });
...@@ -212,6 +310,7 @@ describe('Dependencies actions', () => { ...@@ -212,6 +310,7 @@ describe('Dependencies actions', () => {
[ [
{ {
type: 'requestDependencies', type: 'requestDependencies',
payload: { page },
}, },
{ {
type: 'receiveDependenciesError', type: 'receiveDependenciesError',
......
...@@ -7,6 +7,18 @@ import { TEST_HOST } from 'helpers/test_constants'; ...@@ -7,6 +7,18 @@ import { TEST_HOST } from 'helpers/test_constants';
describe('Dependencies mutations', () => { describe('Dependencies mutations', () => {
let state; let state;
const errorLoadingState = () => ({
isLoading: false,
errorLoading: true,
dependencies: [],
pageInfo: {},
initialized: true,
reportInfo: {
status: REPORT_STATUS.ok,
jobPath: '',
},
});
beforeEach(() => { beforeEach(() => {
state = getInitialState(); state = getInitialState();
}); });
...@@ -19,14 +31,47 @@ describe('Dependencies mutations', () => { ...@@ -19,14 +31,47 @@ describe('Dependencies mutations', () => {
}); });
}); });
describe(types.REQUEST_DEPENDENCIES_PAGINATION, () => {
beforeEach(() => {
mutations[types.REQUEST_DEPENDENCIES_PAGINATION](state);
});
it('correctly mutates the state', () => {
expect(state.isLoading).toBe(true);
});
});
describe(types.RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS, () => {
const total = 123;
beforeEach(() => {
mutations[types.RECEIVE_DEPENDENCIES_PAGINATION_SUCCESS](state, total);
});
it('correctly mutates the state', () => {
expect(state.pageInfo.total).toBe(total);
});
});
describe(types.RECEIVE_DEPENDENCIES_PAGINATION_ERROR, () => {
beforeEach(() => {
mutations[types.RECEIVE_DEPENDENCIES_PAGINATION_ERROR](state);
});
it('correctly mutates the state', () => {
expect(state).toEqual(expect.objectContaining(errorLoadingState()));
});
});
describe(types.REQUEST_DEPENDENCIES, () => { describe(types.REQUEST_DEPENDENCIES, () => {
const page = 4;
beforeEach(() => { beforeEach(() => {
mutations[types.REQUEST_DEPENDENCIES](state); mutations[types.REQUEST_DEPENDENCIES](state, { page });
}); });
it('correctly mutates the state', () => { it('correctly mutates the state', () => {
expect(state.isLoading).toBe(true); expect(state.isLoading).toBe(true);
expect(state.errorLoading).toBe(false); expect(state.errorLoading).toBe(false);
expect(state.pageInfo.page).toBe(page);
}); });
}); });
...@@ -37,21 +82,27 @@ describe('Dependencies mutations', () => { ...@@ -37,21 +82,27 @@ describe('Dependencies mutations', () => {
status: REPORT_STATUS.jobFailed, status: REPORT_STATUS.jobFailed,
job_path: 'foo', job_path: 'foo',
}; };
let originalPageInfo;
beforeEach(() => { beforeEach(() => {
originalPageInfo = state.pageInfo;
mutations[types.RECEIVE_DEPENDENCIES_SUCCESS](state, { dependencies, reportInfo, pageInfo }); mutations[types.RECEIVE_DEPENDENCIES_SUCCESS](state, { dependencies, reportInfo, pageInfo });
}); });
it('correctly mutates the state', () => { it('correctly mutates the state', () => {
expect(state.isLoading).toBe(false); expect(state).toEqual(
expect(state.errorLoading).toBe(false); expect.objectContaining({
expect(state.dependencies).toBe(dependencies); isLoading: false,
expect(state.pageInfo).toBe(pageInfo); errorLoading: false,
expect(state.initialized).toBe(true); dependencies,
expect(state.reportInfo).toEqual({ pageInfo: originalPageInfo,
initialized: true,
reportInfo: {
status: REPORT_STATUS.jobFailed, status: REPORT_STATUS.jobFailed,
jobPath: 'foo', jobPath: 'foo',
}); },
}),
);
}); });
}); });
...@@ -61,15 +112,7 @@ describe('Dependencies mutations', () => { ...@@ -61,15 +112,7 @@ describe('Dependencies mutations', () => {
}); });
it('correctly mutates the state', () => { it('correctly mutates the state', () => {
expect(state.isLoading).toBe(false); expect(state).toEqual(expect.objectContaining(errorLoadingState()));
expect(state.errorLoading).toBe(true);
expect(state.dependencies).toEqual([]);
expect(state.pageInfo).toEqual({});
expect(state.initialized).toBe(true);
expect(state.reportInfo).toEqual({
status: REPORT_STATUS.ok,
jobPath: '',
});
}); });
}); });
......
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import actionsModule, * as actions from 'ee/dependencies/store/actions';
import getInitialState from 'ee/dependencies/store/state';
import { FETCH_ERROR_MESSAGE } from 'ee/dependencies/store/constants';
describe('Dependencies actions', () => {
/**
* This file only contains tests for failure conditions. Tests for success
* conditions are in `ee/spec/frontend/dependencies/store/actions_spec.js`.
* The split is due to https://gitlab.com/gitlab-org/gitlab-ce/issues/63225.
*/
describe('fetchDependenciesPagination', () => {
const failureScenarios = [
{
context: 'an invalid response',
endpoint: TEST_HOST,
responseDetails: [200, { foo: 'bar' }],
},
{
context: 'a response error',
endpoint: TEST_HOST,
responseDetails: [500],
},
{
context: 'no endpoint',
endpoint: '',
responseDetails: [],
},
];
failureScenarios.forEach(({ context, endpoint, responseDetails }) => {
describe(`given ${context}`, () => {
let state;
let flashSpy;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
state = getInitialState();
flashSpy = spyOnDependency(actionsModule, 'createFlash');
state.endpoint = endpoint;
if (endpoint) {
mock.onGet(state.endpoint).replyOnce(...responseDetails);
}
});
afterEach(() => {
mock.restore();
});
it('dispatches the correct actions and creates a flash', done => {
testAction(
actions.fetchDependenciesPagination,
undefined,
state,
[],
[
{
type: 'requestDependenciesPagination',
},
{
type: 'receiveDependenciesPaginationError',
payload: jasmine.any(Error),
},
],
)
.then(done.fail)
.catch(() => {
expect(flashSpy).toHaveBeenCalledTimes(1);
expect(flashSpy).toHaveBeenCalledWith(FETCH_ERROR_MESSAGE);
done();
});
});
});
});
});
});
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