Commit 96fa5c90 authored by Nathan Friend's avatar Nathan Friend

Update Deployment Frequency to use new API

This commit updates the project-level Deployment Frequency graphs to use
the new `dora/metrics` REST API. The previous API was deprecated in
favor of the new API.
parent 3c6281a6
import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils';
export const DEPLOYMENT_FREQUENCY_METRIC_TYPE = 'deployment_frequency';
export const LEAD_TIME_FOR_CHANGES = 'lead_time_for_changes';
export const ALL_METRIC_TYPES = Object.freeze([
DEPLOYMENT_FREQUENCY_METRIC_TYPE,
LEAD_TIME_FOR_CHANGES,
]);
const PROJECTS_DORA_METRICS_PATH = '/api/:version/projects/:id/dora/metrics';
/**
* Gets DORA 4 metrics data from a project
* See https://docs.gitlab.com/ee/api/dora/metrics.html
*
* @param {String|Number} projectId The ID or path of the project
* @param {String} metric The name of the metric to fetch. Must be one of:
* `["deployment_frequency", "lead_time_for_changes"]`
* @param {Object} params Any additional query parameters that should be
* included with the request. These parameters are optional. See
* https://docs.gitlab.com/ee/api/dora/metrics.html for a list of available options.
*
* @returns {Promise} A `Promise` that resolves to an array of data points.
*/
export function getProjectDoraMetrics(projectId, metric, params = {}) {
if (!ALL_METRIC_TYPES.includes(metric)) {
throw new Error(`Unsupported metric type provided to getProjectDoraMetrics(): "${metric}"`);
}
const url = buildApiUrl(PROJECTS_DORA_METRICS_PATH).replace(':id', encodeURIComponent(projectId));
return axios.get(url, {
params: {
metric,
...params,
},
});
}
<script> <script>
import { GlLink, GlSprintf } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import Api from 'ee/api'; import * as DoraApi from 'ee/api/dora_api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_analytics_charts.vue'; import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_analytics_charts.vue';
...@@ -47,7 +47,11 @@ export default { ...@@ -47,7 +47,11 @@ export default {
async mounted() { async mounted() {
const results = await Promise.allSettled( const results = await Promise.allSettled(
allChartDefinitions.map(async ({ id, requestParams, startDate, endDate }) => { allChartDefinitions.map(async ({ id, requestParams, startDate, endDate }) => {
const { data: apiData } = await Api.deploymentFrequencies(this.projectPath, requestParams); const { data: apiData } = await DoraApi.getProjectDoraMetrics(
this.projectPath,
DoraApi.DEPLOYMENT_FREQUENCY_METRIC_TYPE,
requestParams,
);
this.chartData[id] = apiDataToChartSeries(apiData, startDate, endDate); this.chartData[id] = apiDataToChartSeries(apiData, startDate, endDate);
}), }),
......
...@@ -19,8 +19,8 @@ const last90Days = nDaysBefore(startOfTomorrow, 90, { utc: true }); ...@@ -19,8 +19,8 @@ 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 = {
environment: 'production',
interval: 'daily', interval: 'daily',
end_date: dateFormat(startOfTomorrow, apiDateFormatString, true),
// We will never have more than 91 records (1 record per day), so we // 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 // don't have to worry about making multiple requests to get all the results
...@@ -39,8 +39,7 @@ export const allChartDefinitions = [ ...@@ -39,8 +39,7 @@ export const allChartDefinitions = [
endDate: startOfTomorrow, endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(lastWeek, apiDateFormatString, true), start_date: dateFormat(lastWeek, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
{ {
...@@ -54,8 +53,7 @@ export const allChartDefinitions = [ ...@@ -54,8 +53,7 @@ export const allChartDefinitions = [
endDate: startOfTomorrow, endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(lastMonth, apiDateFormatString, true), start_date: dateFormat(lastMonth, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
{ {
...@@ -69,8 +67,7 @@ export const allChartDefinitions = [ ...@@ -69,8 +67,7 @@ export const allChartDefinitions = [
endDate: startOfTomorrow, endDate: startOfTomorrow,
requestParams: { requestParams: {
...sharedRequestParams, ...sharedRequestParams,
from: dateFormat(last90Days, apiDateFormatString, true), start_date: dateFormat(last90Days, apiDateFormatString, true),
to: dateFormat(startOfTomorrow, apiDateFormatString, true),
}, },
}, },
]; ];
......
...@@ -24,7 +24,7 @@ export const apiDataToChartSeries = (apiData, startDate, endDate) => { ...@@ -24,7 +24,7 @@ export const apiDataToChartSeries = (apiData, startDate, endDate) => {
// The timestamps are explicitly set to the _beginning_ of the day (in UTC) // The timestamps are explicitly set to the _beginning_ of the day (in UTC)
// so that we can confidently compare dates by value below. // so that we can confidently compare dates by value below.
const timestampToApiValue = apiData.reduce((acc, curr) => { const timestampToApiValue = apiData.reduce((acc, curr) => {
const apiTimestamp = getStartOfDay(new Date(curr.from), { utc: true }).getTime(); const apiTimestamp = getStartOfDay(new Date(curr.date), { utc: true }).getTime();
acc[apiTimestamp] = curr.value; acc[apiTimestamp] = curr.value;
return acc; return acc;
}, {}); }, {});
......
export * from './api/groups_api'; export * from './api/groups_api';
export * from './api/subscriptions_api'; export * from './api/subscriptions_api';
export * from './api/dora_api';
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Project Analytics (JavaScript fixtures)' do
include ApiHelpers
include JavaScriptFixturesHelpers
let_it_be(:reporter) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:environment) { create(:environment, project: project, name: 'production') }
let!(:deployments) do
[
1.minute.ago,
2.days.ago,
3.days.ago,
3.days.ago,
3.days.ago,
8.days.ago,
32.days.ago,
91.days.ago
].map do |finished_at|
create(:deployment,
:success,
project: project,
environment: environment,
finished_at: finished_at)
end
end
before do
stub_licensed_features(dora4_analytics: true)
project.add_reporter(reporter)
sign_in(reporter)
end
after(:all) do
remove_repository(project)
end
describe API::Analytics::ProjectDeploymentFrequency, type: :request do
before(:all) do
clean_frontend_fixtures('api/project_analytics/')
end
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)
get api("/projects/#{project.id}/analytics/deployment_frequency?#{params.to_query}", reporter)
end
it 'api/project_analytics/daily_deployment_frequencies_for_last_week.json' do
make_request(additional_query_params: { from: 1.week.ago })
expect(response).to be_successful
end
it 'api/project_analytics/daily_deployment_frequencies_for_last_month.json' do
make_request(additional_query_params: { from: 1.month.ago })
expect(response).to be_successful
end
it 'api/project_analytics/daily_deployment_frequencies_for_last_90_days.json' do
make_request(additional_query_params: { from: 90.days.ago })
expect(response).to be_successful
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'DORA Metrics (JavaScript fixtures)' do
include ApiHelpers
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
let_it_be(:environment) { create(:environment, project: project, name: 'production') }
before_all do
[
{ date: '2015-06-01', value: 1 },
{ date: '2015-06-25', value: 1 },
{ date: '2015-06-30', value: 3 },
{ date: '2015-07-01', value: 1 },
{ date: '2015-07-03', value: 1 }
].each do |data_point|
create(:dora_daily_metrics, deployment_frequency: data_point[:value], environment: environment, date: data_point[:date])
end
end
before do
stub_licensed_features(dora4_analytics: true)
sign_in(reporter)
end
after(:all) do
remove_repository(project)
end
describe API::Dora::Metrics, type: :request do
before(:all) do
clean_frontend_fixtures('api/dora/metrics')
end
describe 'deployment frequency' do
let(:shared_params) do
{
metric: 'deployment_frequency',
end_date: Date.tomorrow.beginning_of_day,
interval: 'daily'
}
end
def make_request(additional_query_params:)
params = shared_params.merge(additional_query_params)
get api("/projects/#{project.id}/dora/metrics?#{params.to_query}", reporter)
end
it 'api/dora/metrics/daily_deployment_frequencies_for_last_week.json' do
make_request(additional_query_params: { start_date: 1.week.ago })
expect(response).to be_successful
end
it 'api/dora/metrics/daily_deployment_frequencies_for_last_month.json' do
make_request(additional_query_params: { start_date: 1.month.ago })
expect(response).to be_successful
end
it 'api/dora/metrics/daily_deployment_frequencies_for_last_90_days.json' do
make_request(additional_query_params: { start_date: 90.days.ago })
expect(response).to be_successful
end
end
end
end
...@@ -42,11 +42,10 @@ Array [ ...@@ -42,11 +42,10 @@ Array [
"id": "LAST_WEEK", "id": "LAST_WEEK",
"range": "Jun 27 - Jul 3", "range": "Jun 27 - Jul 3",
"requestParams": Object { "requestParams": Object {
"environment": "production", "end_date": "2015-07-04T00:00:00+0000",
"from": "2015-06-27T00:00:00+0000",
"interval": "daily", "interval": "daily",
"per_page": 100, "per_page": 100,
"to": "2015-07-04T00:00:00+0000", "start_date": "2015-06-27T00:00:00+0000",
}, },
"startDate": 2015-06-27T00:00:00.000Z, "startDate": 2015-06-27T00:00:00.000Z,
"title": "Last week", "title": "Last week",
...@@ -183,11 +182,10 @@ Array [ ...@@ -183,11 +182,10 @@ Array [
"id": "LAST_MONTH", "id": "LAST_MONTH",
"range": "Jun 4 - Jul 3", "range": "Jun 4 - Jul 3",
"requestParams": Object { "requestParams": Object {
"environment": "production", "end_date": "2015-07-04T00:00:00+0000",
"from": "2015-06-04T00:00:00+0000",
"interval": "daily", "interval": "daily",
"per_page": 100, "per_page": 100,
"to": "2015-07-04T00:00:00+0000", "start_date": "2015-06-04T00:00:00+0000",
}, },
"startDate": 2015-06-04T00:00:00.000Z, "startDate": 2015-06-04T00:00:00.000Z,
"title": "Last month", "title": "Last month",
...@@ -564,11 +562,10 @@ Array [ ...@@ -564,11 +562,10 @@ Array [
"id": "LAST_90_DAYS", "id": "LAST_90_DAYS",
"range": "Apr 5 - Jul 3", "range": "Apr 5 - Jul 3",
"requestParams": Object { "requestParams": Object {
"environment": "production", "end_date": "2015-07-04T00:00:00+0000",
"from": "2015-04-05T00:00:00+0000",
"interval": "daily", "interval": "daily",
"per_page": 100, "per_page": 100,
"to": "2015-07-04T00:00:00+0000", "start_date": "2015-04-05T00:00:00+0000",
}, },
"startDate": 2015-04-05T00:00:00.000Z, "startDate": 2015-04-05T00:00:00.000Z,
"title": "Last 90 days", "title": "Last 90 days",
......
...@@ -11,13 +11,13 @@ import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_an ...@@ -11,13 +11,13 @@ import CiCdAnalyticsCharts from '~/projects/pipelines/charts/components/ci_cd_an
jest.mock('~/flash'); jest.mock('~/flash');
const lastWeekData = getJSONFixture( const lastWeekData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_week.json', 'api/dora/metrics/daily_deployment_frequencies_for_last_week.json',
); );
const lastMonthData = getJSONFixture( const lastMonthData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_month.json', 'api/dora/metrics/daily_deployment_frequencies_for_last_month.json',
); );
const last90DaysData = getJSONFixture( const last90DaysData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_90_days.json', 'api/dora/metrics/daily_deployment_frequencies_for_last_90_days.json',
); );
describe('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue', () => { describe('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue', () => {
...@@ -49,15 +49,15 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency ...@@ -49,15 +49,15 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
// Initializes the mock endpoint to return a specific set of deployment // Initializes the mock endpoint to return a specific set of deployment
// frequency data for a given "from" date. // frequency data for a given "from" date.
const setUpMockDeploymentFrequencies = ({ from, data }) => { const setUpMockDeploymentFrequencies = ({ start_date, data }) => {
mock mock
.onGet(/projects\/test%2Fproject\/analytics\/deployment_frequency/, { .onGet(/projects\/test%2Fproject\/dora\/metrics/, {
params: { params: {
environment: 'production', metric: 'deployment_frequency',
interval: 'daily', interval: 'daily',
per_page: 100, per_page: 100,
to: '2015-07-04T00:00:00+0000', end_date: '2015-07-04T00:00:00+0000',
from, start_date,
}, },
}) })
.replyOnce(httpStatus.OK, data); .replyOnce(httpStatus.OK, data);
...@@ -77,15 +77,15 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency ...@@ -77,15 +77,15 @@ describe('ee_component/projects/pipelines/charts/components/deployment_frequency
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
setUpMockDeploymentFrequencies({ setUpMockDeploymentFrequencies({
from: '2015-06-27T00:00:00+0000', start_date: '2015-06-27T00:00:00+0000',
data: lastWeekData, data: lastWeekData,
}); });
setUpMockDeploymentFrequencies({ setUpMockDeploymentFrequencies({
from: '2015-06-04T00:00:00+0000', start_date: '2015-06-04T00:00:00+0000',
data: lastMonthData, data: lastMonthData,
}); });
setUpMockDeploymentFrequencies({ setUpMockDeploymentFrequencies({
from: '2015-04-05T00:00:00+0000', start_date: '2015-04-05T00:00:00+0000',
data: last90DaysData, data: last90DaysData,
}); });
......
...@@ -5,11 +5,11 @@ describe('ee/projects/pipelines/charts/components/util.js', () => { ...@@ -5,11 +5,11 @@ describe('ee/projects/pipelines/charts/components/util.js', () => {
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 = [
// This is the date format we expect from the API // 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' }, { value: 5, date: '2015-06-28' },
// But we should support _any_ date format // 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: 1, date: '2015-06-28T20:00:00.000-0400' },
{ value: 8, from: '2015-07-01', to: '2015-07-02' }, { value: 8, date: '2015-07-01T00:00:00.000Z' },
]; ];
const startDate = new Date(2015, 5, 26, 10); const startDate = new Date(2015, 5, 26, 10);
......
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