Commit 397d1ec6 authored by Nathan Friend's avatar Nathan Friend Committed by Nicolò Maria Mezzopera

Extract logic from Deployment Frequency component

This commit restructures the Deployment Frequency component to simplify
the component by removing some of its responsibilities.
parent 9ab299f4
import { s__ } from '~/locale';
/* eslint-disable @gitlab/require-i18n-strings */
export const LAST_WEEK = 'LAST_WEEK';
export const LAST_MONTH = 'LAST_MONTH';
export const LAST_90_DAYS = 'LAST_90_DAYS';
/* eslint-enable @gitlab/require-i18n-strings */
export const CHART_TITLE = s__('DeploymentFrequencyCharts|Deployments');
<script> <script>
import dateFormat from 'dateformat';
import Api from 'ee/api'; import Api from 'ee/api';
import { s__, sprintf } from '~/locale'; import { s__ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import { nDaysBefore, nMonthsBefore, getDatesInRange } from '~/lib/utils/datetime_utility';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue'; import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import { allChartDefinitions, areaChartOptions } from './static_data';
import { apiDataToChartSeries } from './util';
import { LAST_WEEK, LAST_MONTH, LAST_90_DAYS } from './constants';
export default { export default {
name: 'DeploymentFrequencyCharts', name: 'DeploymentFrequencyCharts',
...@@ -19,97 +20,20 @@ export default { ...@@ -19,97 +20,20 @@ export default {
}, },
}, },
data() { data() {
// Compute all relative dates based on the _beginning_ of today
const startOfToday = new Date(new Date().setHours(0, 0, 0, 0));
const lastWeek = new Date(nDaysBefore(startOfToday, 7));
const lastMonth = new Date(nMonthsBefore(startOfToday, 1));
const last90Days = new Date(nDaysBefore(startOfToday, 90));
const apiDateFormatString = 'isoDateTime';
const titleDateFormatString = 'mmm d';
const sharedRequestParams = {
environment: 'production',
interval: 'daily',
// We will never have more than 91 records (1 record per day), so we
// don't have to worry about making multiple requests to get all the results
per_page: 100,
};
return { return {
charts: [ chartData: {
{ [LAST_WEEK]: [],
title: sprintf( [LAST_MONTH]: [],
s__( [LAST_90_DAYS]: [],
'DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastWeek, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: lastWeek,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastWeek, apiDateFormatString),
},
isLoading: true,
data: [],
},
{
title: sprintf(
s__(
'DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastMonth, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: lastMonth,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastMonth, apiDateFormatString),
},
isLoading: true,
data: [],
},
{
title: sprintf(
s__(
'DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(last90Days, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: last90Days,
requestParams: {
...sharedRequestParams,
from: dateFormat(last90Days, apiDateFormatString),
},
isLoading: true,
data: [],
}, },
],
}; };
}, },
async mounted() { async mounted() {
const results = await Promise.allSettled( const results = await Promise.allSettled(
this.charts.map(async (c) => { allChartDefinitions.map(async ({ id, requestParams, startDate }) => {
const chart = c; const { data: apiData } = await Api.deploymentFrequencies(this.projectPath, requestParams);
chart.isLoading = true;
try {
const { data: apiData } = await Api.deploymentFrequencies(
this.projectPath,
chart.requestParams,
);
chart.data = this.apiDataToChartSeries(apiData, chart.startDate); this.chartData[id] = apiDataToChartSeries(apiData, startDate);
} finally {
chart.isLoading = false;
}
}), }),
); );
...@@ -130,59 +54,17 @@ export default { ...@@ -130,59 +54,17 @@ export default {
); );
} }
}, },
methods: { allChartDefinitions,
/** areaChartOptions,
* Converts the raw data fetched from the
* [Deployment Frequency API](https://docs.gitlab.com/ee/api/project_analytics.html#list-project-deployment-frequencies)
* 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
*/
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'),
);
// 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];
});
return [
{
name: s__('DeploymentFrequencyCharts|Deployments'),
data,
},
];
},
},
areaChartOptions: {
xAxis: {
name: s__('DeploymentFrequencyCharts|Date'),
type: 'category',
},
yAxis: {
name: s__('DeploymentFrequencyCharts|Deployments'),
type: 'value',
minInterval: 1,
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<h4 class="gl-my-4">{{ s__('DeploymentFrequencyCharts|Deployments charts') }}</h4> <h4 class="gl-my-4">{{ s__('DeploymentFrequencyCharts|Deployments charts') }}</h4>
<ci-cd-analytics-area-chart <ci-cd-analytics-area-chart
v-for="(chart, index) in charts" v-for="chart of $options.allChartDefinitions"
:key="index" :key="chart.id"
:chart-data="chart.data" :chart-data="chartData[chart.id]"
:area-chart-options="$options.areaChartOptions" :area-chart-options="$options.areaChartOptions"
> >
{{ chart.title }} {{ chart.title }}
......
import dateFormat from 'dateformat';
import { s__, sprintf } from '~/locale';
import { nDaysBefore, nMonthsBefore } 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 = new Date(nDaysBefore(startOfToday, 7));
const lastMonth = new Date(nMonthsBefore(startOfToday, 1));
const last90Days = new Date(nDaysBefore(startOfToday, 90));
const apiDateFormatString = 'isoDateTime';
const titleDateFormatString = 'mmm d';
const sharedRequestParams = {
environment: 'production',
interval: 'daily',
// We will never have more than 91 records (1 record per day), so we
// don't have to worry about making multiple requests to get all the results
per_page: 100,
};
export const allChartDefinitions = [
{
id: LAST_WEEK,
title: sprintf(
s__(
'DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastWeek, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: lastWeek,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastWeek, apiDateFormatString),
},
},
{
id: LAST_MONTH,
title: sprintf(
s__(
'DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(lastMonth, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: lastMonth,
requestParams: {
...sharedRequestParams,
from: dateFormat(lastMonth, apiDateFormatString),
},
},
{
id: LAST_90_DAYS,
title: sprintf(
s__(
'DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})',
),
{
startDate: dateFormat(last90Days, titleDateFormatString),
endDate: dateFormat(startOfToday, titleDateFormatString),
},
),
startDate: last90Days,
requestParams: {
...sharedRequestParams,
from: dateFormat(last90Days, apiDateFormatString),
},
},
];
export const areaChartOptions = {
xAxis: {
name: s__('DeploymentFrequencyCharts|Date'),
type: 'category',
},
yAxis: {
name: s__('DeploymentFrequencyCharts|Deployments'),
type: 'value',
minInterval: 1,
},
};
import dateFormat from 'dateformat';
import { getDatesInRange } from '~/lib/utils/datetime_utility';
import { CHART_TITLE } from './constants';
/**
* Converts the raw data fetched from the
* [Deployment Frequency API](https://docs.gitlab.com/ee/api/project_analytics.html#list-project-deployment-frequencies)
* 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
*/
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'));
// 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];
});
return [
{
name: CHART_TITLE,
data,
},
];
};
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import DeploymentFrequencyCharts from 'ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue'; import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
...@@ -25,6 +24,18 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency ...@@ -25,6 +24,18 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
// Set the current Date to the same value that is used when generating the fixtures // Set the current Date to the same value that is used when generating the fixtures
useFakeDate(2015, 6, 3, 10); useFakeDate(2015, 6, 3, 10);
let DeploymentFrequencyCharts;
// Import the component _after_ the date has been set using `useFakeDate`, so
// that any calls to `new Date()` during module initialization use the fake date
beforeAll(async () => {
DeploymentFrequencyCharts = (
await import(
'ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'
)
).default;
});
let wrapper; let wrapper;
let mock; let mock;
......
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' },
{ value: 8, from: '2015-07-01', to: '2015-07-02' },
];
const startDate = new Date(2015, 5, 26, 10);
const expected = [
{
name: 'Deployments',
data: [
['Jun 26', 0],
['Jun 27', 0],
['Jun 28', 5],
['Jun 29', 1],
['Jun 30', 0],
['Jul 1', 8],
['Jul 2', 0],
['Jul 3', 0],
],
},
];
expect(apiDataToChartSeries(apiData, startDate)).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