Commit 065153e6 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '12524-vsa-enable-pagination-frontend' into 'master'

[VSA] Enable pagination (frontend)

See merge request gitlab-org/gitlab!59650
parents 9028f616 41ee917d
...@@ -69,6 +69,7 @@ export default { ...@@ -69,6 +69,7 @@ export default {
'isLoadingValueStreams', 'isLoadingValueStreams',
'selectedStageError', 'selectedStageError',
'selectedValueStream', 'selectedValueStream',
'pagination',
]), ]),
// NOTE: formEvents are fetched in the same request as the list of stages (fetchGroupStagesAndEvents) // NOTE: formEvents are fetched in the same request as the list of stages (fetchGroupStagesAndEvents)
// so i think its ok to bind formEvents here even though its only used as a prop to the custom-stage-form // so i think its ok to bind formEvents here even though its only used as a prop to the custom-stage-form
...@@ -156,6 +157,7 @@ export default { ...@@ -156,6 +157,7 @@ export default {
'removeStage', 'removeStage',
'updateStage', 'updateStage',
'reorderStage', 'reorderStage',
'updateStageTablePagination',
]), ]),
...mapActions('customStages', ['hideForm', 'showCreateForm', 'showEditForm', 'createStage']), ...mapActions('customStages', ['hideForm', 'showCreateForm', 'showEditForm', 'createStage']),
onProjectsSelect(projects) { onProjectsSelect(projects) {
...@@ -190,9 +192,11 @@ export default { ...@@ -190,9 +192,11 @@ export default {
onStageReorder(data) { onStageReorder(data) {
this.reorderStage(data); this.reorderStage(data);
}, },
onHandleSelectPage(data) {
this.updateStageTablePagination(data);
},
}, },
multiProjectSelect: true, multiProjectSelect: true,
dateOptions: [7, 30, 90],
maxDateRange: DATE_RANGE_LIMIT, maxDateRange: DATE_RANGE_LIMIT,
}; };
</script> </script>
...@@ -279,9 +283,11 @@ export default { ...@@ -279,9 +283,11 @@ export default {
v-if="!isLoading && !isOverviewStageSelected" v-if="!isLoading && !isOverviewStageSelected"
:is-loading="isLoading || isLoadingStage" :is-loading="isLoading || isLoadingStage"
:stage-events="currentStageEvents" :stage-events="currentStageEvents"
:current-stage="selectedStage" :selected-stage="selectedStage"
:empty-state-message="selectedStageError" :empty-state-message="selectedStageError"
:no-data-svg-path="noDataSvgPath" :no-data-svg-path="noDataSvgPath"
:pagination="pagination"
@handleSelectPage="onHandleSelectPage"
/> />
</template> </template>
<stage-table <stage-table
......
<script> <script>
import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { NOT_ENOUGH_DATA_ERROR } from '../constants'; import { NOT_ENOUGH_DATA_ERROR } from '../constants';
import TotalTime from './total_time_component.vue'; import TotalTime from './total_time_component.vue';
...@@ -19,11 +19,12 @@ export default { ...@@ -19,11 +19,12 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlPagination,
GlTable, GlTable,
TotalTime, TotalTime,
}, },
props: { props: {
currentStage: { selectedStage: {
type: Object, type: Object,
required: true, required: true,
}, },
...@@ -44,6 +45,10 @@ export default { ...@@ -44,6 +45,10 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
pagination: {
type: Object,
required: true,
},
}, },
computed: { computed: {
isEmptyStage() { isEmptyStage() {
...@@ -54,12 +59,12 @@ export default { ...@@ -54,12 +59,12 @@ export default {
return emptyStateMessage || NOT_ENOUGH_DATA_ERROR; return emptyStateMessage || NOT_ENOUGH_DATA_ERROR;
}, },
isDefaultTestStage() { isDefaultTestStage() {
const { currentStage } = this; const { selectedStage } = this;
return !currentStage.custom && currentStage.title?.toLowerCase().trim() === 'test'; return !selectedStage.custom && selectedStage.title?.toLowerCase().trim() === 'test';
}, },
isDefaultStagingStage() { isDefaultStagingStage() {
const { currentStage } = this; const { selectedStage } = this;
return !currentStage.custom && currentStage.title?.toLowerCase().trim() === 'staging'; return !selectedStage.custom && selectedStage.title?.toLowerCase().trim() === 'staging';
}, },
isMergeRequestStage() { isMergeRequestStage() {
const [firstEvent] = this.stageEvents; const [firstEvent] = this.stageEvents;
...@@ -78,6 +83,12 @@ export default { ...@@ -78,6 +83,12 @@ export default {
fields() { fields() {
return [this.workflowTitle, { key: 'time', label: __('Time'), thClass: 'gl-w-half' }]; return [this.workflowTitle, { key: 'time', label: __('Time'), thClass: 'gl-w-half' }];
}, },
prevPage() {
return Math.max(this.pagination.page - 1, 0);
},
nextPage() {
return this.pagination.hasNextPage ? this.pagination.page + 1 : null;
},
}, },
methods: { methods: {
isMrLink(url = '') { isMrLink(url = '') {
...@@ -86,6 +97,9 @@ export default { ...@@ -86,6 +97,9 @@ export default {
itemTitle(item) { itemTitle(item) {
return item.title || item.name; return item.title || item.name;
}, },
onSelectPage(page) {
this.$emit('handleSelectPage', { page });
},
}, },
}; };
</script> </script>
...@@ -194,5 +208,15 @@ export default { ...@@ -194,5 +208,15 @@ export default {
<total-time :time="item.totalTime" data-testid="vsa-stage-event-time" /> <total-time :time="item.totalTime" data-testid="vsa-stage-event-time" />
</template> </template>
</gl-table> </gl-table>
<gl-pagination
v-if="!isLoading"
:value="pagination.page"
:prev-page="prevPage"
:next-page="nextPage"
align="center"
class="gl-mt-3"
data-testid="vsa-stage-pagination"
@input="onSelectPage"
/>
</div> </div>
</template> </template>
...@@ -73,3 +73,6 @@ export const OVERVIEW_STAGE_CONFIG = { ...@@ -73,3 +73,6 @@ export const OVERVIEW_STAGE_CONFIG = {
export const NOT_ENOUGH_DATA_ERROR = s__( export const NOT_ENOUGH_DATA_ERROR = s__(
"ValueStreamAnalyticsStage|We don't have enough data to show this stage.", "ValueStreamAnalyticsStage|We don't have enough data to show this stage.",
); );
export const PAGINATION_TYPE = 'keyset';
export const PAGINATION_SORT_FIELD = 'created_at';
import Api from 'ee/api'; import Api from 'ee/api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { FETCH_VALUE_STREAM_DATA, OVERVIEW_STAGE_CONFIG } from '../constants'; import { FETCH_VALUE_STREAM_DATA, OVERVIEW_STAGE_CONFIG } from '../constants';
...@@ -30,7 +31,10 @@ export const setFeatureFlags = ({ commit }, featureFlags) => ...@@ -30,7 +31,10 @@ export const setFeatureFlags = ({ commit }, featureFlags) =>
export const setSelectedProjects = ({ commit }, projects) => export const setSelectedProjects = ({ commit }, projects) =>
commit(types.SET_SELECTED_PROJECTS, projects); commit(types.SET_SELECTED_PROJECTS, projects);
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage); export const setSelectedStage = ({ commit }, stage) => {
commit(types.SET_SELECTED_STAGE, stage);
commit(types.SET_PAGINATION, { page: 1, hasNextPage: null });
};
export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDate, endDate }) => { export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDate, endDate }) => {
commit(types.SET_DATE_RANGE, { startDate, endDate }); commit(types.SET_DATE_RANGE, { startDate, endDate });
...@@ -41,8 +45,6 @@ export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDat ...@@ -41,8 +45,6 @@ export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDat
}; };
export const requestStageData = ({ commit }) => commit(types.REQUEST_STAGE_DATA); export const requestStageData = ({ commit }) => commit(types.REQUEST_STAGE_DATA);
export const receiveStageDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_STAGE_DATA_SUCCESS, data);
export const receiveStageDataError = ({ commit }, error) => { export const receiveStageDataError = ({ commit }, error) => {
const { message = '' } = error; const { message = '' } = error;
...@@ -53,18 +55,30 @@ export const receiveStageDataError = ({ commit }, error) => { ...@@ -53,18 +55,30 @@ export const receiveStageDataError = ({ commit }, error) => {
commit(types.RECEIVE_STAGE_DATA_ERROR, message); commit(types.RECEIVE_STAGE_DATA_ERROR, message);
}; };
export const fetchStageData = ({ dispatch, getters }, stageId) => { export const fetchStageData = ({ dispatch, getters, commit }, stageId) => {
const { cycleAnalyticsRequestParams = {}, currentValueStreamId, currentGroupPath } = getters; const {
cycleAnalyticsRequestParams = {},
currentValueStreamId,
currentGroupPath,
paginationParams,
} = getters;
dispatch('requestStageData'); dispatch('requestStageData');
return Api.cycleAnalyticsStageEvents({ return Api.cycleAnalyticsStageEvents({
groupId: currentGroupPath, groupId: currentGroupPath,
valueStreamId: currentValueStreamId, valueStreamId: currentValueStreamId,
stageId, stageId,
params: cycleAnalyticsRequestParams, params: {
...cycleAnalyticsRequestParams,
...paginationParams,
},
}) })
.then(checkForDataError) .then(checkForDataError)
.then(({ data }) => dispatch('receiveStageDataSuccess', data)) .then(({ data, headers }) => {
const { page = null, nextPage = null } = parseIntPagination(normalizeHeaders(headers));
commit(types.RECEIVE_STAGE_DATA_SUCCESS, data);
commit(types.SET_PAGINATION, { page, hasNextPage: Boolean(nextPage) });
})
.catch((error) => dispatch('receiveStageDataError', error)); .catch((error) => dispatch('receiveStageDataError', error));
}; };
...@@ -466,3 +480,11 @@ export const fetchValueStreams = ({ commit, dispatch, getters }) => { ...@@ -466,3 +480,11 @@ export const fetchValueStreams = ({ commit, dispatch, getters }) => {
export const setFilters = ({ dispatch }) => { export const setFilters = ({ dispatch }) => {
return dispatch('fetchCycleAnalyticsData'); return dispatch('fetchCycleAnalyticsData');
}; };
export const updateStageTablePagination = (
{ commit, dispatch, state: { selectedStage } },
{ page },
) => {
commit(types.SET_PAGINATION, { page });
return dispatch('fetchStageData', selectedStage.id);
};
...@@ -4,7 +4,12 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; ...@@ -4,7 +4,12 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants'; import { dateFormats } from '../../shared/constants';
import { DEFAULT_VALUE_STREAM_ID, OVERVIEW_STAGE_CONFIG } from '../constants'; import {
DEFAULT_VALUE_STREAM_ID,
OVERVIEW_STAGE_CONFIG,
PAGINATION_TYPE,
PAGINATION_SORT_FIELD,
} from '../constants';
import { transformStagesForPathNavigation } from '../utils'; import { transformStagesForPathNavigation } from '../utils';
export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN; export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN;
...@@ -44,6 +49,12 @@ export const cycleAnalyticsRequestParams = (state, getters) => { ...@@ -44,6 +49,12 @@ export const cycleAnalyticsRequestParams = (state, getters) => {
}; };
}; };
export const paginationParams = ({ pagination: { page } }) => ({
pagination: PAGINATION_TYPE,
sort: PAGINATION_SORT_FIELD,
page,
});
const filterStagesByHiddenStatus = (stages = [], isHidden = true) => const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
stages.filter(({ hidden = false }) => hidden === isHidden); stages.filter(({ hidden = false }) => hidden === isHidden);
......
...@@ -4,6 +4,7 @@ export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS'; ...@@ -4,6 +4,7 @@ export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE'; export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const SET_DATE_RANGE = 'SET_DATE_RANGE'; export const SET_DATE_RANGE = 'SET_DATE_RANGE';
export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM'; export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM';
export const SET_PAGINATION = 'SET_PAGINATION';
export const REQUEST_VALUE_STREAM_DATA = 'REQUEST_VALUE_STREAM_DATA'; export const REQUEST_VALUE_STREAM_DATA = 'REQUEST_VALUE_STREAM_DATA';
export const RECEIVE_VALUE_STREAM_DATA_SUCCESS = 'RECEIVE_VALUE_STREAM_DATA_SUCCESS'; export const RECEIVE_VALUE_STREAM_DATA_SUCCESS = 'RECEIVE_VALUE_STREAM_DATA_SUCCESS';
......
...@@ -183,4 +183,7 @@ export default { ...@@ -183,4 +183,7 @@ export default {
return aName.toUpperCase() > bName.toUpperCase() ? 1 : -1; return aName.toUpperCase() > bName.toUpperCase() ? 1 : -1;
}); });
}, },
[types.SET_PAGINATION](state, { page, hasNextPage }) {
state.pagination = { page, hasNextPage };
},
}; };
...@@ -34,4 +34,9 @@ export default () => ({ ...@@ -34,4 +34,9 @@ export default () => ({
summary: [], summary: [],
medians: {}, medians: {},
valueStreams: [], valueStreams: [],
pagination: {
page: null,
hasNextPage: false,
},
}); });
---
title: Add pagination to the VSA stage table
merge_request: 59650
author:
type: changed
...@@ -144,10 +144,10 @@ describe('Value Stream Analytics component', () => { ...@@ -144,10 +144,10 @@ describe('Value Stream Analytics component', () => {
}); });
if (withStageSelected) { if (withStageSelected) {
await Promise.all([ await store.dispatch(
store.dispatch('receiveGroupStagesSuccess', mockData.customizableStagesAndEvents.stages), 'receiveGroupStagesSuccess',
store.dispatch('receiveStageDataSuccess', mockData.issueEvents), mockData.customizableStagesAndEvents.stages,
]); );
} }
return comp; return comp;
} }
......
...@@ -22,8 +22,10 @@ const [firstIssueEvent] = issueEvents; ...@@ -22,8 +22,10 @@ const [firstIssueEvent] = issueEvents;
const [firstStagingEvent] = stagingEvents; const [firstStagingEvent] = stagingEvents;
const [firstTestEvent] = testEvents; const [firstTestEvent] = testEvents;
const [firstReviewEvent] = reviewEvents; const [firstReviewEvent] = reviewEvents;
const pagination = { page: 1, hasNextPage: true };
const findStageEvents = () => wrapper.findAllByTestId('vsa-stage-event'); const findStageEvents = () => wrapper.findAllByTestId('vsa-stage-event');
const findPagination = () => wrapper.findByTestId('vsa-stage-pagination');
const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title'); const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title');
function createComponent(props = {}, shallow = false) { function createComponent(props = {}, shallow = false) {
...@@ -34,7 +36,8 @@ function createComponent(props = {}, shallow = false) { ...@@ -34,7 +36,8 @@ function createComponent(props = {}, shallow = false) {
isLoading: false, isLoading: false,
stageEvents: issueEvents, stageEvents: issueEvents,
noDataSvgPath, noDataSvgPath,
currentStage: issueStage, selectedStage: issueStage,
pagination,
...props, ...props,
}, },
stubs: { stubs: {
...@@ -90,7 +93,7 @@ describe('StageTable', () => { ...@@ -90,7 +93,7 @@ describe('StageTable', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
stageEvents: [{ ...firstIssueEvent }], stageEvents: [{ ...firstIssueEvent }],
currentStage: { ...issueStage, custom: false }, selectedStage: { ...issueStage, custom: false },
}); });
}); });
...@@ -131,7 +134,7 @@ describe('StageTable', () => { ...@@ -131,7 +134,7 @@ describe('StageTable', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
stageEvents: [{ ...firstReviewEvent }], stageEvents: [{ ...firstReviewEvent }],
currentStage: { ...reviewStage, custom: false }, selectedStage: { ...reviewStage, custom: false },
}); });
}); });
...@@ -145,7 +148,7 @@ describe('StageTable', () => { ...@@ -145,7 +148,7 @@ describe('StageTable', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
stageEvents: [{ ...firstStagingEvent }], stageEvents: [{ ...firstStagingEvent }],
currentStage: { ...stagingStage, custom: false }, selectedStage: { ...stagingStage, custom: false },
}); });
}); });
...@@ -187,7 +190,7 @@ describe('StageTable', () => { ...@@ -187,7 +190,7 @@ describe('StageTable', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
stageEvents: [{ ...firstTestEvent }], stageEvents: [{ ...firstTestEvent }],
currentStage: { ...testStage, custom: false }, selectedStage: { ...testStage, custom: false },
}); });
}); });
...@@ -234,9 +237,18 @@ describe('StageTable', () => { ...@@ -234,9 +237,18 @@ describe('StageTable', () => {
}); });
}); });
it('isLoading = true', () => { describe('isLoading = true', () => {
wrapper = createComponent({ isLoading: true }, true); beforeEach(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toEqual(true); wrapper = createComponent({ isLoading: true }, true);
});
it('will display the loading icon', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('will not display pagination', () => {
expect(findPagination().exists()).toBe(false);
});
}); });
describe('with no stageEvents', () => { describe('with no stageEvents', () => {
...@@ -263,4 +275,31 @@ describe('StageTable', () => { ...@@ -263,4 +275,31 @@ describe('StageTable', () => {
expect(wrapper.html()).toContain(emptyStateMessage); expect(wrapper.html()).toContain(emptyStateMessage);
}); });
}); });
describe('Pagination', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('will display the pagination component', () => {
expect(findPagination().exists()).toBe(true);
});
it('clicking prev or next will emit an event', async () => {
findPagination().vm.$emit('input', 2);
await wrapper.vm.$nextTick();
expect(wrapper.emitted('handleSelectPage')[0]).toEqual([{ page: 2 }]);
});
describe('with `hasNextPage=false', () => {
beforeEach(() => {
wrapper = createComponent({ pagination: { page: 1, hasNextPage: false } });
});
it('will not display the pagination component', () => {
expect(findPagination().exists()).toBe(false);
});
});
});
}); });
...@@ -3,6 +3,8 @@ import { ...@@ -3,6 +3,8 @@ import {
DEFAULT_DAYS_IN_PAST, DEFAULT_DAYS_IN_PAST,
TASKS_BY_TYPE_SUBJECT_ISSUE, TASKS_BY_TYPE_SUBJECT_ISSUE,
OVERVIEW_STAGE_CONFIG, OVERVIEW_STAGE_CONFIG,
PAGINATION_TYPE,
PAGINATION_SORT_FIELD,
} from 'ee/analytics/cycle_analytics/constants'; } from 'ee/analytics/cycle_analytics/constants';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import mutations from 'ee/analytics/cycle_analytics/store/mutations'; import mutations from 'ee/analytics/cycle_analytics/store/mutations';
...@@ -302,3 +304,10 @@ export const selectedProjects = [ ...@@ -302,3 +304,10 @@ export const selectedProjects = [
]; ];
export const pathNavIssueMetric = 172800; export const pathNavIssueMetric = 172800;
export const initialPaginationState = { page: null, hasNextPage: false };
export const basePaginationResult = {
pagination: PAGINATION_TYPE,
sort: PAGINATION_SORT_FIELD,
page: null,
};
...@@ -78,7 +78,6 @@ describe('Value Stream Analytics actions', () => { ...@@ -78,7 +78,6 @@ describe('Value Stream Analytics actions', () => {
action | type | stateKey | payload action | type | stateKey | payload
${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ hasDurationChart: true }} ${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ hasDurationChart: true }}
${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]} ${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]}
${'setSelectedStage'} | ${'SET_SELECTED_STAGE'} | ${'selectedStage'} | ${{ id: 'someStageId' }}
`('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => { `('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => {
return testAction( return testAction(
actions[action], actions[action],
...@@ -94,6 +93,18 @@ describe('Value Stream Analytics actions', () => { ...@@ -94,6 +93,18 @@ describe('Value Stream Analytics actions', () => {
); );
}); });
describe('setSelectedStage', () => {
const data = { id: 'someStageId' };
const payload = { hasNextPage: null, page: 1 };
it(`dispatches the ${types.SET_SELECTED_STAGE} and ${types.SET_PAGINATION} actions`, () => {
return testAction(actions.setSelectedStage, data, { ...state, selectedValueStream: {} }, [
{ type: types.SET_SELECTED_STAGE, payload: data },
{ type: types.SET_PAGINATION, payload },
]);
});
});
describe('setSelectedValueStream', () => { describe('setSelectedValueStream', () => {
const vs = { id: 'vs-1', name: 'Value stream 1' }; const vs = { id: 'vs-1', name: 'Value stream 1' };
...@@ -144,28 +155,64 @@ describe('Value Stream Analytics actions', () => { ...@@ -144,28 +155,64 @@ describe('Value Stream Analytics actions', () => {
}); });
describe('fetchStageData', () => { describe('fetchStageData', () => {
const headers = {
'X-Next-Page': 2,
'X-Page': 1,
};
beforeEach(() => { beforeEach(() => {
state = { ...state, currentGroup }; state = { ...state, currentGroup };
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(endpoints.stageData).reply(httpStatusCodes.OK, { events: [] }); mock.onGet(endpoints.stageData).reply(httpStatusCodes.OK, stageData, headers);
}); });
it('dispatches receiveStageDataSuccess with received data on success', () => { it(`commits ${types.RECEIVE_STAGE_DATA_SUCCESS} with received data and headers on success`, () => {
return testAction( return testAction(
actions.fetchStageData, actions.fetchStageData,
selectedStageSlug, selectedStageSlug,
state, state,
[],
[ [
{ type: 'requestStageData' },
{ {
type: 'receiveStageDataSuccess', type: types.RECEIVE_STAGE_DATA_SUCCESS,
payload: { events: [] }, payload: stageData,
},
{
type: types.SET_PAGINATION,
payload: { page: headers['X-Page'], hasNextPage: true },
}, },
], ],
[{ type: 'requestStageData' }],
); );
}); });
describe('without a next page', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock
.onGet(endpoints.stageData)
.reply(httpStatusCodes.OK, { events: [] }, { ...headers, 'X-Next-Page': null });
});
it('sets hasNextPage to false', () => {
return testAction(
actions.fetchStageData,
selectedStageSlug,
state,
[
{
type: types.RECEIVE_STAGE_DATA_SUCCESS,
payload: { events: [] },
},
{
type: types.SET_PAGINATION,
payload: { page: headers['X-Page'], hasNextPage: false },
},
],
[{ type: 'requestStageData' }],
);
});
});
describe('with a failing request', () => { describe('with a failing request', () => {
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
...@@ -190,18 +237,6 @@ describe('Value Stream Analytics actions', () => { ...@@ -190,18 +237,6 @@ describe('Value Stream Analytics actions', () => {
); );
}); });
}); });
describe('receiveStageDataSuccess', () => {
it(`commits the ${types.RECEIVE_STAGE_DATA_SUCCESS} mutation`, () => {
return testAction(
actions.receiveStageDataSuccess,
{ ...stageData },
state,
[{ type: types.RECEIVE_STAGE_DATA_SUCCESS, payload: { events: [] } }],
[],
);
});
});
}); });
describe('receiveStageDataError', () => { describe('receiveStageDataError', () => {
......
...@@ -16,6 +16,8 @@ import { ...@@ -16,6 +16,8 @@ import {
transformedStagePathData, transformedStagePathData,
issueStage, issueStage,
stageMedians, stageMedians,
basePaginationResult,
initialPaginationState,
} from '../mock_data'; } from '../mock_data';
let state = null; let state = null;
...@@ -218,4 +220,24 @@ describe('Value Stream Analytics getters', () => { ...@@ -218,4 +220,24 @@ describe('Value Stream Analytics getters', () => {
expect(getters.pathNavigationData(state)).toEqual(transformedStagePathData); expect(getters.pathNavigationData(state)).toEqual(transformedStagePathData);
}); });
}); });
describe('paginationParams', () => {
beforeEach(() => {
state = { pagination: initialPaginationState };
});
it('returns the `pagination` type', () => {
expect(getters.paginationParams(state)).toEqual(basePaginationResult);
});
it('returns the `sort` type', () => {
expect(getters.paginationParams(state)).toEqual(basePaginationResult);
});
it('with page=10, sets the `page` property', () => {
const page = 10;
state = { pagination: { ...initialPaginationState, page } };
expect(getters.paginationParams(state)).toEqual({ ...basePaginationResult, page });
});
});
}); });
...@@ -78,6 +78,8 @@ describe('Value Stream Analytics mutations', () => { ...@@ -78,6 +78,8 @@ describe('Value Stream Analytics mutations', () => {
stages: [{}, { name: "Can't be blank" }, {}, {}, {}, {}, {}, {}], stages: [{}, { name: "Can't be blank" }, {}, {}, {}, {}, {}, {}],
}; };
const pagination = { page: 10, hasNextPage: true };
it.each` it.each`
mutation | payload | expectedState mutation | payload | expectedState
${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }} ${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }}
...@@ -91,6 +93,7 @@ describe('Value Stream Analytics mutations', () => { ...@@ -91,6 +93,7 @@ describe('Value Stream Analytics mutations', () => {
${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }} ${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }}
${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }} ${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }}
${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }} ${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }}
${types.SET_PAGINATION} | ${pagination} | ${{ pagination }}
`( `(
'$mutation with payload $payload will update state with $expectedState', '$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => { ({ mutation, payload, expectedState }) => {
......
...@@ -225,7 +225,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do ...@@ -225,7 +225,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do
expect(data_collector_params[:direction]).to eq(:asc) expect(data_collector_params[:direction]).to eq(:asc)
end end
it 'adds corting params to data attributes' do it 'adds sorting params to data attributes' do
data_attributes = subject.to_data_attributes data_attributes = subject.to_data_attributes
expect(data_attributes[:sort]).to eq('duration') expect(data_attributes[:sort]).to eq('duration')
......
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