Commit 39655e41 authored by Michael Lunøe's avatar Michael Lunøe Committed by Brandon Labuschagne

Feat(Instance Statistics): add getAverageByMonth

Add util to convert issues and merge request data
to average by month
parent 1ed54ff5
import { masks } from 'dateformat';
import { formatDate } from '~/lib/utils/datetime_utility';
const { isoDate } = masks;
/**
* Takes an array of items and returns one item per month with the average of the `count`s from that month
* @param {Array} items
* @param {Number} items[index].count value to be averaged
* @param {String} items[index].recordedAt item dateTime time stamp to be collected into a month
* @param {Object} options
* @param {Object} options.shouldRound an option to specify whether the retuned averages should be rounded
* @return {Array} items collected into [month, average],
* where month is a dateTime string representing the first of the given month
* and average is the average of the count
*/
export function getAverageByMonth(items = [], options = {}) {
const { shouldRound = false } = options;
const itemsMap = items.reduce((memo, item) => {
const { count, recordedAt } = item;
const date = new Date(recordedAt);
const month = formatDate(new Date(date.getFullYear(), date.getMonth(), 1), isoDate);
if (memo[month]) {
const { sum, recordCount } = memo[month];
return { ...memo, [month]: { sum: sum + count, recordCount: recordCount + 1 } };
}
return { ...memo, [month]: { sum: count, recordCount: 1 } };
}, {});
return Object.keys(itemsMap).map(month => {
const { sum, recordCount } = itemsMap[month];
const avg = sum / recordCount;
if (shouldRound) {
return [month, Math.round(avg)];
}
return [month, avg];
});
}
import { masks } from 'dateformat';
const { isoDate, mediumDate } = masks;
export const dateFormats = {
isoDate: 'yyyy-mm-dd',
defaultDate: 'mmm d, yyyy',
isoDate,
defaultDate: mediumDate,
defaultDateTime: 'mmm d, yyyy h:MMtt',
};
......
import { shallowMount } from '@vue/test-utils';
import InstanceCounts from '~/analytics/instance_statistics/components/instance_counts.vue';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import countsMockData from '../mock_data';
import { mockInstanceCounts } from '../mock_data';
describe('InstanceCounts', () => {
let wrapper;
......@@ -44,11 +44,11 @@ describe('InstanceCounts', () => {
describe('with data', () => {
beforeEach(() => {
createComponent({ data: { counts: countsMockData } });
createComponent({ data: { counts: mockInstanceCounts } });
});
it('passes the counts data to the metric card', () => {
expect(findMetricCard().props('metrics')).toEqual(countsMockData);
expect(findMetricCard().props('metrics')).toEqual(mockInstanceCounts);
});
});
});
export default [
export const mockInstanceCounts = [
{ key: 'projects', value: 10, label: 'Projects' },
{ key: 'groups', value: 20, label: 'Group' },
];
export const mockCountsData1 = [
{ recordedAt: '2020-07-23', count: 52 },
{ recordedAt: '2020-07-22', count: 40 },
{ recordedAt: '2020-07-21', count: 31 },
{ recordedAt: '2020-06-14', count: 23 },
{ recordedAt: '2020-06-12', count: 20 },
];
export const countsMonthlyChartData1 = [
['2020-07-01', 41], // average of 2020-07-x items
['2020-06-01', 21.5], // average of 2020-06-x items
];
export const mockCountsData2 = [
{ recordedAt: '2020-07-28', count: 10 },
{ recordedAt: '2020-07-27', count: 9 },
{ recordedAt: '2020-06-26', count: 14 },
{ recordedAt: '2020-06-25', count: 23 },
{ recordedAt: '2020-06-24', count: 25 },
];
export const countsMonthlyChartData2 = [
['2020-07-01', 9.5], // average of 2020-07-x items
['2020-06-01', 20.666666666666668], // average of 2020-06-x items
];
import { getAverageByMonth } from '~/analytics/instance_statistics/utils';
import {
mockCountsData1,
mockCountsData2,
countsMonthlyChartData1,
countsMonthlyChartData2,
} from './mock_data';
describe('getAverageByMonth', () => {
it('collects data into average by months', () => {
expect(getAverageByMonth(mockCountsData1)).toStrictEqual(countsMonthlyChartData1);
expect(getAverageByMonth(mockCountsData2)).toStrictEqual(countsMonthlyChartData2);
});
it('it transforms a data point to the first of the month', () => {
const item = mockCountsData1[0];
const firstOfTheMonth = item.recordedAt.replace(/-[0-9]{2}$/, '-01');
expect(getAverageByMonth([item])).toStrictEqual([[firstOfTheMonth, item.count]]);
});
it('it uses sane defaults', () => {
expect(getAverageByMonth()).toStrictEqual([]);
});
it('it errors when passing null', () => {
expect(() => {
getAverageByMonth(null);
}).toThrow();
});
describe('when shouldRound = true', () => {
const options = { shouldRound: true };
it('rounds the averages', () => {
const roundedData1 = countsMonthlyChartData1.map(([date, avg]) => [date, Math.round(avg)]);
const roundedData2 = countsMonthlyChartData2.map(([date, avg]) => [date, Math.round(avg)]);
expect(getAverageByMonth(mockCountsData1, options)).toStrictEqual(roundedData1);
expect(getAverageByMonth(mockCountsData2, options)).toStrictEqual(roundedData2);
});
});
});
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