Commit 0f05882e authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'nfriend-update-deployment-frequency-frontend-date-format' into 'master'

Update deployment frequency graphs to use UTC dates

See merge request gitlab-org/gitlab!52919
parents 402059c7 fbc8a0c3
...@@ -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