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 { ...@@ -38,10 +38,10 @@ export default {
}, },
async mounted() { async mounted() {
const results = await Promise.allSettled( 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); 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 dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper'; 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'; import { LAST_WEEK, LAST_MONTH, LAST_90_DAYS } from './constants';
// Compute all relative dates based on the _beginning_ of today // Compute all relative dates based on the _beginning_ of today.
const startOfToday = new Date(new Date().setHours(0, 0, 0, 0)); // We use this date as the end date for the charts. This causes
const lastWeek = nDaysBefore(startOfToday, 7); // the current date to be the last day included in the graph.
const lastMonth = nMonthsBefore(startOfToday, 1); const startOfToday = getStartOfDay(new Date(), { utc: true });
const last90Days = nDaysBefore(startOfToday, 90);
// 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 apiDateFormatString = 'isoDateTime';
const titleDateFormatString = 'mmm d'; const titleDateFormatString = 'mmm d';
const sharedRequestParams = { const sharedRequestParams = {
...@@ -28,14 +35,16 @@ export const allChartDefinitions = [ ...@@ -28,14 +35,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})', 'DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})',
), ),
{ {
startDate: dateFormat(lastWeek, titleDateFormatString), startDate: dateFormat(lastWeek, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString), endDate: dateFormat(startOfToday, titleDateFormatString, true),
}, },
), ),
startDate: lastWeek, startDate: lastWeek,
endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(lastWeek, apiDateFormatString), from: dateFormat(lastWeek, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
{ {
...@@ -45,14 +54,16 @@ export const allChartDefinitions = [ ...@@ -45,14 +54,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})', 'DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})',
), ),
{ {
startDate: dateFormat(lastMonth, titleDateFormatString), startDate: dateFormat(lastMonth, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString), endDate: dateFormat(startOfToday, titleDateFormatString, true),
}, },
), ),
startDate: lastMonth, startDate: lastMonth,
endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(lastMonth, apiDateFormatString), from: dateFormat(lastMonth, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
{ {
...@@ -62,14 +73,16 @@ export const allChartDefinitions = [ ...@@ -62,14 +73,16 @@ export const allChartDefinitions = [
'DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})', 'DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})',
), ),
{ {
startDate: dateFormat(last90Days, titleDateFormatString), startDate: dateFormat(last90Days, titleDateFormatString, true),
endDate: dateFormat(startOfToday, titleDateFormatString), endDate: dateFormat(startOfToday, titleDateFormatString, true),
}, },
), ),
startDate: last90Days, startDate: last90Days,
endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(last90Days, apiDateFormatString), from: dateFormat(last90Days, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
]; ];
......
import dateFormat from 'dateformat'; 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'; import { CHART_TITLE } from './constants';
/** /**
...@@ -8,20 +8,32 @@ import { CHART_TITLE } from './constants'; ...@@ -8,20 +8,32 @@ import { CHART_TITLE } from './constants';
* into series data consumable by * into series data consumable by
* [GlAreaChart](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/charts-area-chart--default) * [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 {Array} apiData The raw JSON data from the API request
* @param startDate The first day that should be rendered on the graph * @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) => { export const apiDataToChartSeries = (apiData, startDate, endDate) => {
// Get a list of dates (formatted identically to the dates in the API response), // Get a list of dates, one date per day in the graph's date range
// one date per day in the graph's date range const beginningOfStartDate = getStartOfDay(startDate, { utc: true });
const dates = getDatesInRange(startDate, new Date(), (date) => dateFormat(date, 'yyyy-mm-dd')); 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 // 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 // days with 0 deployments) and transform it for use in the graph
const data = dates.map((date) => { const data = dates.map((date) => {
const value = apiData.find((dataPoint) => dataPoint.from === date)?.value || 0; const formattedDate = dateFormat(date, 'mmm d', true);
const formattedDate = dateFormat(new Date(date), 'mmm d'); return [formattedDate, timestampToApiValue[date.getTime()] || 0];
return [formattedDate, value];
}); });
return [ 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 ...@@ -44,7 +44,13 @@ RSpec.describe 'Project Analytics (JavaScript fixtures)' do
clean_frontend_fixtures('api/project_analytics/') clean_frontend_fixtures('api/project_analytics/')
end 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:) def make_request(additional_query_params:)
params = shared_params.merge(additional_query_params) params = shared_params.merge(additional_query_params)
......
...@@ -2,10 +2,6 @@ ...@@ -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`] = ` 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 [
Array [
"Jun 26",
0,
],
Array [ Array [
"Jun 27", "Jun 27",
0, 0,
...@@ -34,19 +30,11 @@ Array [ ...@@ -34,19 +30,11 @@ Array [
"Jul 3", "Jul 3",
1, 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`] = ` 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 [
Array [
"Jun 3",
0,
],
Array [ Array [
"Jun 4", "Jun 4",
0, 0,
...@@ -167,19 +155,11 @@ Array [ ...@@ -167,19 +155,11 @@ Array [
"Jul 3", "Jul 3",
1, 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`] = ` 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 [
Array [
"Apr 4",
0,
],
Array [ Array [
"Apr 5", "Apr 5",
0, 0,
...@@ -540,9 +520,5 @@ Array [ ...@@ -540,9 +520,5 @@ Array [
"Jul 3", "Jul 3",
1, 1,
], ],
Array [
"Jul 4",
0,
],
] ]
`; `;
...@@ -58,6 +58,7 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency ...@@ -58,6 +58,7 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
environment: 'production', environment: 'production',
interval: 'daily', interval: 'daily',
per_page: 100, per_page: 100,
to: '2015-07-04T00:00:00+0000',
from, from,
}, },
}) })
...@@ -77,9 +78,18 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency ...@@ -77,9 +78,18 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
beforeEach(async () => { beforeEach(async () => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
setUpMockDeploymentFrequencies({ from: '2015-06-26T00:00:00+0000', data: lastWeekData }); setUpMockDeploymentFrequencies({
setUpMockDeploymentFrequencies({ from: '2015-06-03T00:00:00+0000', data: lastMonthData }); from: '2015-06-27T00:00:00+0000',
setUpMockDeploymentFrequencies({ from: '2015-04-04T00:00:00+0000', data: last90DaysData }); data: lastWeekData,
});
setUpMockDeploymentFrequencies({
from: '2015-06-04T00:00:00+0000',
data: lastMonthData,
});
setUpMockDeploymentFrequencies({
from: '2015-04-05T00:00:00+0000',
data: last90DaysData,
});
createComponent(); createComponent();
......
import { useFakeDate } from 'helpers/fake_date';
import { apiDataToChartSeries } from 'ee/projects/pipelines/charts/components/util'; import { apiDataToChartSeries } from 'ee/projects/pipelines/charts/components/util';
describe('ee/projects/pipelines/charts/components/util.js', () => { describe('ee/projects/pipelines/charts/components/util.js', () => {
useFakeDate(2015, 6, 3, 10);
describe('apiDataToChartSeries', () => { describe('apiDataToChartSeries', () => {
it('transforms the data from the API into data the chart component can use', () => { it('transforms the data from the API into data the chart component can use', () => {
const apiData = [ const apiData = [
{ value: 5, from: '2015-06-28', to: '2015-06-29' }, // This is the date format we expect from the API
{ value: 1, from: '2015-06-29', to: '2015-06-30' }, { 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' }, { value: 8, from: '2015-07-01', to: '2015-07-02' },
]; ];
const startDate = new Date(2015, 5, 26, 10); const startDate = new Date(2015, 5, 26, 10);
const endDate = new Date(2015, 6, 4, 10);
const expected = [ const expected = [
{ {
...@@ -30,7 +31,7 @@ describe('ee/projects/pipelines/charts/components/util.js', () => { ...@@ -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