Skip to content
Snippets Groups Projects
Commit 1d807edc authored by Jose Ivan Vargas's avatar Jose Ivan Vargas :palm_tree:
Browse files

Merge branch '357072-add-chart-for-dora-change-failure-rate-metric' into 'master'

Add Chart for DORA Change Failure Rate metric

See merge request !90890
parents 044523e0 962b68b1
No related branches found
No related tags found
1 merge request!90890Add Chart for DORA Change Failure Rate metric
Pipeline #581563083 passed
......@@ -14,6 +14,8 @@ export default {
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
TimeToRestoreServiceCharts: () =>
import('ee_component/dora/components/time_to_restore_service_charts.vue'),
ChangeFailureRateCharts: () =>
import('ee_component/dora/components/change_failure_rate_charts.vue'),
ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'),
},
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
......@@ -40,7 +42,12 @@ export default {
const chartsToShow = ['pipelines'];
if (this.shouldRenderDoraCharts) {
chartsToShow.push('deployment-frequency', 'lead-time', 'time-to-restore-service');
chartsToShow.push(
'deployment-frequency',
'lead-time',
'time-to-restore-service',
'change-failure-rate',
);
}
if (this.shouldRenderQualitySummary) {
......@@ -105,6 +112,12 @@ export default {
>
<time-to-restore-service-charts />
</gl-tab>
<gl-tab
:title="s__('DORA4Metrics|Change failure rate')"
data-testid="change-failure-rate-tab"
>
<change-failure-rate-charts />
</gl-tab>
</template>
<gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')">
<project-quality-summary />
......
......@@ -78,7 +78,7 @@ To view the lead time for changes chart:
![Lead time](img/lead_time_chart_v13_11.png)
## View time to restore service chart **(PREMIUM)**
## View time to restore service chart **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356959) in GitLab 15.1
......@@ -93,3 +93,17 @@ To view the time to restore service chart:
1. Select the **Time to restore service** tab.
![Lead time](img/time_to_restore_service_charts_v15_1.png)
## View change failure rate chart **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357072) in GitLab 15.2
The change failure rate chart shows information about the percentage of deployments that cause an incident in a production environment. This chart is available for groups and projects.
Change failure rate is one of the four [DORA metrics](index.md#devops-research-and-assessment-dora-key-metrics) that DevOps teams use for measuring excellence in software delivery.
To view the change failure rate chart:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > CI/CD Analytics**.
1. Select the **Change failure rate** tab.
......@@ -3,6 +3,7 @@ import { GlTabs, GlTab, GlLink } from '@gitlab/ui';
import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue';
import LeadTimeCharts from 'ee/dora/components/lead_time_charts.vue';
import TimeToRestoreServiceCharts from 'ee/dora/components/time_to_restore_service_charts.vue';
import ChangeFailureRateCharts from 'ee/dora/components/change_failure_rate_charts.vue';
import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
import API from '~/api';
import ReleaseStatsCard from './release_stats_card.vue';
......@@ -18,6 +19,7 @@ export default {
DeploymentFrequencyCharts,
LeadTimeCharts,
TimeToRestoreServiceCharts,
ChangeFailureRateCharts,
SharedRunnersUsage,
},
releaseStatisticsTabEvent: 'g_analytics_ci_cd_release_statistics',
......@@ -48,7 +50,12 @@ export default {
const tabsToShow = ['release-statistics'];
if (this.shouldRenderDoraCharts) {
tabsToShow.push('deployment-frequency', 'lead-time', 'time-to-restore-service');
tabsToShow.push(
'deployment-frequency',
'lead-time',
'time-to-restore-service',
'change-failure-rate',
);
}
tabsToShow.push('shared-runner-usage');
......@@ -114,6 +121,12 @@ export default {
>
<time-to-restore-service-charts />
</gl-tab>
<gl-tab
:title="s__('CICDAnalytics|Change failure rate')"
data-testid="change-failure-rate-service-tab"
>
<change-failure-rate-charts />
</gl-tab>
</template>
<gl-tab :title="s__('CICDAnalytics|Shared runner usage')">
<shared-runners-usage />
......
......@@ -174,7 +174,7 @@ export default {
};
</script>
<template>
<div>
<div data-testid="change-failure-rate-charts">
<dora-chart-header
:header-text="s__('DORA4Metrics|Change failure rate')"
:chart-description-text="$options.chartDescriptionText"
......
......@@ -48,5 +48,9 @@
it_behaves_like 'a DORA chart', '[data-testid="deployment-frequency-charts"]', 'Deployment frequency'
it_behaves_like 'a DORA chart', '[data-testid="lead-time-charts"]', 'Lead time'
it_behaves_like 'a DORA chart', '[data-testid="time-to-restore-service-charts"]', 'Time to restore service'
it_behaves_like 'a DORA chart', '[data-testid="change-failure-rate-charts"]', 'Change failure rate'
end
end
......@@ -6,6 +6,8 @@ import ReleaseStatsCard from 'ee/analytics/group_ci_cd_analytics/components/rele
import SharedRunnersUsage from 'ee/analytics/group_ci_cd_analytics/components/shared_runner_usage.vue';
import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue';
import LeadTimeCharts from 'ee/dora/components/lead_time_charts.vue';
import TimeToRestoreServiceCharts from 'ee/dora/components/time_to_restore_service_charts.vue';
import ChangeFailureRateCharts from 'ee/dora/components/change_failure_rate_charts.vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { getParameterValues } from '~/lib/utils/url_utility';
......@@ -50,13 +52,16 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
});
it('renders tabs in the correct order', () => {
expect(findGlTabs().exists()).toBe(true);
expect(findAllGlTabs()).toHaveLength(5);
expect(findGlTabAtIndex(0).attributes('title')).toBe('Release statistics');
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
expect(findGlTabAtIndex(3).attributes('title')).toBe('Time to restore service');
expect(findGlTabAtIndex(4).attributes('title')).toBe('Shared runner usage');
[
'Release statistics',
'Deployment frequency',
'Lead time',
'Time to restore service',
'Change failure rate',
'Shared runner usage',
].forEach((tabName, index) => {
expect(findGlTabAtIndex(index).attributes('title')).toBe(tabName);
});
});
describe('event tracking', () => {
......@@ -94,6 +99,13 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
it('renders the shared runner usage component', () => {
expect(wrapper.findComponent(SharedRunnersUsage).exists()).toBe(true);
});
it('does not render the DORA chart components', () => {
expect(wrapper.findComponent(DeploymentFrequencyCharts).exists()).toBe(false);
expect(wrapper.findComponent(LeadTimeCharts).exists()).toBe(false);
expect(wrapper.findComponent(TimeToRestoreServiceCharts).exists()).toBe(false);
expect(wrapper.findComponent(ChangeFailureRateCharts).exists()).toBe(false);
});
});
});
......@@ -127,6 +139,26 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
});
});
describe('time to restore service', () => {
beforeEach(() => {
createComponent();
});
it('renders the time to restore service component inside the fourth tab', () => {
expect(findGlTabAtIndex(3).findComponent(TimeToRestoreServiceCharts).exists()).toBe(true);
});
});
describe('change failure rate', () => {
beforeEach(() => {
createComponent();
});
it('renders the change failure rate inside the fifth tab', () => {
expect(findGlTabAtIndex(4).findComponent(ChangeFailureRateCharts).exists()).toBe(true);
});
});
describe('when provided with a query param', () => {
it.each`
tab | index
......@@ -134,6 +166,7 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
${'deployment-frequency'} | ${'1'}
${'lead-time'} | ${'2'}
${'time-to-restore-service'} | ${'3'}
${'change-failure-rate'} | ${'4'}
${'fake'} | ${'0'}
${''} | ${'0'}
`('shows the correct tab for URL parameter "$tab"', ({ tab, index }) => {
......
......@@ -6969,6 +6969,9 @@ msgstr ""
msgid "CICDAnalytics|All time"
msgstr ""
 
msgid "CICDAnalytics|Change failure rate"
msgstr ""
msgid "CICDAnalytics|Deployment frequency"
msgstr ""
 
......@@ -14,6 +14,7 @@ jest.mock('~/lib/utils/url_utility');
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} };
const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} };
const ChangeFailureRateChartsStub = { name: 'ChangeFailureRateCharts', render: () => {} };
const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} };
describe('ProjectsPipelinesChartsApp', () => {
......@@ -33,6 +34,7 @@ describe('ProjectsPipelinesChartsApp', () => {
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
LeadTimeCharts: LeadTimeChartsStub,
TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub,
ChangeFailureRateCharts: ChangeFailureRateChartsStub,
ProjectQualitySummary: ProjectQualitySummaryStub,
},
},
......@@ -50,6 +52,7 @@ describe('ProjectsPipelinesChartsApp', () => {
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
const findChangeFailureRateCharts = () => wrapper.find(ChangeFailureRateChartsStub);
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
const findPipelineCharts = () => wrapper.find(PipelineCharts);
const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
......@@ -59,58 +62,49 @@ describe('ProjectsPipelinesChartsApp', () => {
createComponent();
});
it('renders tabs', () => {
expect(findGlTabs().exists()).toBe(true);
describe.each`
title | finderFn | index
${'Pipelines'} | ${findPipelineCharts} | ${0}
${'Deployment frequency'} | ${findDeploymentFrequencyCharts} | ${1}
${'Lead time'} | ${findLeadTimeCharts} | ${2}
${'Time to restore service'} | ${findTimeToRestoreServiceCharts} | ${3}
${'Change failure rate'} | ${findChangeFailureRateCharts} | ${4}
${'Project quality'} | ${findProjectQualitySummary} | ${5}
`('Tabs', ({ title, finderFn, index }) => {
it(`renders tab with a title ${title} at index ${index}`, () => {
expect(findGlTabAtIndex(index).attributes('title')).toBe(title);
});
expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
expect(findGlTabAtIndex(3).attributes('title')).toBe('Time to restore service');
});
it(`renders the ${title} chart`, () => {
expect(finderFn().exists()).toBe(true);
});
it('renders the pipeline charts', () => {
expect(findPipelineCharts().exists()).toBe(true);
});
it(`updates the current tab and url when the ${title} tab is clicked`, async () => {
let chartsPath;
const tabName = title.toLowerCase().replace(/\s/g, '-');
it('renders the deployment frequency charts', () => {
expect(findDeploymentFrequencyCharts().exists()).toBe(true);
});
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
it('renders the lead time charts', () => {
expect(findLeadTimeCharts().exists()).toBe(true);
});
mergeUrlParams.mockImplementation(({ chart }, path) => {
expect(chart).toBe(tabName);
expect(path).toBe(window.location.pathname);
chartsPath = `${path}?chart=${chart}`;
return chartsPath;
});
it('renders the time to restore service charts', () => {
expect(findTimeToRestoreServiceCharts().exists()).toBe(true);
});
updateHistory.mockImplementation(({ url }) => {
expect(url).toBe(chartsPath);
});
const tabs = findGlTabs();
it('renders the project quality summary', () => {
expect(findProjectQualitySummary().exists()).toBe(true);
});
expect(tabs.attributes('value')).toBe('0');
it('sets the tab and url when a tab is clicked', async () => {
let chartsPath;
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
tabs.vm.$emit('input', index);
mergeUrlParams.mockImplementation(({ chart }, path) => {
expect(chart).toBe('deployment-frequency');
expect(path).toBe(window.location.pathname);
chartsPath = `${path}?chart=${chart}`;
return chartsPath;
});
await nextTick();
updateHistory.mockImplementation(({ url }) => {
expect(url).toBe(chartsPath);
expect(tabs.attributes('value')).toBe(index.toString());
});
const tabs = findGlTabs();
expect(tabs.attributes('value')).toBe('0');
tabs.vm.$emit('input', 1);
await nextTick();
expect(tabs.attributes('value')).toBe('1');
});
it('should not try to push history if the tab does not change', async () => {
......@@ -151,6 +145,7 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('when provided with a query param', () => {
it.each`
chart | tab
${'change-failure-rate'} | ${'4'}
${'time-to-restore-service'} | ${'3'}
${'lead-time'} | ${'2'}
${'deployment-frequency'} | ${'1'}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment