Commit 8d3dc79d authored by Miguel Rincon's avatar Miguel Rincon

Merge branch '321079-fe-vsa-add-url-sort-params' into 'master'

[FE] VSA - Add URL sort params

See merge request gitlab-org/gitlab!62439
parents cb9cf4ab ba9face4
...@@ -94,6 +94,17 @@ export default { ...@@ -94,6 +94,17 @@ export default {
}, },
query() { query() {
const selectedProjectIds = this.selectedProjectIds?.length ? this.selectedProjectIds : null; const selectedProjectIds = this.selectedProjectIds?.length ? this.selectedProjectIds : null;
const paginationUrlParams = !this.isOverviewStageSelected
? {
sort: this.pagination?.sort || null,
direction: this.pagination?.direction || null,
page: this.pagination?.page || null,
}
: {
sort: null,
direction: null,
page: null,
};
return { return {
value_stream_id: this.selectedValueStream?.id || null, value_stream_id: this.selectedValueStream?.id || null,
...@@ -101,8 +112,7 @@ export default { ...@@ -101,8 +112,7 @@ export default {
created_after: toYmd(this.startDate), created_after: toYmd(this.startDate),
created_before: toYmd(this.endDate), created_before: toYmd(this.endDate),
stage_id: (!this.isOverviewStageSelected && this.selectedStage?.id) || null, // the `overview` stage is always the default, so dont persist the id if its selected stage_id: (!this.isOverviewStageSelected && this.selectedStage?.id) || null, // the `overview` stage is always the default, so dont persist the id if its selected
sort: (!this.isOverviewStageSelected && this.pagination?.sort) || null, ...paginationUrlParams,
direction: (!this.isOverviewStageSelected && this.pagination?.direction) || null,
}; };
}, },
stageCount() { stageCount() {
...@@ -134,7 +144,7 @@ export default { ...@@ -134,7 +144,7 @@ export default {
this.setDefaultSelectedStage(); this.setDefaultSelectedStage();
} else { } else {
this.setSelectedStage(stage); this.setSelectedStage(stage);
this.fetchStageData(stage.slug); this.updateStageTablePagination({ ...this.pagination, page: 1 });
} }
}, },
onHandleUpdatePagination(data) { onHandleUpdatePagination(data) {
......
...@@ -28,6 +28,7 @@ export default () => { ...@@ -28,6 +28,7 @@ export default () => {
label_name = [], label_name = [],
sort, sort,
direction, direction,
page,
} = urlQueryToFilter(window.location.search); } = urlQueryToFilter(window.location.search);
store.dispatch('initializeCycleAnalytics', { store.dispatch('initializeCycleAnalytics', {
...@@ -37,7 +38,11 @@ export default () => { ...@@ -37,7 +38,11 @@ export default () => {
selectedAssigneeList: assignee_username, selectedAssigneeList: assignee_username,
selectedLabelList: label_name, selectedLabelList: label_name,
featureFlags: { hasDurationChart }, featureFlags: { hasDurationChart },
pagination: { sort: sort?.value || null, direction: direction?.value || null }, pagination: {
sort: sort?.value || null,
direction: direction?.value || null,
page: page?.value || null,
},
}); });
return new Vue({ return new Vue({
......
...@@ -30,10 +30,7 @@ export const setFeatureFlags = ({ commit }, featureFlags) => ...@@ -30,10 +30,7 @@ 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, getters: { paginationParams } }, stage) => { export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
commit(types.SET_SELECTED_STAGE, stage);
commit(types.SET_PAGINATION, { ...paginationParams, page: 1, hasNextPage: null });
};
export const setDateRange = ( export const setDateRange = (
{ commit, dispatch, getters: { isOverviewStageSelected }, state: { selectedStage } }, { commit, dispatch, getters: { isOverviewStageSelected }, state: { selectedStage } },
......
...@@ -11,10 +11,7 @@ import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigat ...@@ -11,10 +11,7 @@ import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigat
import StageTableNew from 'ee/analytics/cycle_analytics/components/stage_table_new.vue'; import StageTableNew from 'ee/analytics/cycle_analytics/components/stage_table_new.vue';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import { import { OVERVIEW_STAGE_ID } from 'ee/analytics/cycle_analytics/constants';
PAGINATION_SORT_FIELD_END_EVENT,
PAGINATION_SORT_DIRECTION_DESC,
} from 'ee/analytics/cycle_analytics/constants';
import createStore from 'ee/analytics/cycle_analytics/store'; import createStore from 'ee/analytics/cycle_analytics/store';
import Daterange from 'ee/analytics/shared/components/daterange.vue'; import Daterange from 'ee/analytics/shared/components/daterange.vue';
import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue'; import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue';
...@@ -142,7 +139,7 @@ describe('Value Stream Analytics component', () => { ...@@ -142,7 +139,7 @@ describe('Value Stream Analytics component', () => {
...opts, ...opts,
}); });
if (withStageSelected) { if (withStageSelected || selectedStage) {
await store.dispatch( await store.dispatch(
'receiveGroupStagesSuccess', 'receiveGroupStagesSuccess',
mockData.customizableStagesAndEvents.stages, mockData.customizableStagesAndEvents.stages,
...@@ -181,8 +178,10 @@ describe('Value Stream Analytics component', () => { ...@@ -181,8 +178,10 @@ describe('Value Stream Analytics component', () => {
expect(wrapper.findComponent(TypeOfWorkCharts).exists()).toBe(flag); expect(wrapper.findComponent(TypeOfWorkCharts).exists()).toBe(flag);
}; };
const findPathNavigation = () => wrapper.findComponent(PathNavigation);
const displaysPathNavigation = (flag) => { const displaysPathNavigation = (flag) => {
expect(wrapper.findComponent(PathNavigation).exists()).toBe(flag); expect(findPathNavigation().exists()).toBe(flag);
}; };
const displaysFilterBar = (flag) => { const displaysFilterBar = (flag) => {
...@@ -350,10 +349,7 @@ describe('Value Stream Analytics component', () => { ...@@ -350,10 +349,7 @@ describe('Value Stream Analytics component', () => {
beforeEach(async () => { beforeEach(async () => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mockRequiredRoutes(mock); mockRequiredRoutes(mock);
wrapper = await createComponent({ wrapper = await createComponent({ selectedStage: mockData.issueStage });
withStageSelected: true,
selectedStage: mockData.issueStage,
});
}); });
it('displays the stage table', () => { it('displays the stage table', () => {
...@@ -405,7 +401,7 @@ describe('Value Stream Analytics component', () => { ...@@ -405,7 +401,7 @@ describe('Value Stream Analytics component', () => {
.onGet(mockData.endpoints.stageData) .onGet(mockData.endpoints.stageData)
.reply(httpStatusCodes.NOT_FOUND, { response: { status: httpStatusCodes.NOT_FOUND } }); .reply(httpStatusCodes.NOT_FOUND, { response: { status: httpStatusCodes.NOT_FOUND } });
await createComponent({ withStageSelected: true, selectedStage: mockData.issueStage }); await createComponent({ selectedStage: mockData.issueStage });
await findError('There was an error fetching data for the selected stage'); await findError('There was an error fetching data for the selected stage');
}); });
...@@ -447,6 +443,48 @@ describe('Value Stream Analytics component', () => { ...@@ -447,6 +443,48 @@ describe('Value Stream Analytics component', () => {
}); });
}); });
describe('Path navigation', () => {
const selectedStage = { title: 'Plan', slug: 2 };
const overviewStage = { title: 'Overview', slug: OVERVIEW_STAGE_ID };
let actionSpies = {};
beforeEach(async () => {
mock = new MockAdapter(axios);
mockRequiredRoutes(mock);
wrapper = await createComponent();
actionSpies = {
setDefaultSelectedStage: jest.spyOn(wrapper.vm, 'setDefaultSelectedStage'),
setSelectedStage: jest.spyOn(wrapper.vm, 'setSelectedStage'),
updateStageTablePagination: jest.spyOn(wrapper.vm, 'updateStageTablePagination'),
};
});
afterEach(() => {
wrapper.destroy();
mock.restore();
wrapper = null;
});
it('when a stage is selected', () => {
findPathNavigation().vm.$emit('selected', selectedStage);
expect(actionSpies.setDefaultSelectedStage).not.toHaveBeenCalled();
expect(actionSpies.setSelectedStage).toHaveBeenCalledWith(selectedStage);
expect(actionSpies.updateStageTablePagination).toHaveBeenCalledWith({
...mockData.initialPaginationQuery,
page: 1,
});
});
it('when the overview is selected', () => {
findPathNavigation().vm.$emit('selected', overviewStage);
expect(actionSpies.setSelectedStage).not.toHaveBeenCalled();
expect(actionSpies.updateStageTablePagination).not.toHaveBeenCalled();
expect(actionSpies.setDefaultSelectedStage).toHaveBeenCalled();
});
});
describe('Url parameters', () => { describe('Url parameters', () => {
const defaultParams = { const defaultParams = {
value_stream_id: selectedValueStream.id, value_stream_id: selectedValueStream.id,
...@@ -456,6 +494,7 @@ describe('Value Stream Analytics component', () => { ...@@ -456,6 +494,7 @@ describe('Value Stream Analytics component', () => {
project_ids: null, project_ids: null,
sort: null, sort: null,
direction: null, direction: null,
page: null,
}; };
const selectedProjectIds = mockData.selectedProjects.map(({ id }) => getIdFromGraphQLId(id)); const selectedProjectIds = mockData.selectedProjects.map(({ id }) => getIdFromGraphQLId(id));
...@@ -527,17 +566,20 @@ describe('Value Stream Analytics component', () => { ...@@ -527,17 +566,20 @@ describe('Value Stream Analytics component', () => {
describe('with selectedStage set', () => { describe('with selectedStage set', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = await createComponent(); wrapper = await createComponent({
store.dispatch('setSelectedStage', selectedStage); initialState: {
await wrapper.vm.$nextTick(); ...initialCycleAnalyticsState,
pagination: mockData.initialPaginationQuery,
},
selectedStage,
});
}); });
it('sets the stage, sort and direction parameters', async () => { it('sets the stage, sort, direction and page parameters', async () => {
await shouldMergeUrlParams(wrapper, { await shouldMergeUrlParams(wrapper, {
...defaultParams, ...defaultParams,
...mockData.initialPaginationQuery,
stage_id: selectedStage.id, stage_id: selectedStage.id,
direction: PAGINATION_SORT_DIRECTION_DESC,
sort: PAGINATION_SORT_FIELD_END_EVENT,
}); });
}); });
}); });
......
...@@ -325,11 +325,16 @@ export const selectedProjects = [ ...@@ -325,11 +325,16 @@ export const selectedProjects = [
export const pathNavIssueMetric = 172800; export const pathNavIssueMetric = 172800;
export const initialPaginationQuery = {
page: 15,
sort: PAGINATION_SORT_FIELD_END_EVENT,
direction: PAGINATION_SORT_DIRECTION_DESC,
};
export const initialPaginationState = { export const initialPaginationState = {
...initialPaginationQuery,
page: null, page: null,
hasNextPage: false, hasNextPage: false,
sort: PAGINATION_SORT_FIELD_END_EVENT,
direction: PAGINATION_SORT_DIRECTION_DESC,
}; };
export const basePaginationResult = { export const basePaginationResult = {
......
...@@ -92,12 +92,10 @@ describe('Value Stream Analytics actions', () => { ...@@ -92,12 +92,10 @@ describe('Value Stream Analytics actions', () => {
describe('setSelectedStage', () => { describe('setSelectedStage', () => {
const data = { id: 'someStageId' }; const data = { id: 'someStageId' };
const payload = { hasNextPage: null, page: 1 };
it(`dispatches the ${types.SET_SELECTED_STAGE} and ${types.SET_PAGINATION} actions`, () => { it(`dispatches the ${types.SET_SELECTED_STAGE} and ${types.SET_PAGINATION} actions`, () => {
return testAction(actions.setSelectedStage, data, { ...state, selectedValueStream: {} }, [ return testAction(actions.setSelectedStage, data, { ...state, selectedValueStream: {} }, [
{ type: types.SET_SELECTED_STAGE, payload: data }, { type: types.SET_SELECTED_STAGE, payload: data },
{ type: types.SET_PAGINATION, payload },
]); ]);
}); });
}); });
...@@ -625,6 +623,16 @@ describe('Value Stream Analytics actions', () => { ...@@ -625,6 +623,16 @@ describe('Value Stream Analytics actions', () => {
}); });
}); });
describe('with pagination parameters', () => {
it('dispatches "setSelectedStage" and "fetchStageData"', async () => {
const stage = { id: 2, title: 'plan' };
const pagination = { sort: 'end_event', direction: 'desc', page: 1337 };
const payload = { ...initialData, stage, pagination };
await actions.initializeCycleAnalytics(store, payload);
expect(mockCommit).toHaveBeenCalledWith('INITIALIZE_VSA', payload);
});
});
describe('without a selected stage', () => { describe('without a selected stage', () => {
it('dispatches "setDefaultSelectedStage"', async () => { it('dispatches "setDefaultSelectedStage"', async () => {
await actions.initializeCycleAnalytics(store, { await actions.initializeCycleAnalytics(store, {
......
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