Commit 4f55b1fe authored by Martin Wortschack's avatar Martin Wortschack Committed by Phil Hughes

Add "Clear chart data" button

- Adds a button next to the main chart
which enables users to clear selected items
on the main chart
parent bf5cf820
...@@ -68,6 +68,7 @@ export default { ...@@ -68,6 +68,7 @@ export default {
'scatterplotYaxisLabel', 'scatterplotYaxisLabel',
'hasNoAccessError', 'hasNoAccessError',
'isChartEnabled', 'isChartEnabled',
'isFilteringByDaysToMerge',
]), ]),
...mapGetters('table', [ ...mapGetters('table', [
'sortFieldDropdownLabel', 'sortFieldDropdownLabel',
...@@ -103,6 +104,7 @@ export default { ...@@ -103,6 +104,7 @@ export default {
'fetchChartData', 'fetchChartData',
'setMetricType', 'setMetricType',
'updateSelectedItems', 'updateSelectedItems',
'resetMainChartSelection',
'setChartEnabled', 'setChartEnabled',
]), ]),
...mapActions('table', ['setSortField', 'setPage', 'toggleSortOrder', 'setColumnMetric']), ...mapActions('table', ['setSortField', 'setPage', 'toggleSortOrder', 'setColumnMetric']),
...@@ -154,7 +156,18 @@ export default { ...@@ -154,7 +156,18 @@ export default {
" "
/> />
<template v-if="showAppContent"> <template v-if="showAppContent">
<h4>{{ s__('ProductivityAnalytics|Merge Requests') }}</h4> <div class="d-flex justify-content-between">
<h4>{{ s__('ProductivityAnalytics|Merge Requests') }}</h4>
<gl-button
v-if="isFilteringByDaysToMerge"
ref="clearChartFiltersBtn"
class="btn-link float-right"
type="button"
variant="default"
@click="resetMainChartSelection()"
>{{ __('Clear chart filters') }}</gl-button
>
</div>
<metric-chart <metric-chart
ref="mainChart" ref="mainChart"
class="mb-4" class="mb-4"
......
...@@ -66,12 +66,19 @@ export const setMetricType = ({ commit, dispatch }, { chartKey, metricType }) => ...@@ -66,12 +66,19 @@ export const setMetricType = ({ commit, dispatch }, { chartKey, metricType }) =>
dispatch('fetchChartData', chartKey); dispatch('fetchChartData', chartKey);
}; };
export const updateSelectedItems = ( export const updateSelectedItems = ({ commit, dispatch }, { chartKey, item }) => {
{ commit, dispatch },
{ chartKey, item, skipReload = false },
) => {
commit(types.UPDATE_SELECTED_CHART_ITEMS, { chartKey, item }); commit(types.UPDATE_SELECTED_CHART_ITEMS, { chartKey, item });
// update secondary charts
dispatch('fetchSecondaryChartData');
// let's reset the page on the MR table and fetch data
dispatch('table/setPage', 0, { root: true });
};
export const resetMainChartSelection = ({ commit, dispatch }, skipReload = false) => {
commit(types.UPDATE_SELECTED_CHART_ITEMS, { chartKey: chartKeys.main, item: null });
if (!skipReload) { if (!skipReload) {
// update secondary charts // update secondary charts
dispatch('fetchSecondaryChartData'); dispatch('fetchSecondaryChartData');
......
...@@ -153,5 +153,7 @@ export const hasNoAccessError = state => ...@@ -153,5 +153,7 @@ export const hasNoAccessError = state =>
export const isChartEnabled = state => chartKey => state.charts[chartKey].enabled; export const isChartEnabled = state => chartKey => state.charts[chartKey].enabled;
export const isFilteringByDaysToMerge = state => state.charts[chartKeys.main].selected.length > 0;
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -6,11 +6,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => { ...@@ -6,11 +6,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => {
// let's reset the current selection first // let's reset the current selection first
// with skipReload=true we avoid data from being fetched here // with skipReload=true we avoid data from being fetched here
dispatch( dispatch('charts/resetMainChartSelection', true, { root: true });
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
// let's fetch the main chart data first to see if the user has access to the selected group // let's fetch the main chart data first to see if the user has access to the selected group
// if there's no 403, then we fetch all remaining chart data and table data // if there's no 403, then we fetch all remaining chart data and table data
...@@ -24,11 +20,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => { ...@@ -24,11 +20,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => {
export const setProjectPath = ({ commit, dispatch }, projectPath) => { export const setProjectPath = ({ commit, dispatch }, projectPath) => {
commit(types.SET_PROJECT_PATH, projectPath); commit(types.SET_PROJECT_PATH, projectPath);
dispatch( dispatch('charts/resetMainChartSelection', true, { root: true });
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => { return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true }); dispatch('charts/fetchSecondaryChartData', null, { root: true });
...@@ -40,11 +32,7 @@ export const setProjectPath = ({ commit, dispatch }, projectPath) => { ...@@ -40,11 +32,7 @@ export const setProjectPath = ({ commit, dispatch }, projectPath) => {
export const setPath = ({ commit, dispatch }, path) => { export const setPath = ({ commit, dispatch }, path) => {
commit(types.SET_PATH, path); commit(types.SET_PATH, path);
dispatch( dispatch('charts/resetMainChartSelection', true, { root: true });
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => { return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true }); dispatch('charts/fetchSecondaryChartData', null, { root: true });
...@@ -58,11 +46,7 @@ export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDat ...@@ -58,11 +46,7 @@ export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDat
if (skipFetch) return false; if (skipFetch) return false;
dispatch( dispatch('charts/resetMainChartSelection', true, { root: true });
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => { return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true }); dispatch('charts/fetchSecondaryChartData', null, { root: true });
......
...@@ -25,11 +25,11 @@ describe('ProductivityApp component', () => { ...@@ -25,11 +25,11 @@ describe('ProductivityApp component', () => {
}; };
const actionSpies = { const actionSpies = {
updateSelectedItems: jest.fn(),
setSortField: jest.fn(), setSortField: jest.fn(),
setPage: jest.fn(), setPage: jest.fn(),
toggleSortOrder: jest.fn(), toggleSortOrder: jest.fn(),
setColumnMetric: jest.fn(), setColumnMetric: jest.fn(),
resetMainChartSelection: jest.fn(),
}; };
const mainChartData = { 1: 2, 2: 3 }; const mainChartData = { 1: 2, 2: 3 };
...@@ -59,6 +59,7 @@ describe('ProductivityApp component', () => { ...@@ -59,6 +59,7 @@ describe('ProductivityApp component', () => {
}); });
const findMainMetricChart = () => wrapper.find({ ref: 'mainChart' }); const findMainMetricChart = () => wrapper.find({ ref: 'mainChart' });
const findClearFilterButton = () => wrapper.find({ ref: 'clearChartFiltersBtn' });
const findSecondaryChartsSection = () => wrapper.find({ ref: 'secondaryCharts' }); const findSecondaryChartsSection = () => wrapper.find({ ref: 'secondaryCharts' });
const findTimeBasedMetricChart = () => wrapper.find({ ref: 'timeBasedChart' }); const findTimeBasedMetricChart = () => wrapper.find({ ref: 'timeBasedChart' });
const findCommitBasedMetricChart = () => wrapper.find({ ref: 'commitBasedChart' }); const findCommitBasedMetricChart = () => wrapper.find({ ref: 'commitBasedChart' });
...@@ -161,6 +162,8 @@ describe('ProductivityApp component', () => { ...@@ -161,6 +162,8 @@ describe('ProductivityApp component', () => {
describe('when an item on the chart is clicked', () => { describe('when an item on the chart is clicked', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(store, 'dispatch');
const data = { const data = {
chart: null, chart: null,
params: { params: {
...@@ -176,13 +179,29 @@ describe('ProductivityApp component', () => { ...@@ -176,13 +179,29 @@ describe('ProductivityApp component', () => {
}); });
it('dispatches updateSelectedItems action', () => { it('dispatches updateSelectedItems action', () => {
expect(actionSpies.updateSelectedItems).toHaveBeenCalledWith({ expect(store.dispatch).toHaveBeenCalledWith('charts/updateSelectedItems', {
chartKey: chartKeys.main, chartKey: chartKeys.main,
item: 0, item: 0,
}); });
}); });
}); });
describe('when the main chart has selected items', () => {
beforeEach(() => {
wrapper.vm.$store.state.charts.charts[chartKeys.main].selected = [1];
});
it('renders the "Clear chart data" button', () => {
expect(findClearFilterButton().exists()).toBe(true);
});
it('dispatches resetMainChartSelection action when the user clicks on the "Clear chart data" button', () => {
findClearFilterButton().vm.$emit('click');
expect(actionSpies.resetMainChartSelection).toHaveBeenCalled();
});
});
describe('Time based histogram', () => { describe('Time based histogram', () => {
it('renders a metric chart component', () => { it('renders a metric chart component', () => {
expect(findTimeBasedMetricChart().exists()).toBe(true); expect(findTimeBasedMetricChart().exists()).toBe(true);
......
...@@ -252,14 +252,26 @@ describe('Productivity analytics chart actions', () => { ...@@ -252,14 +252,26 @@ describe('Productivity analytics chart actions', () => {
}); });
describe('updateSelectedItems', () => { describe('updateSelectedItems', () => {
it('should commit selected chart item and dispatch fetchSecondaryChartData and setPage', done => {
testAction(
actions.updateSelectedItems,
{ chartKey, item: 5 },
mockedContext.state,
[{ type: types.UPDATE_SELECTED_CHART_ITEMS, payload: { chartKey, item: 5 } }],
[{ type: 'fetchSecondaryChartData' }, { type: 'table/setPage', payload: 0 }],
done,
);
});
});
describe('resetMainChartSelection', () => {
describe('when skipReload is false (by default)', () => { describe('when skipReload is false (by default)', () => {
const item = 5;
it('should commit selected chart item and dispatch fetchSecondaryChartData and setPage', done => { it('should commit selected chart item and dispatch fetchSecondaryChartData and setPage', done => {
testAction( testAction(
actions.updateSelectedItems, actions.resetMainChartSelection,
{ chartKey, item }, null,
mockedContext.state, mockedContext.state,
[{ type: types.UPDATE_SELECTED_CHART_ITEMS, payload: { chartKey, item } }], [{ type: types.UPDATE_SELECTED_CHART_ITEMS, payload: { chartKey, item: null } }],
[{ type: 'fetchSecondaryChartData' }, { type: 'table/setPage', payload: 0 }], [{ type: 'fetchSecondaryChartData' }, { type: 'table/setPage', payload: 0 }],
done, done,
); );
...@@ -269,8 +281,8 @@ describe('Productivity analytics chart actions', () => { ...@@ -269,8 +281,8 @@ describe('Productivity analytics chart actions', () => {
describe('when skipReload is true', () => { describe('when skipReload is true', () => {
it('should commit selected chart and it should not dispatch any further actions', done => { it('should commit selected chart and it should not dispatch any further actions', done => {
testAction( testAction(
actions.updateSelectedItems, actions.resetMainChartSelection,
{ chartKey, item: null, skipReload: true }, true,
mockedContext.state, mockedContext.state,
[ [
{ {
......
...@@ -303,4 +303,16 @@ describe('Productivity analytics chart getters', () => { ...@@ -303,4 +303,16 @@ describe('Productivity analytics chart getters', () => {
expect(getters.isChartEnabled(state)(chartKey)).toBe(false); expect(getters.isChartEnabled(state)(chartKey)).toBe(false);
}); });
}); });
describe('isFilteringByDaysToMerge', () => {
it('returns true if there are items selected on the main chart', () => {
state.charts[chartKeys.main].selected = [1, 2];
expect(getters.isFilteringByDaysToMerge(state)).toBe(true);
});
it('returns false if there are no items selected on the main chart', () => {
state.charts[chartKeys.main].selected = [];
expect(getters.isFilteringByDaysToMerge(state)).toBe(false);
});
});
}); });
...@@ -26,8 +26,8 @@ describe('Productivity analytics filter actions', () => { ...@@ -26,8 +26,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_GROUP_NAMESPACE, groupNamespace); expect(store.commit).toHaveBeenCalledWith(types.SET_GROUP_NAMESPACE, groupNamespace);
expect(store.dispatch.mock.calls[0]).toEqual([ expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems', 'charts/resetMainChartSelection',
{ chartKey: chartKeys.main, item: null, skipReload: true }, true,
{ root: true }, { root: true },
]); ]);
...@@ -58,8 +58,8 @@ describe('Productivity analytics filter actions', () => { ...@@ -58,8 +58,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PROJECT_PATH, projectPath); expect(store.commit).toHaveBeenCalledWith(types.SET_PROJECT_PATH, projectPath);
expect(store.dispatch.mock.calls[0]).toEqual([ expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems', 'charts/resetMainChartSelection',
{ chartKey: chartKeys.main, item: null, skipReload: true }, true,
{ root: true }, { root: true },
]); ]);
...@@ -90,8 +90,8 @@ describe('Productivity analytics filter actions', () => { ...@@ -90,8 +90,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, path); expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, path);
expect(store.dispatch.mock.calls[0]).toEqual([ expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems', 'charts/resetMainChartSelection',
{ chartKey: chartKeys.main, item: null, skipReload: true }, true,
{ root: true }, { root: true },
]); ]);
...@@ -122,8 +122,8 @@ describe('Productivity analytics filter actions', () => { ...@@ -122,8 +122,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, { startDate, endDate }); expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, { startDate, endDate });
expect(store.dispatch.mock.calls[0]).toEqual([ expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems', 'charts/resetMainChartSelection',
{ chartKey: chartKeys.main, item: null, skipReload: true }, true,
{ root: true }, { root: true },
]); ]);
......
...@@ -3361,6 +3361,9 @@ msgstr "" ...@@ -3361,6 +3361,9 @@ msgstr ""
msgid "Clear" msgid "Clear"
msgstr "" msgstr ""
msgid "Clear chart filters"
msgstr ""
msgid "Clear input" msgid "Clear input"
msgstr "" msgstr ""
......
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