Commit 0ccf9639 authored by Martin Wortschack's avatar Martin Wortschack Committed by Filipa Lacerda

Improve scatterplot performance

- Store transformed data in store
and optimize median computation
parent cc602cfa
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { chartKeys } from '../../../constants'; import { getDateInPast } from '~/lib/utils/datetime_utility';
import { chartKeys, scatterPlotAddonQueryDays } from '../../../constants';
import { transformScatterData } from '../../../utils';
/** /**
* Fetches data for all charts except for the main chart * Fetches data for all charts except for the main chart
...@@ -27,14 +29,28 @@ export const fetchChartData = ({ dispatch, getters, state, rootState }, chartKey ...@@ -27,14 +29,28 @@ export const fetchChartData = ({ dispatch, getters, state, rootState }, chartKey
.get(rootState.endpoint, { params }) .get(rootState.endpoint, { params })
.then(response => { .then(response => {
const { data } = response; const { data } = response;
dispatch('receiveChartDataSuccess', { chartKey, data });
if (chartKey === chartKeys.scatterplot) {
const transformedData = transformScatterData(
data,
new Date(getDateInPast(rootState.filters.startDate, scatterPlotAddonQueryDays)),
new Date(rootState.filters.endDate),
);
dispatch('receiveChartDataSuccess', { chartKey, data, transformedData });
} else {
dispatch('receiveChartDataSuccess', { chartKey, data });
}
}) })
.catch(error => dispatch('receiveChartDataError', { chartKey, error })); .catch(error => dispatch('receiveChartDataError', { chartKey, error }));
} }
}; };
export const receiveChartDataSuccess = ({ commit }, { chartKey, data = {} }) => { export const receiveChartDataSuccess = (
commit(types.RECEIVE_CHART_DATA_SUCCESS, { chartKey, data }); { commit },
{ chartKey, data = {}, transformedData = null },
) => {
commit(types.RECEIVE_CHART_DATA_SUCCESS, { chartKey, data, transformedData });
}; };
export const receiveChartDataError = ({ commit }, { chartKey, error }) => { export const receiveChartDataError = ({ commit }, { chartKey, error }) => {
......
...@@ -54,28 +54,12 @@ export const getColumnChartData = state => chartKey => { ...@@ -54,28 +54,12 @@ export const getColumnChartData = state => chartKey => {
export const chartHasData = state => chartKey => !_.isEmpty(state.charts[chartKey].data); export const chartHasData = state => chartKey => !_.isEmpty(state.charts[chartKey].data);
/**
* Creates a series array of main data for the scatterplot chart.
*
* Takes an object of the form
* {
* "1": { "metric": 138", merged_at": "2019-07-09T14:58:07.756Z" },
* "2": { "metric": 139, "merged_at": "2019-07-10T11:13:23.557Z" },
* "3": { "metric": 24, "merged_at": "2019-07-01T07:06:23.193Z" }
* }
*
* and creates the following structure:
*
* [
* ["2019-07-01T07:06:23.193Z", 24],
* ["2019-07-09T14:58:07.756Z", 138],
* ["2019-07-10T11:13:23.557Z", 139],
* ]
*
* It eliminates items which were merged before the startDate (minus an additional days offset).
*/
export const getScatterPlotMainData = (state, getters, rootState) => export const getScatterPlotMainData = (state, getters, rootState) =>
getScatterPlotData(state.charts.scatterplot.data, rootState.filters.startDate); getScatterPlotData(
state.charts.scatterplot.transformedData,
new Date(rootState.filters.startDate),
new Date(rootState.filters.endDate),
);
/** /**
* Creates a series array of median data for the scatterplot chart. * Creates a series array of median data for the scatterplot chart.
...@@ -83,10 +67,11 @@ export const getScatterPlotMainData = (state, getters, rootState) => ...@@ -83,10 +67,11 @@ export const getScatterPlotMainData = (state, getters, rootState) =>
* It calls getMedianLineData internally with the raw scatterplot data and the computed by getters.getScatterPlotMainData. * It calls getMedianLineData internally with the raw scatterplot data and the computed by getters.getScatterPlotMainData.
* scatterPlotAddonQueryDays is necessary since we query the API with an additional day offset to compute the median. * scatterPlotAddonQueryDays is necessary since we query the API with an additional day offset to compute the median.
*/ */
export const getScatterPlotMedianData = (state, getters) => export const getScatterPlotMedianData = (state, getters, rootState) =>
getMedianLineData( getMedianLineData(
state.charts.scatterplot.data, state.charts.scatterplot.transformedData,
getters.getScatterPlotMainData, new Date(rootState.filters.startDate),
new Date(rootState.filters.endDate),
scatterPlotAddonQueryDays, scatterPlotAddonQueryDays,
); );
......
import * as types from './mutation_types'; import * as types from './mutation_types';
import { chartKeys } from '../../../constants';
export default { export default {
[types.RESET_CHART_DATA](state, chartKey) { [types.RESET_CHART_DATA](state, chartKey) {
...@@ -8,15 +9,23 @@ export default { ...@@ -8,15 +9,23 @@ export default {
[types.REQUEST_CHART_DATA](state, chartKey) { [types.REQUEST_CHART_DATA](state, chartKey) {
state.charts[chartKey].isLoading = true; state.charts[chartKey].isLoading = true;
}, },
[types.RECEIVE_CHART_DATA_SUCCESS](state, { chartKey, data }) { [types.RECEIVE_CHART_DATA_SUCCESS](state, { chartKey, data, transformedData }) {
state.charts[chartKey].isLoading = false; state.charts[chartKey].isLoading = false;
state.charts[chartKey].errorCode = null; state.charts[chartKey].errorCode = null;
state.charts[chartKey].data = data; state.charts[chartKey].data = data;
if (chartKey === chartKeys.scatterplot) {
state.charts[chartKey].transformedData = transformedData;
}
}, },
[types.RECEIVE_CHART_DATA_ERROR](state, { chartKey, status }) { [types.RECEIVE_CHART_DATA_ERROR](state, { chartKey, status }) {
state.charts[chartKey].isLoading = false; state.charts[chartKey].isLoading = false;
state.charts[chartKey].errorCode = status; state.charts[chartKey].errorCode = status;
state.charts[chartKey].data = {}; state.charts[chartKey].data = {};
if (chartKey === chartKeys.scatterplot) {
state.charts[chartKey].transformedData = [];
}
}, },
[types.SET_METRIC_TYPE](state, { chartKey, metricType }) { [types.SET_METRIC_TYPE](state, { chartKey, metricType }) {
state.charts[chartKey].params.metricType = metricType; state.charts[chartKey].params.metricType = metricType;
......
...@@ -39,6 +39,7 @@ export default () => ({ ...@@ -39,6 +39,7 @@ export default () => ({
errorCode: null, errorCode: null,
enabled: true, enabled: true,
data: {}, data: {},
transformedData: [],
selected: [], selected: [],
params: { params: {
chartType: chartTypes.scatterplot, chartType: chartTypes.scatterplot,
......
...@@ -90,38 +90,6 @@ export const transformScatterData = (data, startDate, endDate) => { ...@@ -90,38 +90,6 @@ export const transformScatterData = (data, startDate, endDate) => {
return result; return result;
}; };
/**
* Transforms a given data object into an array
* which will be used as series data for the scatterplot chart.
* It eliminates items which were merged before a "dateInPast" and sorts
* the result by date (ascending)
*
* Takes an object of the form
* {
* "1": { "metric": 138", merged_at": "2019-07-09T14:58:07.756Z" },
* "2": { "metric": 139, "merged_at": "2019-07-10T11:13:23.557Z" },
* "3": { "metric": 24, "merged_at": "2019-07-01T07:06:23.193Z" }
* }
*
* and creates the following two-dimensional array
* where the first value is the "merged_at" date and the second value is the metric:
*
* [
* ["2019-07-01T07:06:23.193Z", 24],
* ["2019-07-09T14:58:07.756Z", 138],
* ["2019-07-10T11:13:23.557Z", 139],
* ]
*
* @param {Object} data The raw data which will be transformed
* @param {Date} dateInPast Date in the past
* @returns {Array} The transformed data array sorted by date ascending
*/
export const getScatterPlotData = (data, dateInPast) =>
Object.keys(data)
.filter(key => new Date(data[key].merged_at) >= dateInPast)
.map(key => [data[key].merged_at, data[key].metric])
.sort((a, b) => new Date(a[0]) - new Date(b[0]));
/** /**
* Brings the data the we receive from transformScatterData into a format that can be passed to the chart. * Brings the data the we receive from transformScatterData into a format that can be passed to the chart.
* Since transformScatterData contains more data than we actually want to display on the scatterplot * Since transformScatterData contains more data than we actually want to display on the scatterplot
...@@ -145,7 +113,7 @@ export const getScatterPlotData = (data, dateInPast) => ...@@ -145,7 +113,7 @@ export const getScatterPlotData = (data, dateInPast) =>
* @param {*} endDate - The end date selected by the user * @param {*} endDate - The end date selected by the user
* @returns {Array} An array with each item being another arry of two items (date, computed median) * @returns {Array} An array with each item being another arry of two items (date, computed median)
*/ */
export const getScatterPlotDataNew = (data, startDate, endDate) => { export const getScatterPlotData = (data, startDate, endDate) => {
if (!data.length) return []; if (!data.length) return [];
const startIndex = data.length - 1 - getDayDifference(startDate, endDate); const startIndex = data.length - 1 - getDayDifference(startDate, endDate);
...@@ -161,39 +129,6 @@ export const getScatterPlotDataNew = (data, startDate, endDate) => { ...@@ -161,39 +129,6 @@ export const getScatterPlotDataNew = (data, startDate, endDate) => {
return result; return result;
}; };
/**
* Computes the moving median line data.
* It takes the raw data object (which contains historical data) and the scatterData (from getScatterPlotData)
* and computes the median for every date in scatterData.
* The median for a given date in scatterData (called item) is computed by taking all metrics of the raw data into account
* which are before (or eqaul to) the the item's merged_at date
* and after (or equal to) the item's merged_at date minus a given "daysOffset" (e.g., 30 days for "30 day rolling median")
*
* i.e., moving median for a given DAY is the median the range of values (DAY-30 ... DAY)
*
* @param {Object} data The raw data which will be used for computing the median
* @param {Array} scatterData The transformed data from getScatterPlotData
* @param {Number} daysOffset The number of days that is substracted from each date in scatterData (e.g. 30 days in the past)
* @returns {Array} An array with each item being another arry of two items (date, computed median)
*/
export const getMedianLineData = (data, scatterData, daysOffset) =>
scatterData.map(item => {
const [dateString] = item;
const values = Object.keys(data)
.filter(key => {
const mergedAtDate = new Date(data[key].merged_at);
const itemDate = new Date(dateString);
return (
mergedAtDate <= itemDate && mergedAtDate >= new Date(getDateInPast(itemDate, daysOffset))
);
})
.map(key => data[key].metric);
const computedMedian = values.length ? median(values) : 0;
return [dateString, computedMedian];
});
/** /**
* Computes the moving median line data, i.e, it computes the 30 day rolling median for every item displayd on the scatterplot * Computes the moving median line data, i.e, it computes the 30 day rolling median for every item displayd on the scatterplot
* For example the 30 day rolling median for startDate=2019-09-01 and endDate=2019-09-03 is computed as follows: * For example the 30 day rolling median for startDate=2019-09-01 and endDate=2019-09-03 is computed as follows:
...@@ -210,7 +145,7 @@ export const getMedianLineData = (data, scatterData, daysOffset) => ...@@ -210,7 +145,7 @@ export const getMedianLineData = (data, scatterData, daysOffset) =>
* @param {Number} daysOffset The number of days that to look up data in the past (e.g. 30 days in the past for 30 day rolling median) * @param {Number} daysOffset The number of days that to look up data in the past (e.g. 30 days in the past for 30 day rolling median)
* @returns {Array} An array with each item being another arry of two items (date, computed median) * @returns {Array} An array with each item being another arry of two items (date, computed median)
*/ */
export const getMedianLineDataNew = (data, startDate, endDate, daysOffset) => { export const getMedianLineData = (data, startDate, endDate, daysOffset) => {
const result = []; const result = [];
const dayDiff = getDayDifference(startDate, endDate); const dayDiff = getDayDifference(startDate, endDate);
const transformedData = data.map(arr => arr.map(x => x.metric)); const transformedData = data.map(arr => arr.map(x => x.metric));
......
...@@ -32,6 +32,7 @@ export default { ...@@ -32,6 +32,7 @@ export default {
tooltipContent: '', tooltipContent: '',
chartOption: { chartOption: {
xAxis: { xAxis: {
type: 'time',
axisLabel: { axisLabel: {
formatter: date => dateFormat(date, dateFormats.defaultDate), formatter: date => dateFormat(date, dateFormats.defaultDate),
}, },
...@@ -67,9 +68,9 @@ export default { ...@@ -67,9 +68,9 @@ export default {
}, },
methods: { methods: {
renderTooltip({ data }) { renderTooltip({ data }) {
const [xValue, yValue] = data; const [, metric, dateTime] = data;
this.tooltipTitle = yValue; this.tooltipTitle = metric;
this.tooltipContent = dateFormat(xValue, dateFormats.defaultDateTime); this.tooltipContent = dateFormat(dateTime, dateFormats.defaultDateTime);
}, },
}, },
}; };
......
...@@ -82,6 +82,11 @@ describe('ProductivityApp component', () => { ...@@ -82,6 +82,11 @@ describe('ProductivityApp component', () => {
describe('with a group being selected', () => { describe('with a group being selected', () => {
beforeEach(() => { beforeEach(() => {
wrapper.vm.$store.dispatch('filters/setDateRange', {
skipFetch: true,
startDate: new Date('2019-09-01'),
endDate: new Date('2019-09-02'),
});
wrapper.vm.$store.dispatch('filters/setGroupNamespace', 'gitlab-org'); wrapper.vm.$store.dispatch('filters/setGroupNamespace', 'gitlab-org');
mock.onGet(wrapper.vm.$store.state.endpoint).replyOnce(200); mock.onGet(wrapper.vm.$store.state.endpoint).replyOnce(200);
}); });
...@@ -277,9 +282,13 @@ describe('ProductivityApp component', () => { ...@@ -277,9 +282,13 @@ describe('ProductivityApp component', () => {
wrapper.vm.$store.dispatch('charts/receiveChartDataSuccess', { wrapper.vm.$store.dispatch('charts/receiveChartDataSuccess', {
chartKey: chartKeys.scatterplot, chartKey: chartKeys.scatterplot,
data: { data: {
1: { metric: 2, merged_at: '2019-07-01T07:06:23.193Z' }, 1: { metric: 2, merged_at: '2019-09-01T07:06:23.193Z' },
2: { metric: 3, merged_at: '2019-07-05T08:27:42.411Z' }, 2: { metric: 3, merged_at: '2019-09-05T08:27:42.411Z' },
}, },
transformedData: [
[{ metric: 2, merged_at: '2019-09-01T07:06:23.193Z' }],
[{ metric: 3, merged_at: '2019-09-05T08:27:42.411Z' }],
],
}); });
}); });
......
...@@ -6,7 +6,13 @@ import * as actions from 'ee/analytics/productivity_analytics/store/modules/char ...@@ -6,7 +6,13 @@ import * as actions from 'ee/analytics/productivity_analytics/store/modules/char
import * as types from 'ee/analytics/productivity_analytics/store/modules/charts/mutation_types'; import * as types from 'ee/analytics/productivity_analytics/store/modules/charts/mutation_types';
import getInitialState from 'ee/analytics/productivity_analytics/store/modules/charts/state'; import getInitialState from 'ee/analytics/productivity_analytics/store/modules/charts/state';
import { chartKeys } from 'ee/analytics/productivity_analytics/constants'; import { chartKeys } from 'ee/analytics/productivity_analytics/constants';
import { mockHistogramData } from '../../../mock_data'; import { mockHistogramData, mockScatterplotData } from '../../../mock_data';
jest.mock('ee/analytics/productivity_analytics/utils', () => ({
transformScatterData: jest
.fn()
.mockImplementation(() => [[{ merged_at: '2019-09-01T00:00:000Z', metric: 10 }]]),
}));
describe('Productivity analytics chart actions', () => { describe('Productivity analytics chart actions', () => {
let mockedContext; let mockedContext;
...@@ -24,6 +30,10 @@ describe('Productivity analytics chart actions', () => { ...@@ -24,6 +30,10 @@ describe('Productivity analytics chart actions', () => {
dispatch() {}, dispatch() {},
rootState: { rootState: {
endpoint: `${TEST_HOST}/analytics/productivity_analytics.json`, endpoint: `${TEST_HOST}/analytics/productivity_analytics.json`,
filters: {
startDate: '2019-09-01',
endDate: '2091-09-05',
},
}, },
getters: { getters: {
getFilterParams: () => globalParams, getFilterParams: () => globalParams,
...@@ -49,33 +59,62 @@ describe('Productivity analytics chart actions', () => { ...@@ -49,33 +59,62 @@ describe('Productivity analytics chart actions', () => {
describe('fetchChartData', () => { describe('fetchChartData', () => {
describe('when chart is enabled', () => { describe('when chart is enabled', () => {
describe('success', () => { describe('success', () => {
beforeEach(() => { describe('histogram charts', () => {
mock.onGet(mockedState.endpoint).replyOnce(200, mockHistogramData); beforeEach(() => {
}); mock.onGet(mockedState.endpoint).replyOnce(200, mockHistogramData);
});
it('calls API with params', () => { it('calls API with params', () => {
jest.spyOn(axios, 'get'); jest.spyOn(axios, 'get');
actions.fetchChartData(mockedContext, chartKey); actions.fetchChartData(mockedContext, chartKey);
expect(axios.get).toHaveBeenCalledWith(mockedState.endpoint, { params: globalParams }); expect(axios.get).toHaveBeenCalledWith(mockedState.endpoint, { params: globalParams });
});
it('dispatches success with received data', done =>
testAction(
actions.fetchChartData,
chartKey,
mockedState,
[],
[
{ type: 'requestChartData', payload: chartKey },
{
type: 'receiveChartDataSuccess',
payload: expect.objectContaining({ chartKey, data: mockHistogramData }),
},
],
done,
));
}); });
it('dispatches success with received data', done => describe('scatterplot chart', () => {
testAction( beforeEach(() => {
actions.fetchChartData, mock.onGet(mockedState.endpoint).replyOnce(200, mockScatterplotData);
chartKey, });
mockedState,
[], it('dispatches success with received data and transformedData', done => {
[ testAction(
{ type: 'requestChartData', payload: chartKey }, actions.fetchChartData,
{ chartKeys.scatterplot,
type: 'receiveChartDataSuccess', mockedState,
payload: expect.objectContaining({ chartKey, data: mockHistogramData }), [],
}, [
], { type: 'requestChartData', payload: chartKeys.scatterplot },
done, {
)); type: 'receiveChartDataSuccess',
payload: {
chartKey: chartKeys.scatterplot,
data: mockScatterplotData,
transformedData: [[{ merged_at: '2019-09-01T00:00:000Z', metric: 10 }]],
},
},
],
done,
);
});
});
}); });
describe('error', () => { describe('error', () => {
...@@ -149,7 +188,7 @@ describe('Productivity analytics chart actions', () => { ...@@ -149,7 +188,7 @@ describe('Productivity analytics chart actions', () => {
[ [
{ {
type: types.RECEIVE_CHART_DATA_SUCCESS, type: types.RECEIVE_CHART_DATA_SUCCESS,
payload: { chartKey, data: mockHistogramData }, payload: { chartKey, data: mockHistogramData, transformedData: null },
}, },
], ],
[], [],
......
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
scatterPlotAddonQueryDays, scatterPlotAddonQueryDays,
} from 'ee/analytics/productivity_analytics/constants'; } from 'ee/analytics/productivity_analytics/constants';
import { getScatterPlotData, getMedianLineData } from 'ee/analytics/productivity_analytics/utils'; import { getScatterPlotData, getMedianLineData } from 'ee/analytics/productivity_analytics/utils';
import { mockHistogramData, mockScatterplotData } from '../../../mock_data'; import { mockHistogramData } from '../../../mock_data';
jest.mock('ee/analytics/productivity_analytics/utils'); jest.mock('ee/analytics/productivity_analytics/utils');
...@@ -17,6 +17,10 @@ describe('Productivity analytics chart getters', () => { ...@@ -17,6 +17,10 @@ describe('Productivity analytics chart getters', () => {
const groupNamespace = 'gitlab-org'; const groupNamespace = 'gitlab-org';
const projectPath = 'gitlab-org/gitlab-test'; const projectPath = 'gitlab-org/gitlab-test';
const transformedData = [
[{ merged_at: '2019-09-01T00:00:000Z', metric: 10 }],
[{ merged_at: '2019-09-02T00:00:000Z', metric: 20 }],
];
beforeEach(() => { beforeEach(() => {
state = createState(); state = createState();
...@@ -54,33 +58,42 @@ describe('Productivity analytics chart getters', () => { ...@@ -54,33 +58,42 @@ describe('Productivity analytics chart getters', () => {
describe('getScatterPlotMainData', () => { describe('getScatterPlotMainData', () => {
it('calls getScatterPlotData with the raw scatterplot data and the date in past', () => { it('calls getScatterPlotData with the raw scatterplot data and the date in past', () => {
state.charts.scatterplot.data = mockScatterplotData; state.charts.scatterplot.transformedData = transformedData;
const rootState = { const rootState = {
filters: { filters: {
startDate: '2019-07-16', startDate: '2019-09-01',
endDate: '2019-09-05',
}, },
}; };
getters.getScatterPlotMainData(state, null, rootState); getters.getScatterPlotMainData(state, null, rootState);
expect(getScatterPlotData).toHaveBeenCalledWith(mockScatterplotData, '2019-07-16'); expect(getScatterPlotData).toHaveBeenCalledWith(
transformedData,
new Date(rootState.filters.startDate),
new Date(rootState.filters.endDate),
);
}); });
}); });
describe('getScatterPlotMedianData', () => { describe('getScatterPlotMedianData', () => {
it('calls getMedianLineData with the raw scatterplot data, the getScatterPlotMainData getter and the an additional days offset', () => { it('calls getMedianLineData with the raw scatterplot data, the getScatterPlotMainData getter and the an additional days offset', () => {
state.charts.scatterplot.data = mockScatterplotData; state.charts.scatterplot.transformedData = transformedData;
const mockGetters = { const rootState = {
getScatterPlotMainData: jest.fn(), filters: {
startDate: '2019-09-01',
endDate: '2019-09-05',
},
}; };
getters.getScatterPlotMedianData(state, mockGetters); getters.getScatterPlotMedianData(state, null, rootState);
expect(getMedianLineData).toHaveBeenCalledWith( expect(getMedianLineData).toHaveBeenCalledWith(
mockScatterplotData, transformedData,
mockGetters.getScatterPlotMainData, new Date(rootState.filters.startDate),
new Date(rootState.filters.endDate),
scatterPlotAddonQueryDays, scatterPlotAddonQueryDays,
); );
}); });
......
...@@ -2,7 +2,7 @@ import * as types from 'ee/analytics/productivity_analytics/store/modules/charts ...@@ -2,7 +2,7 @@ import * as types from 'ee/analytics/productivity_analytics/store/modules/charts
import mutations from 'ee/analytics/productivity_analytics/store/modules/charts/mutations'; import mutations from 'ee/analytics/productivity_analytics/store/modules/charts/mutations';
import getInitialState from 'ee/analytics/productivity_analytics/store/modules/charts/state'; import getInitialState from 'ee/analytics/productivity_analytics/store/modules/charts/state';
import { chartKeys } from 'ee/analytics/productivity_analytics/constants'; import { chartKeys } from 'ee/analytics/productivity_analytics/constants';
import { mockHistogramData } from '../../../mock_data'; import { mockHistogramData, mockScatterplotData } from '../../../mock_data';
describe('Productivity analytics chart mutations', () => { describe('Productivity analytics chart mutations', () => {
let state; let state;
...@@ -37,23 +37,54 @@ describe('Productivity analytics chart mutations', () => { ...@@ -37,23 +37,54 @@ describe('Productivity analytics chart mutations', () => {
expect(state.charts[chartKey].errorCode).toBe(null); expect(state.charts[chartKey].errorCode).toBe(null);
expect(state.charts[chartKey].data).toEqual(mockHistogramData); expect(state.charts[chartKey].data).toEqual(mockHistogramData);
}); });
it('updates the transformedData when chartKey=scatterplot', () => {
const transformedData = [
[
{
metric: 139,
merged_at: '2019-08-18T22:00:00.000Z',
},
],
[
{
metric: 138,
merged_at: '2019-08-17T22:00:00.000Z',
},
],
];
mutations[types.RECEIVE_CHART_DATA_SUCCESS](state, {
chartKey: chartKeys.scatterplot,
data: mockScatterplotData,
transformedData,
});
expect(state.charts[chartKey].isLoading).toBe(false);
expect(state.charts[chartKey].errorCode).toBe(null);
expect(state.charts[chartKey].data).toEqual(mockScatterplotData);
expect(state.charts[chartKey].transformedData).toEqual(transformedData);
});
}); });
describe(types.RECEIVE_CHART_DATA_ERROR, () => { describe(types.RECEIVE_CHART_DATA_ERROR, () => {
const status = 500; const status = 500;
beforeEach(() => {
mutations[types.RECEIVE_CHART_DATA_ERROR](state, { chartKey, status });
});
it('sets errorCode to 500', () => { it('sets errorCode to 500', () => {
mutations[types.RECEIVE_CHART_DATA_ERROR](state, { chartKey, status });
expect(state.charts[chartKey].isLoading).toBe(false); expect(state.charts[chartKey].isLoading).toBe(false);
expect(state.charts[chartKey].errorCode).toBe(status); expect(state.charts[chartKey].errorCode).toBe(status);
}); });
it('clears data', () => { it('clears data', () => {
mutations[types.RECEIVE_CHART_DATA_ERROR](state, { chartKey, status });
expect(state.charts[chartKey].isLoading).toBe(false); expect(state.charts[chartKey].isLoading).toBe(false);
expect(state.charts[chartKey].data).toEqual({}); expect(state.charts[chartKey].data).toEqual({});
}); });
it('clears transformedData when chartKey=scatterplot', () => {
mutations[types.RECEIVE_CHART_DATA_ERROR](state, { chartKey: chartKeys.scatterplot, status });
expect(state.charts[chartKey].transformedData).toEqual([]);
});
}); });
describe(types.SET_METRIC_TYPE, () => { describe(types.SET_METRIC_TYPE, () => {
......
...@@ -4,13 +4,9 @@ import { ...@@ -4,13 +4,9 @@ import {
initDateArray, initDateArray,
transformScatterData, transformScatterData,
getScatterPlotData, getScatterPlotData,
getScatterPlotDataNew,
getMedianLineData, getMedianLineData,
getMedianLineDataNew,
} from 'ee/analytics/productivity_analytics/utils'; } from 'ee/analytics/productivity_analytics/utils';
import { mockScatterplotData } from './mock_data';
describe('Productivity Analytics utils', () => { describe('Productivity Analytics utils', () => {
const namespacePath = 'gitlab-org'; const namespacePath = 'gitlab-org';
const projectWithNamespace = 'gitlab-org/gitlab-test'; const projectWithNamespace = 'gitlab-org/gitlab-test';
...@@ -72,26 +68,6 @@ describe('Productivity Analytics utils', () => { ...@@ -72,26 +68,6 @@ describe('Productivity Analytics utils', () => {
}); });
describe('getScatterPlotData', () => { describe('getScatterPlotData', () => {
it('filters out data before given "dateInPast", transforms the data and sorts by date ascending', () => {
const dateInPast = new Date(2019, 7, 9); // '2019-08-09T22:00:00.000Z';
const result = getScatterPlotData(mockScatterplotData, dateInPast);
const expected = [
['2019-08-09T22:00:00.000Z', 44],
['2019-08-10T22:00:00.000Z', 46],
['2019-08-11T22:00:00.000Z', 62],
['2019-08-12T22:00:00.000Z', 60],
['2019-08-13T22:00:00.000Z', 43],
['2019-08-14T22:00:00.000Z', 46],
['2019-08-15T22:00:00.000Z', 56],
['2019-08-16T22:00:00.000Z', 24],
['2019-08-17T22:00:00.000Z', 138],
['2019-08-18T22:00:00.000Z', 139],
];
expect(result).toEqual(expected);
});
});
describe('getScatterPlotDataNew', () => {
it('returns a subset of data for the given start and end date and flattens the data', () => { it('returns a subset of data for the given start and end date and flattens the data', () => {
const startDate = new Date('2019-08-02'); const startDate = new Date('2019-08-02');
const endDate = new Date('2019-08-04'); const endDate = new Date('2019-08-04');
...@@ -104,7 +80,7 @@ describe('Productivity Analytics utils', () => { ...@@ -104,7 +80,7 @@ describe('Productivity Analytics utils', () => {
{ merged_at: '2019-08-04T16:00:00.000Z', metric: 60 }, { merged_at: '2019-08-04T16:00:00.000Z', metric: 60 },
], ],
]; ];
const result = getScatterPlotDataNew(data, startDate, endDate); const result = getScatterPlotData(data, startDate, endDate);
const expected = [ const expected = [
['2019-08-02', 30, '2019-08-02T13:00:00.000Z'], ['2019-08-02', 30, '2019-08-02T13:00:00.000Z'],
['2019-08-03', 40, '2019-08-03T14:00:00.000Z'], ['2019-08-03', 40, '2019-08-03T14:00:00.000Z'],
...@@ -116,25 +92,6 @@ describe('Productivity Analytics utils', () => { ...@@ -116,25 +92,6 @@ describe('Productivity Analytics utils', () => {
}); });
describe('getMedianLineData', () => { describe('getMedianLineData', () => {
const daysOffset = 10;
it(`computes the median for every item in the scatterData array for the past ${daysOffset} days`, () => {
const scatterData = [
['2019-08-16T22:00:00.000Z', 24],
['2019-08-17T22:00:00.000Z', 138],
['2019-08-18T22:00:00.000Z', 139],
];
const result = getMedianLineData(mockScatterplotData, scatterData, daysOffset);
const expected = [
['2019-08-16T22:00:00.000Z', 51],
['2019-08-17T22:00:00.000Z', 51],
['2019-08-18T22:00:00.000Z', 56],
];
expect(result).toEqual(expected);
});
});
describe('getMedianLineDataNew', () => {
const daysOffset = 2; const daysOffset = 2;
it(`computes the median for every date in the data array based on the past ${daysOffset} days`, () => { it(`computes the median for every date in the data array based on the past ${daysOffset} days`, () => {
...@@ -151,7 +108,7 @@ describe('Productivity Analytics utils', () => { ...@@ -151,7 +108,7 @@ describe('Productivity Analytics utils', () => {
[{ merged_at: '2019-08-05T17:00:00.000Z', metric: 70 }], [{ merged_at: '2019-08-05T17:00:00.000Z', metric: 70 }],
[{ merged_at: '2019-08-06T18:00:00.000Z', metric: 80 }], [{ merged_at: '2019-08-06T18:00:00.000Z', metric: 80 }],
]; ];
const result = getMedianLineDataNew(data, startDate, endDate, daysOffset); const result = getMedianLineData(data, startDate, endDate, daysOffset);
const expected = [['2019-08-04', 45], ['2019-08-05', 55], ['2019-08-06', 65]]; const expected = [['2019-08-04', 45], ['2019-08-05', 55], ['2019-08-06', 65]];
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });
......
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