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 {
'scatterplotYaxisLabel',
'hasNoAccessError',
'isChartEnabled',
'isFilteringByDaysToMerge',
]),
...mapGetters('table', [
'sortFieldDropdownLabel',
......@@ -103,6 +104,7 @@ export default {
'fetchChartData',
'setMetricType',
'updateSelectedItems',
'resetMainChartSelection',
'setChartEnabled',
]),
...mapActions('table', ['setSortField', 'setPage', 'toggleSortOrder', 'setColumnMetric']),
......@@ -154,7 +156,18 @@ export default {
"
/>
<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
ref="mainChart"
class="mb-4"
......
......@@ -66,12 +66,19 @@ export const setMetricType = ({ commit, dispatch }, { chartKey, metricType }) =>
dispatch('fetchChartData', chartKey);
};
export const updateSelectedItems = (
{ commit, dispatch },
{ chartKey, item, skipReload = false },
) => {
export const updateSelectedItems = ({ commit, dispatch }, { 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) {
// update secondary charts
dispatch('fetchSecondaryChartData');
......
......@@ -153,5 +153,7 @@ export const hasNoAccessError = state =>
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
export default () => {};
......@@ -6,11 +6,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => {
// let's reset the current selection first
// with skipReload=true we avoid data from being fetched here
dispatch(
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
dispatch('charts/resetMainChartSelection', true, { root: true });
// 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
......@@ -24,11 +20,7 @@ export const setGroupNamespace = ({ commit, dispatch }, groupNamespace) => {
export const setProjectPath = ({ commit, dispatch }, projectPath) => {
commit(types.SET_PROJECT_PATH, projectPath);
dispatch(
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
dispatch('charts/resetMainChartSelection', true, { root: true });
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true });
......@@ -40,11 +32,7 @@ export const setProjectPath = ({ commit, dispatch }, projectPath) => {
export const setPath = ({ commit, dispatch }, path) => {
commit(types.SET_PATH, path);
dispatch(
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
dispatch('charts/resetMainChartSelection', true, { root: true });
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true });
......@@ -58,11 +46,7 @@ export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDat
if (skipFetch) return false;
dispatch(
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
{ root: true },
);
dispatch('charts/resetMainChartSelection', true, { root: true });
return dispatch('charts/fetchChartData', chartKeys.main, { root: true }).then(() => {
dispatch('charts/fetchSecondaryChartData', null, { root: true });
......
......@@ -25,11 +25,11 @@ describe('ProductivityApp component', () => {
};
const actionSpies = {
updateSelectedItems: jest.fn(),
setSortField: jest.fn(),
setPage: jest.fn(),
toggleSortOrder: jest.fn(),
setColumnMetric: jest.fn(),
resetMainChartSelection: jest.fn(),
};
const mainChartData = { 1: 2, 2: 3 };
......@@ -59,6 +59,7 @@ describe('ProductivityApp component', () => {
});
const findMainMetricChart = () => wrapper.find({ ref: 'mainChart' });
const findClearFilterButton = () => wrapper.find({ ref: 'clearChartFiltersBtn' });
const findSecondaryChartsSection = () => wrapper.find({ ref: 'secondaryCharts' });
const findTimeBasedMetricChart = () => wrapper.find({ ref: 'timeBasedChart' });
const findCommitBasedMetricChart = () => wrapper.find({ ref: 'commitBasedChart' });
......@@ -161,6 +162,8 @@ describe('ProductivityApp component', () => {
describe('when an item on the chart is clicked', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch');
const data = {
chart: null,
params: {
......@@ -176,13 +179,29 @@ describe('ProductivityApp component', () => {
});
it('dispatches updateSelectedItems action', () => {
expect(actionSpies.updateSelectedItems).toHaveBeenCalledWith({
expect(store.dispatch).toHaveBeenCalledWith('charts/updateSelectedItems', {
chartKey: chartKeys.main,
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', () => {
it('renders a metric chart component', () => {
expect(findTimeBasedMetricChart().exists()).toBe(true);
......
......@@ -252,14 +252,26 @@ describe('Productivity analytics chart actions', () => {
});
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)', () => {
const item = 5;
it('should commit selected chart item and dispatch fetchSecondaryChartData and setPage', done => {
testAction(
actions.updateSelectedItems,
{ chartKey, item },
actions.resetMainChartSelection,
null,
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 }],
done,
);
......@@ -269,8 +281,8 @@ describe('Productivity analytics chart actions', () => {
describe('when skipReload is true', () => {
it('should commit selected chart and it should not dispatch any further actions', done => {
testAction(
actions.updateSelectedItems,
{ chartKey, item: null, skipReload: true },
actions.resetMainChartSelection,
true,
mockedContext.state,
[
{
......
......@@ -303,4 +303,16 @@ describe('Productivity analytics chart getters', () => {
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', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_GROUP_NAMESPACE, groupNamespace);
expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
'charts/resetMainChartSelection',
true,
{ root: true },
]);
......@@ -58,8 +58,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PROJECT_PATH, projectPath);
expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
'charts/resetMainChartSelection',
true,
{ root: true },
]);
......@@ -90,8 +90,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, path);
expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
'charts/resetMainChartSelection',
true,
{ root: true },
]);
......@@ -122,8 +122,8 @@ describe('Productivity analytics filter actions', () => {
expect(store.commit).toHaveBeenCalledWith(types.SET_PATH, { startDate, endDate });
expect(store.dispatch.mock.calls[0]).toEqual([
'charts/updateSelectedItems',
{ chartKey: chartKeys.main, item: null, skipReload: true },
'charts/resetMainChartSelection',
true,
{ root: true },
]);
......
......@@ -3361,6 +3361,9 @@ msgstr ""
msgid "Clear"
msgstr ""
msgid "Clear chart filters"
msgstr ""
msgid "Clear input"
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