Commit fbc8a0c3 authored by Nathan Friend's avatar Nathan Friend Committed by Jose Ivan Vargas

Update deployment frequency charts to use UTC

This commit updates the deployment frequency charts to always use UTC
dates when computing offsets and when communicating with the API.
parent 628241ef
......@@ -38,10 +38,10 @@ export default {
},
async mounted() {
const results = await Promise.allSettled(
allChartDefinitions.map(async ({ id, requestParams, startDate }) => {
allChartDefinitions.map(async ({ id, requestParams, startDate, endDate }) => {
const { data: apiData } = await Api.deploymentFrequencies(this.projectPath, requestParams);
this.chartData[id] = apiDataToChartSeries(apiData, startDate);
this.chartData[id] = apiDataToChartSeries(apiData, startDate, endDate);
}),
);
......
import dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import { nDaysBefore, nMonthsBefore } from '~/lib/utils/datetime_utility';
import { nDaysBefore, nMonthsBefore, getStartOfDay, dayAfter } from '~/lib/utils/datetime_utility';
import { LAST_WEEK, LAST_MONTH, LAST_90_DAYS } from './constants';
// Compute all relative dates based on the _beginning_ of today
const startOfToday = new Date(new Date().setHours(0, 0, 0, 0));
const lastWeek = nDaysBefore(startOfToday, 7);
const lastMonth = nMonthsBefore(startOfToday, 1);
const last90Days = nDaysBefore(startOfToday, 90);
// Compute all relative dates based on the _beginning_ of today.
// We use this date as the end date for the charts. This causes
// the current date to be the last day included in the graph.
const startOfToday = getStartOfDay(new Date(), { utc: true });
// We use this date as the "to" parameter for the API. This allows
// us to get deployment information about the current day.
const startOfTomorrow = dayAfter(startOfToday, { utc: true });
const lastWeek = nDaysBefore(startOfTomorrow, 7, { utc: true });
const lastMonth = nMonthsBefore(startOfTomorrow, 1, { utc: true });
const last90Days = nDaysBefore(startOfTomorrow, 90, { utc: true });
const apiDateFormatString = 'isoDateTime';
const titleDateFormatString = 'mmm d';
const sharedRequestParams = {
......@@ -28,14 +35,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastWeek, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
startDate: dateFormat(lastWeek, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString, true),
},
),
startDate: lastWeek,
endDate: startOfTomorrow,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastWeek, apiDateFormatString),
from: dateFormat(lastWeek, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
},
},
{
......@@ -45,14 +54,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastMonth, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
startDate: dateFormat(lastMonth, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString, true),
},
),
startDate: lastMonth,
endDate: startOfTomorrow,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastMonth, apiDateFormatString),
from: dateFormat(lastMonth, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
},
},
{
......@@ -62,14 +73,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(last90Days, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
startDate: dateFormat(last90Days, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString, true),
},
),
startDate: last90Days,
endDate: startOfTomorrow,
requestParams: {
...sharedRequestParams,
from: dateFormat(last90Days, apiDateFormatString),
from: dateFormat(last90Days, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
},
},
];
......
import dateFormat from 'dateformat';
import { getDatesInRange } from '~/lib/utils/datetime_utility';
import { getDatesInRange, nDaysBefore, getStartOfDay } from '~/lib/utils/datetime_utility';
import { CHART_TITLE } from './constants';
/**
......@@ -8,20 +8,32 @@ import { CHART_TITLE } from './constants';
* into series data consumable by
* [GlAreaChart](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/charts-area-chart--default)
*
* @param apiData The raw JSON data from the API request
* @param startDate The first day that should be rendered on the graph
* @param {Array} apiData The raw JSON data from the API request
* @param {Date} startDate The first day (inclusive) of the graph's date range
* @param {Date} endDate The last day (exclusive) of the graph's date range
*/
export const apiDataToChartSeries = (apiData, startDate) => {
// Get a list of dates (formatted identically to the dates in the API response),
// one date per day in the graph's date range
const dates = getDatesInRange(startDate, new Date(), (date) => dateFormat(date, 'yyyy-mm-dd'));
export const apiDataToChartSeries = (apiData, startDate, endDate) => {
// Get a list of dates, one date per day in the graph's date range
const beginningOfStartDate = getStartOfDay(startDate, { utc: true });
const beginningOfEndDate = nDaysBefore(getStartOfDay(endDate, { utc: true }), 1, { utc: true });
const dates = getDatesInRange(beginningOfStartDate, beginningOfEndDate).map((d) =>
getStartOfDay(d, { utc: true }),
);
// Generate a map of API timestamps to its associated value.
// The timestamps are explicitly set to the _beginning_ of the day (in UTC)
// so that we can confidently compare dates by value below.
const timestampToApiValue = apiData.reduce((acc, curr) => {
const apiTimestamp = getStartOfDay(new Date(curr.from), { utc: true }).getTime();
acc[apiTimestamp] = curr.value;
return acc;
}, {});
// Fill in the API data (the API data doesn't included data points for
// days with 0 deployments) and transform it for use in the graph
const data = dates.map((date) => {
const value = apiData.find((dataPoint) => dataPoint.from === date)?.value || 0;
const formattedDate = dateFormat(new Date(date), 'mmm d');
return [formattedDate, value];
const formattedDate = dateFormat(date, 'mmm d', true);
return [formattedDate, timestampToApiValue[date.getTime()] || 0];
});
return [
......
---
title: Update Deployment Frequency graphs to always use UTC dates
merge_request: 52919
author:
type: changed
......@@ -44,7 +44,13 @@ RSpec.describe 'Project Analytics (JavaScript fixtures)' do
clean_frontend_fixtures('api/project_analytics/')
end
let(:shared_params) { { environment: environment.name, interval: 'daily' } }
let(:shared_params) do
{
environment: environment.name,
interval: 'daily',
to: Date.tomorrow.beginning_of_day
}
end
def make_request(additional_query_params:)
params = shared_params.merge(additional_query_params)
......
......@@ -2,10 +2,6 @@
exports[`ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue when there are no network errors converts the data from the API into data usable by the chart component 1`] = `
Array [
Array [
"Jun 26",
0,
],
Array [
"Jun 27",
0,
......@@ -34,19 +30,11 @@ Array [
"Jul 3",
1,
],
Array [
"Jul 4",
0,
],
]
`;
exports[`ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue when there are no network errors converts the data from the API into data usable by the chart component 2`] = `
Array [
Array [
"Jun 3",
0,
],
Array [
"Jun 4",
0,
......@@ -167,19 +155,11 @@ Array [
"Jul 3",
1,
],
Array [
"Jul 4",
0,
],
]
`;
exports[`ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue when there are no network errors converts the data from the API into data usable by the chart component 3`] = `
Array [
Array [
"Apr 4",
0,
],
Array [
"Apr 5",
0,
......@@ -540,9 +520,5 @@ Array [
"Jul 3",
1,
],
Array [
"Jul 4",
0,
],
]
`;
......@@ -58,6 +58,7 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
environment: 'production',
interval: 'daily',
per_page: 100,
to: '2015-07-04T00:00:00+0000',
from,
},
})
......@@ -77,9 +78,18 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
beforeEach(async () => {
mock = new MockAdapter(axios);
setUpMockDeploymentFrequencies({ from: '2015-06-26T00:00:00+0000', data: lastWeekData });
setUpMockDeploymentFrequencies({ from: '2015-06-03T00:00:00+0000', data: lastMonthData });
setUpMockDeploymentFrequencies({ from: '2015-04-04T00:00:00+0000', data: last90DaysData });
setUpMockDeploymentFrequencies({
from: '2015-06-27T00:00:00+0000',
data: lastWeekData,
});
setUpMockDeploymentFrequencies({
from: '2015-06-04T00:00:00+0000',
data: lastMonthData,
});
setUpMockDeploymentFrequencies({
from: '2015-04-05T00:00:00+0000',
data: last90DaysData,
});
createComponent();
......
import { useFakeDate } from 'helpers/fake_date';
import { apiDataToChartSeries } from 'ee/projects/pipelines/charts/components/util';
describe('ee/projects/pipelines/charts/components/util.js', () => {
useFakeDate(2015, 6, 3, 10);
describe('apiDataToChartSeries', () => {
it('transforms the data from the API into data the chart component can use', () => {
const apiData = [
{ value: 5, from: '2015-06-28', to: '2015-06-29' },
{ value: 1, from: '2015-06-29', to: '2015-06-30' },
// This is the date format we expect from the API
{ value: 5, from: '2015-06-28T00:00:00.000Z', to: '2015-06-29T00:00:00.000Z' },
// But we should support _any_ date format
{ value: 1, from: '2015-06-28T20:00:00.000-0400', to: '2015-06-19T20:00:00.000-0400' },
{ value: 8, from: '2015-07-01', to: '2015-07-02' },
];
const startDate = new Date(2015, 5, 26, 10);
const endDate = new Date(2015, 6, 4, 10);
const expected = [
{
......@@ -30,7 +31,7 @@ describe('ee/projects/pipelines/charts/components/util.js', () => {
},
];
expect(apiDataToChartSeries(apiData, startDate)).toEqual(expected);
expect(apiDataToChartSeries(apiData, startDate, endDate)).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