Commit 92c36c07 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch 'nfriend-add-deployment-frequency-chart-rest' into 'master'

Add deployment frequency charts to CI/CD Analytics page

See merge request gitlab-org/gitlab!50885
parents c08f828f 9b9b25ea
......@@ -7,7 +7,8 @@ import { getDateInPast } from '~/lib/utils/datetime_utility';
import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
import StatisticsList from './statistics_list.vue';
import PipelinesAreaChart from './pipelines_area_chart.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
import {
CHART_CONTAINER_HEIGHT,
CHART_DATE_FORMAT,
......@@ -52,13 +53,19 @@ export default {
GlColumnChart,
GlSkeletonLoader,
StatisticsList,
PipelinesAreaChart,
CiCdAnalyticsAreaChart,
DeploymentFrequencyCharts: () =>
import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'),
},
inject: {
projectPath: {
type: String,
default: '',
},
shouldRenderDeploymentFrequencyCharts: {
type: Boolean,
default: false,
},
},
data() {
return {
......@@ -260,6 +267,15 @@ export default {
lastYear: __('Pipelines for last year'),
};
},
areaChartOptions: {
xAxis: {
name: s__('Pipeline|Date'),
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
},
},
};
</script>
<template>
......@@ -292,12 +308,17 @@ export default {
</div>
<hr />
<h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
<pipelines-area-chart
<ci-cd-analytics-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>
{{ chart.title }}
</pipelines-area-chart>
</ci-cd-analytics-area-chart>
<template v-if="shouldRenderDeploymentFrequencyCharts">
<hr />
<deployment-frequency-charts />
</template>
</div>
</template>
<script>
import dateFormat from 'dateformat';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import { __, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import StatisticsList from './statistics_list.vue';
import PipelinesAreaChart from './pipelines_area_chart.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
import {
CHART_CONTAINER_HEIGHT,
INNER_CHART_HEIGHT,
......@@ -19,7 +20,15 @@ export default {
components: {
StatisticsList,
GlColumnChart,
PipelinesAreaChart,
CiCdAnalyticsAreaChart,
DeploymentFrequencyCharts: () =>
import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'),
},
inject: {
shouldRenderDeploymentFrequencyCharts: {
type: Boolean,
default: false,
},
},
props: {
counts: {
......@@ -112,6 +121,15 @@ export default {
lastYear: __('Pipelines for last year'),
};
},
areaChartOptions: {
xAxis: {
name: s__('Pipeline|Date'),
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
},
},
};
</script>
<template>
......@@ -140,12 +158,17 @@ export default {
</div>
<hr />
<h4 class="my-4">{{ __('Pipelines charts') }}</h4>
<pipelines-area-chart
<ci-cd-analytics-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>
{{ chart.title }}
</pipelines-area-chart>
</ci-cd-analytics-area-chart>
<template v-if="shouldRenderDeploymentFrequencyCharts">
<hr />
<deployment-frequency-charts />
</template>
</div>
</template>
<script>
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { s__ } from '~/locale';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
import { CHART_CONTAINER_HEIGHT } from '../constants';
export default {
name: 'CiCdAnalyticsAreaChart',
components: {
GlAreaChart,
ResizableChartContainer,
......@@ -14,14 +14,9 @@ export default {
type: Array,
required: true,
},
},
areaChartOptions: {
xAxis: {
name: s__('Pipeline|Date'),
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
areaChartOptions: {
type: Object,
required: true,
},
},
chartContainerHeight: CHART_CONTAINER_HEIGHT,
......@@ -39,7 +34,7 @@ export default {
:height="$options.chartContainerHeight"
:data="chartData"
:include-legend-avg-max="false"
:option="$options.areaChartOptions"
:option="areaChartOptions"
/>
</resizable-chart-container>
</div>
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import ProjectPipelinesChartsLegacy from './components/app_legacy.vue';
import ProjectPipelinesCharts from './components/app.vue';
......@@ -35,6 +36,10 @@ const mountPipelineChartsApp = (el) => {
projectPath,
} = el.dataset;
const shouldRenderDeploymentFrequencyCharts = parseBoolean(
el.dataset.shouldRenderDeploymentFrequencyCharts,
);
const parseAreaChartData = (labels, totals, success) => {
let parsedData = {};
......@@ -61,6 +66,7 @@ const mountPipelineChartsApp = (el) => {
apolloProvider,
provide: {
projectPath,
shouldRenderDeploymentFrequencyCharts,
},
render: (createElement) => createElement(ProjectPipelinesCharts, {}),
});
......@@ -72,6 +78,10 @@ const mountPipelineChartsApp = (el) => {
components: {
ProjectPipelinesChartsLegacy,
},
provide: {
projectPath,
shouldRenderDeploymentFrequencyCharts,
},
render: (createElement) =>
createElement(ProjectPipelinesChartsLegacy, {
props: {
......
......@@ -22,4 +22,10 @@ module GraphHelper
ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
ratio.to_i
end
def should_render_deployment_frequency_charts
false
end
end
GraphHelper.prepend_if_ee('EE::GraphHelper')
- page_title _('CI / CD Analytics')
- if Feature.enabled?(:graphql_pipeline_analytics)
#js-project-pipelines-charts-app{ data: { project_path: @project.full_path } }
#js-project-pipelines-charts-app{ data: { project_path: @project.full_path,
should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } }
- else
#js-project-pipelines-charts-app{ data: { counts: @counts, success_ratio: success_ratio(@counts),
times_chart: { labels: @charts[:pipeline_times].labels, values: @charts[:pipeline_times].pipeline_times },
last_week_chart: { labels: @charts[:week].labels, totals: @charts[:week].total, success: @charts[:week].success },
last_month_chart: { labels: @charts[:month].labels, totals: @charts[:month].total, success: @charts[:month].success },
last_year_chart: { labels: @charts[:year].labels, totals: @charts[:year].total, success: @charts[:year].success } } }
last_year_chart: { labels: @charts[:year].labels, totals: @charts[:year].total, success: @charts[:year].success },
project_path: @project.full_path,
should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } }
......@@ -42,6 +42,8 @@ export default {
vulnerabilityIssueLinksPath: '/api/:version/vulnerabilities/:id/issue_links',
applicationSettingsPath: '/api/:version/application/settings',
descendantGroupsPath: '/api/:version/groups/:group_id/descendant_groups',
projectDeploymentFrequencyAnalyticsPath:
'/api/:version/projects/:id/analytics/deployment_frequency',
userSubscription(namespaceId) {
const url = Api.buildUrl(this.subscriptionPath).replace(':id', encodeURIComponent(namespaceId));
......@@ -330,4 +332,13 @@ export default {
const url = Api.buildUrl(this.applicationSettingsPath);
return axios.put(url, data);
},
deploymentFrequencies(projectId, params = {}) {
const url = Api.buildUrl(this.projectDeploymentFrequencyAnalyticsPath).replace(
':id',
encodeURIComponent(projectId),
);
return axios.get(url, { params });
},
};
<script>
import dateFormat from 'dateformat';
import Api from 'ee/api';
import { s__, sprintf } from '~/locale';
import createFlash from '~/flash';
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';
export default {
name: 'DeploymentFrequencyCharts',
components: {
CiCdAnalyticsAreaChart,
},
inject: {
projectPath: {
type: String,
default: '',
},
},
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 {
charts: [
{
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),
},
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() {
const results = await Promise.allSettled(
this.charts.map(async (c) => {
const chart = c;
chart.isLoading = true;
try {
const { data: apiData } = await Api.deploymentFrequencies(
this.projectPath,
chart.requestParams,
);
chart.data = this.apiDataToChartSeries(apiData, chart.startDate);
} finally {
chart.isLoading = false;
}
}),
);
const requestErrors = results.filter((r) => r.status === 'rejected').map((r) => r.reason);
if (requestErrors.length) {
createFlash({
message: s__(
'DeploymentFrequencyCharts|Something went wrong while getting deployment frequency data',
),
});
const allErrorMessages = requestErrors.join('\n');
Sentry.captureException(
new Error(
`Something went wrong while getting deployment frequency data:\n${allErrorMessages}`,
),
);
}
},
methods: {
/**
* 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>
<template>
<div>
<h4 class="gl-my-4">{{ s__('DeploymentFrequencyCharts|Deployments charts') }}</h4>
<ci-cd-analytics-area-chart
v-for="(chart, index) in charts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>
{{ chart.title }}
</ci-cd-analytics-area-chart>
</div>
</template>
# frozen_string_literal: true
module EE
module GraphHelper
extend ::Gitlab::Utils::Override
override :should_render_deployment_frequency_charts
def should_render_deployment_frequency_charts
return false unless ::Feature.enabled?(:deployment_frequency_charts, @project)
return false unless @project.feature_available?(:project_activity_analytics)
can?(current_user, :read_project_activity_analytics, @project)
end
end
end
---
name: deployment_frequency_charts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50885
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/296893
milestone: '13.8'
type: development
group: group::release
default_enabled: false
......@@ -818,4 +818,21 @@ describe('Api', () => {
});
});
});
describe('Project analytics: deployment frequency', () => {
const projectPath = 'test/project';
const encodedProjectPath = encodeURIComponent(projectPath);
const params = { environment: 'production' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodedProjectPath}/analytics/deployment_frequency`;
describe('deploymentFrequencies', () => {
it('GETs the right url', async () => {
mock.onGet(expectedUrl, { params }).replyOnce(httpStatus.OK, []);
const { data } = await Api.deploymentFrequencies(projectPath, params);
expect(data).toEqual([]);
});
});
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
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,
],
Array [
"Jun 28",
0,
],
Array [
"Jun 29",
0,
],
Array [
"Jun 30",
3,
],
Array [
"Jul 1",
1,
],
Array [
"Jul 2",
0,
],
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,
],
Array [
"Jun 5",
0,
],
Array [
"Jun 6",
0,
],
Array [
"Jun 7",
0,
],
Array [
"Jun 8",
0,
],
Array [
"Jun 9",
0,
],
Array [
"Jun 10",
0,
],
Array [
"Jun 11",
0,
],
Array [
"Jun 12",
0,
],
Array [
"Jun 13",
0,
],
Array [
"Jun 14",
0,
],
Array [
"Jun 15",
0,
],
Array [
"Jun 16",
0,
],
Array [
"Jun 17",
0,
],
Array [
"Jun 18",
0,
],
Array [
"Jun 19",
0,
],
Array [
"Jun 20",
0,
],
Array [
"Jun 21",
0,
],
Array [
"Jun 22",
0,
],
Array [
"Jun 23",
0,
],
Array [
"Jun 24",
0,
],
Array [
"Jun 25",
1,
],
Array [
"Jun 26",
0,
],
Array [
"Jun 27",
0,
],
Array [
"Jun 28",
0,
],
Array [
"Jun 29",
0,
],
Array [
"Jun 30",
3,
],
Array [
"Jul 1",
1,
],
Array [
"Jul 2",
0,
],
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,
],
Array [
"Apr 6",
0,
],
Array [
"Apr 7",
0,
],
Array [
"Apr 8",
0,
],
Array [
"Apr 9",
0,
],
Array [
"Apr 10",
0,
],
Array [
"Apr 11",
0,
],
Array [
"Apr 12",
0,
],
Array [
"Apr 13",
0,
],
Array [
"Apr 14",
0,
],
Array [
"Apr 15",
0,
],
Array [
"Apr 16",
0,
],
Array [
"Apr 17",
0,
],
Array [
"Apr 18",
0,
],
Array [
"Apr 19",
0,
],
Array [
"Apr 20",
0,
],
Array [
"Apr 21",
0,
],
Array [
"Apr 22",
0,
],
Array [
"Apr 23",
0,
],
Array [
"Apr 24",
0,
],
Array [
"Apr 25",
0,
],
Array [
"Apr 26",
0,
],
Array [
"Apr 27",
0,
],
Array [
"Apr 28",
0,
],
Array [
"Apr 29",
0,
],
Array [
"Apr 30",
0,
],
Array [
"May 1",
0,
],
Array [
"May 2",
0,
],
Array [
"May 3",
0,
],
Array [
"May 4",
0,
],
Array [
"May 5",
0,
],
Array [
"May 6",
0,
],
Array [
"May 7",
0,
],
Array [
"May 8",
0,
],
Array [
"May 9",
0,
],
Array [
"May 10",
0,
],
Array [
"May 11",
0,
],
Array [
"May 12",
0,
],
Array [
"May 13",
0,
],
Array [
"May 14",
0,
],
Array [
"May 15",
0,
],
Array [
"May 16",
0,
],
Array [
"May 17",
0,
],
Array [
"May 18",
0,
],
Array [
"May 19",
0,
],
Array [
"May 20",
0,
],
Array [
"May 21",
0,
],
Array [
"May 22",
0,
],
Array [
"May 23",
0,
],
Array [
"May 24",
0,
],
Array [
"May 25",
0,
],
Array [
"May 26",
0,
],
Array [
"May 27",
0,
],
Array [
"May 28",
0,
],
Array [
"May 29",
0,
],
Array [
"May 30",
0,
],
Array [
"May 31",
0,
],
Array [
"Jun 1",
1,
],
Array [
"Jun 2",
0,
],
Array [
"Jun 3",
0,
],
Array [
"Jun 4",
0,
],
Array [
"Jun 5",
0,
],
Array [
"Jun 6",
0,
],
Array [
"Jun 7",
0,
],
Array [
"Jun 8",
0,
],
Array [
"Jun 9",
0,
],
Array [
"Jun 10",
0,
],
Array [
"Jun 11",
0,
],
Array [
"Jun 12",
0,
],
Array [
"Jun 13",
0,
],
Array [
"Jun 14",
0,
],
Array [
"Jun 15",
0,
],
Array [
"Jun 16",
0,
],
Array [
"Jun 17",
0,
],
Array [
"Jun 18",
0,
],
Array [
"Jun 19",
0,
],
Array [
"Jun 20",
0,
],
Array [
"Jun 21",
0,
],
Array [
"Jun 22",
0,
],
Array [
"Jun 23",
0,
],
Array [
"Jun 24",
0,
],
Array [
"Jun 25",
1,
],
Array [
"Jun 26",
0,
],
Array [
"Jun 27",
0,
],
Array [
"Jun 28",
0,
],
Array [
"Jun 29",
0,
],
Array [
"Jun 30",
3,
],
Array [
"Jul 1",
1,
],
Array [
"Jul 2",
0,
],
Array [
"Jul 3",
1,
],
Array [
"Jul 4",
0,
],
]
`;
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
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 axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as Sentry from '~/sentry/wrapper';
import httpStatus from '~/lib/utils/http_status';
jest.mock('~/flash');
jest.mock('~/sentry/wrapper');
const lastWeekData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_week.json',
);
const lastMonthData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_month.json',
);
const last90DaysData = getJSONFixture(
'api/project_analytics/daily_deployment_frequencies_for_last_90_days.json',
);
describe('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue', () => {
// Set the current Date to the same value that is used when generating the fixtures
useFakeDate(2015, 6, 3, 10);
let wrapper;
let mock;
const createComponent = () => {
wrapper = shallowMount(DeploymentFrequencyCharts, {
provide: {
projectPath: 'test/project',
},
});
};
// Initializes the mock endpoint to return a specific set of deployment
// frequency data for a given "from" date.
const setUpMockDeploymentFrequencies = ({ from, data }) => {
mock
.onGet(/projects\/test%2Fproject\/analytics\/deployment_frequency/, {
params: {
environment: 'production',
interval: 'daily',
per_page: 100,
from,
},
})
.replyOnce(httpStatus.OK, data);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
mock.restore();
});
describe('when there are no network errors', () => {
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 });
createComponent();
await axios.waitForAll();
});
it('makes 3 GET requests - one for each chart', () => {
expect(mock.history.get).toHaveLength(3);
});
it('converts the data from the API into data usable by the chart component', () => {
wrapper.findAll(CiCdAnalyticsAreaChart).wrappers.forEach((chartWrapper) => {
expect(chartWrapper.props().chartData[0].data).toMatchSnapshot();
});
});
it('does not show a flash message', () => {
expect(createFlash).not.toHaveBeenCalled();
});
});
describe('when there are network errors', () => {
beforeEach(async () => {
mock = new MockAdapter(axios);
createComponent();
await axios.waitForAll();
});
it('shows a flash message', () => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith({
message: 'Something went wrong while getting deployment frequency data',
});
});
it('reports an error to Sentry', () => {
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
const expectedErrorMessage = [
'Something went wrong while getting deployment frequency data:',
'Error: Request failed with status code 404',
'Error: Request failed with status code 404',
'Error: Request failed with status code 404',
].join('\n');
expect(Sentry.captureException).toHaveBeenCalledWith(new Error(expectedErrorMessage));
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::GraphHelper do
describe '#should_render_deployment_frequency_charts' do
let_it_be(:current_user) { create(:user) }
let(:project) { create(:project, :private) }
let(:is_feature_licensed) { true }
let(:is_flag_enabled) { true }
let(:is_user_authorized) { true }
before do
stub_licensed_features(project_activity_analytics: is_feature_licensed)
stub_feature_flags(deployment_frequency_charts: is_flag_enabled)
self.instance_variable_set(:@current_user, current_user)
self.instance_variable_set(:@project, project)
allow(self).to receive(:can?).with(current_user, :read_project_activity_analytics, project).and_return(is_user_authorized)
end
shared_examples 'returns true' do
it { expect(should_render_deployment_frequency_charts).to be(true) }
end
shared_examples 'returns false' do
it { expect(should_render_deployment_frequency_charts).to be(false) }
end
it_behaves_like 'returns true'
context 'when the feature is not available' do
let(:is_feature_licensed) { false }
it_behaves_like 'returns false'
end
context 'when the feature flag is disabled' do
let(:is_flag_enabled) { false }
it_behaves_like 'returns false'
end
context 'when the user does not have permission' do
let(:is_user_authorized) { false }
it_behaves_like 'returns false'
end
end
end
......@@ -9622,6 +9622,27 @@ msgstr ""
msgid "Deployment Frequency"
msgstr ""
msgid "DeploymentFrequencyCharts|Date"
msgstr ""
msgid "DeploymentFrequencyCharts|Deployments"
msgstr ""
msgid "DeploymentFrequencyCharts|Deployments charts"
msgstr ""
msgid "DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})"
msgstr ""
msgid "DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})"
msgstr ""
msgid "DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})"
msgstr ""
msgid "DeploymentFrequencyCharts|Something went wrong while getting deployment frequency data"
msgstr ""
msgid "Deployment|API"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PipelinesAreaChart matches the snapshot 1`] = `
exports[`CiCdAnalyticsAreaChart matches the snapshot 1`] = `
<div
class="gl-mt-3"
>
......
......@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import Component from '~/projects/pipelines/charts/components/app_legacy.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import {
counts,
timesChartData,
......@@ -23,6 +23,13 @@ describe('ProjectsPipelinesChartsApp', () => {
lastMonthChartData,
lastYearChartData,
},
provide: {
projectPath: 'test/project',
shouldRenderDeploymentFrequencyCharts: true,
},
stubs: {
DeploymentFrequencyCharts: true,
},
});
});
......@@ -52,12 +59,12 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('pipelines charts', () => {
it('displays 3 area charts', () => {
expect(wrapper.findAll(PipelinesAreaChart).length).toBe(3);
expect(wrapper.findAll(CiCdAnalyticsAreaChart).length).toBe(3);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
const charts = wrapper.findAll(PipelinesAreaChart);
const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
for (let i = 0; i < charts.length; i += 1) {
const chart = charts.at(i);
......
import { merge } from 'lodash';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import Component from '~/projects/pipelines/charts/components/app.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql';
import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql';
import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
......@@ -13,6 +14,8 @@ const projectPath = 'gitlab-org/gitlab';
const localVue = createLocalVue();
localVue.use(VueApollo);
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
describe('ProjectsPipelinesChartsApp', () => {
let wrapper;
......@@ -25,21 +28,29 @@ describe('ProjectsPipelinesChartsApp', () => {
return createMockApollo(requestHandlers);
}
function createComponent(options = {}) {
const { fakeApollo } = options;
return shallowMount(Component, {
provide: {
projectPath,
},
localVue,
apolloProvider: fakeApollo,
});
function createComponent(mountOptions = {}) {
wrapper = shallowMount(
Component,
merge(
{},
{
provide: {
projectPath,
shouldRenderDeploymentFrequencyCharts: false,
},
localVue,
apolloProvider: createMockApolloProvider(),
stubs: {
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
},
},
mountOptions,
),
);
}
beforeEach(() => {
const fakeApollo = createMockApolloProvider();
wrapper = createComponent({ fakeApollo });
createComponent();
});
afterEach(() => {
......@@ -73,12 +84,12 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('pipelines charts', () => {
it('displays 3 area charts', () => {
expect(wrapper.findAll(PipelinesAreaChart)).toHaveLength(3);
expect(wrapper.findAll(CiCdAnalyticsAreaChart)).toHaveLength(3);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
const charts = wrapper.findAll(PipelinesAreaChart);
const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
for (let i = 0; i < charts.length; i += 1) {
const chart = charts.at(i);
......@@ -92,4 +103,26 @@ describe('ProjectsPipelinesChartsApp', () => {
});
});
});
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
describe('when shouldRenderDeploymentFrequencyCharts is true', () => {
beforeEach(() => {
createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } });
});
it('renders the deployment frequency charts', () => {
expect(findDeploymentFrequencyCharts().exists()).toBe(true);
});
});
describe('when shouldRenderDeploymentFrequencyCharts is false', () => {
beforeEach(() => {
createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: false } });
});
it('does not render the deployment frequency charts', () => {
expect(findDeploymentFrequencyCharts().exists()).toBe(false);
});
});
});
import { mount } from '@vue/test-utils';
import Component from '~/projects/pipelines/charts/components/pipelines_area_chart.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import { transformedAreaChartData } from '../mock_data';
describe('PipelinesAreaChart', () => {
describe('CiCdAnalyticsAreaChart', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(Component, {
wrapper = mount(CiCdAnalyticsAreaChart, {
propsData: {
chartData: transformedAreaChartData,
areaChartOptions: {
xAxis: {
name: 'X axis title',
type: 'category',
},
yAxis: {
name: 'Y axis title',
},
},
},
slots: {
default: 'Some title',
......
......@@ -15,4 +15,16 @@ RSpec.describe GraphHelper do
expect(refs).to match('master')
end
end
describe '#should_render_deployment_frequency_charts' do
let(:project) { create(:project, :private) }
before do
self.instance_variable_set(:@project, project)
end
it 'always returns false' do
expect(should_render_deployment_frequency_charts).to be(false)
end
end
end
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