Commit 483b6276 authored by Nathan Friend's avatar Nathan Friend

Merge branch 'add-tabs-to-ci-cd-charts' into 'master'

[RUN-AS-IF-FOSS] Add Tabs to CI/CD Charts

See merge request gitlab-org/gitlab!51517
parents 16fb5b0e d8bc495f
<script> <script>
import dateFormat from 'dateformat'; import { GlAlert, GlTabs, GlTab } from '@gitlab/ui';
import { GlColumnChart } from '@gitlab/ui/dist/charts'; import { s__ } from '~/locale';
import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql'; import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql'; import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
import StatisticsList from './statistics_list.vue'; import PipelineCharts from './pipeline_charts.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
import { import {
CHART_CONTAINER_HEIGHT,
CHART_DATE_FORMAT,
DEFAULT, DEFAULT,
INNER_CHART_HEIGHT,
LOAD_ANALYTICS_FAILURE, LOAD_ANALYTICS_FAILURE,
LOAD_PIPELINES_FAILURE, LOAD_PIPELINES_FAILURE,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
PARSE_FAILURE, PARSE_FAILURE,
UNSUPPORTED_DATA, UNSUPPORTED_DATA,
X_AXIS_LABEL_ROTATION,
X_AXIS_TITLE_OFFSET,
} from '../constants'; } from '../constants';
const defaultCountValues = {
totalPipelines: {
count: 0,
},
successfulPipelines: {
count: 0,
},
};
const defaultAnalyticsValues = { const defaultAnalyticsValues = {
weekPipelinesTotals: [], weekPipelinesTotals: [],
weekPipelinesLabels: [], weekPipelinesLabels: [],
...@@ -47,36 +27,40 @@ const defaultAnalyticsValues = { ...@@ -47,36 +27,40 @@ const defaultAnalyticsValues = {
pipelineTimesValues: [], pipelineTimesValues: [],
}; };
const defaultCountValues = {
totalPipelines: {
count: 0,
},
successfulPipelines: {
count: 0,
},
};
export default { export default {
components: { components: {
GlAlert, GlAlert,
GlColumnChart, GlTabs,
GlSkeletonLoader, GlTab,
StatisticsList, PipelineCharts,
CiCdAnalyticsAreaChart,
DeploymentFrequencyCharts: () => DeploymentFrequencyCharts: () =>
import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'),
}, },
inject: { inject: {
projectPath: {
type: String,
default: '',
},
shouldRenderDeploymentFrequencyCharts: { shouldRenderDeploymentFrequencyCharts: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
projectPath: {
type: String,
default: '',
},
}, },
data() { data() {
return { return {
counts: {
...defaultCountValues,
},
analytics: {
...defaultAnalyticsValues,
},
showFailureAlert: false, showFailureAlert: false,
failureType: null, failureType: null,
analytics: { ...defaultAnalyticsValues },
counts: { ...defaultCountValues },
}; };
}, },
apollo: { apollo: {
...@@ -134,41 +118,6 @@ export default { ...@@ -134,41 +118,6 @@ export default {
}; };
} }
}, },
successRatio() {
const { successfulPipelines, failedPipelines } = this.counts;
const successfulCount = successfulPipelines?.count;
const failedCount = failedPipelines?.count;
const ratio = (successfulCount / (successfulCount + failedCount)) * 100;
return failedCount === 0 ? 100 : ratio;
},
formattedCounts() {
const { totalPipelines, successfulPipelines, failedPipelines } = this.counts;
return {
total: totalPipelines?.count,
success: successfulPipelines?.count,
failed: failedPipelines?.count,
successRatio: this.successRatio,
};
},
areaCharts() {
const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
let areaChartsData = [];
try {
areaChartsData = [
this.buildAreaChartData(lastWeek, this.lastWeekChartData),
this.buildAreaChartData(lastMonth, this.lastMonthChartData),
this.buildAreaChartData(lastYear, this.lastYearChartData),
];
} catch {
areaChartsData = [];
this.reportFailure(PARSE_FAILURE);
}
return areaChartsData;
},
lastWeekChartData() { lastWeekChartData() {
return { return {
labels: this.analytics.weekPipelinesLabels, labels: this.analytics.weekPipelinesLabels,
...@@ -190,39 +139,32 @@ export default { ...@@ -190,39 +139,32 @@ export default {
success: this.analytics.yearPipelinesSuccessful, success: this.analytics.yearPipelinesSuccessful,
}; };
}, },
timesChartTransformedData() { timesChartData() {
return [ return {
{ labels: this.analytics.pipelineTimesLabels,
name: 'full', values: this.analytics.pipelineTimesValues,
data: this.mergeLabelsAndValues( };
this.analytics.pipelineTimesLabels,
this.analytics.pipelineTimesValues,
),
},
];
},
}, },
methods: { successRatio() {
mergeLabelsAndValues(labels, values) { const { successfulPipelines, failedPipelines } = this.counts;
return labels.map((label, index) => [label, values[index]]); const successfulCount = successfulPipelines?.count;
const failedCount = failedPipelines?.count;
const ratio = (successfulCount / (successfulCount + failedCount)) * 100;
return failedCount === 0 ? 100 : ratio;
}, },
buildAreaChartData(title, data) { formattedCounts() {
const { labels, totals, success } = data; const { totalPipelines, successfulPipelines, failedPipelines } = this.counts;
return { return {
title, total: totalPipelines?.count,
data: [ success: successfulPipelines?.count,
{ failed: failedPipelines?.count,
name: 'all', successRatio: this.successRatio,
data: this.mergeLabelsAndValues(labels, totals),
},
{
name: 'success',
data: this.mergeLabelsAndValues(labels, success),
},
],
}; };
}, },
},
methods: {
hideAlert() { hideAlert() {
this.showFailureAlert = false; this.showFailureAlert = false;
}, },
...@@ -231,16 +173,6 @@ export default { ...@@ -231,16 +173,6 @@ export default {
this.failureType = type; this.failureType = type;
}, },
}, },
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
height: INNER_CHART_HEIGHT,
xAxis: {
axisLabel: {
rotate: X_AXIS_LABEL_ROTATION,
},
nameGap: X_AXIS_TITLE_OFFSET,
},
},
errorTexts: { errorTexts: {
[LOAD_ANALYTICS_FAILURE]: s__( [LOAD_ANALYTICS_FAILURE]: s__(
'PipelineCharts|An error has ocurred when retrieving the analytics data', 'PipelineCharts|An error has ocurred when retrieving the analytics data',
...@@ -251,74 +183,38 @@ export default { ...@@ -251,74 +183,38 @@ export default {
[PARSE_FAILURE]: s__('PipelineCharts|There was an error parsing the data for the charts.'), [PARSE_FAILURE]: s__('PipelineCharts|There was an error parsing the data for the charts.'),
[DEFAULT]: s__('PipelineCharts|An unknown error occurred while processing CI/CD analytics.'), [DEFAULT]: s__('PipelineCharts|An unknown error occurred while processing CI/CD analytics.'),
}, },
get chartTitles() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = (timeScale) =>
dateFormat(getDateInPast(new Date(), timeScale), CHART_DATE_FORMAT);
return {
lastWeek: sprintf(__('Pipelines for last week (%{oneWeekAgo} - %{today})'), {
oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS),
today,
}),
lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), {
oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS),
today,
}),
lastYear: __('Pipelines for last year'),
};
},
areaChartOptions: {
xAxis: {
name: s__('Pipeline|Date'),
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert"> <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">{{
{{ failure.text }} failure.text
</gl-alert> }}</gl-alert>
<div class="gl-mb-3"> <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3> <gl-tab :title="__('Pipelines')">
</div> <pipeline-charts
<h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4> :counts="formattedCounts"
<div class="row"> :last-week="lastWeekChartData"
<div class="col-md-6"> :last-month="lastMonthChartData"
<gl-skeleton-loader v-if="$apollo.queries.counts.loading" :lines="5" /> :last-year="lastYearChartData"
<statistics-list v-else :counts="formattedCounts" /> :times-chart="timesChartData"
</div> :loading="$apollo.queries.counts.loading"
<div class="col-md-6"> @report-failure="reportFailure"
<strong>
{{ __('Duration for the last 30 commits') }}
</strong>
<gl-column-chart
:height="$options.chartContainerHeight"
:option="$options.timesChartOptions"
:bars="timesChartTransformedData"
:y-axis-title="__('Minutes')"
:x-axis-title="__('Commit')"
x-axis-type="category"
/> />
</div> </gl-tab>
</div> <gl-tab :title="__('Deployments')">
<hr />
<h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
<ci-cd-analytics-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>
{{ chart.title }}
</ci-cd-analytics-area-chart>
<template v-if="shouldRenderDeploymentFrequencyCharts">
<hr />
<deployment-frequency-charts /> <deployment-frequency-charts />
</template> </gl-tab>
</gl-tabs>
<pipeline-charts
v-else
:counts="formattedCounts"
:last-week="lastWeekChartData"
:last-month="lastMonthChartData"
:last-year="lastYearChartData"
:times-chart="timesChartData"
:loading="$apollo.queries.counts.loading"
@report-failure="reportFailure"
/>
</div> </div>
</template> </template>
<script> <script>
import dateFormat from 'dateformat'; import { GlTabs, GlTab } from '@gitlab/ui';
import { GlColumnChart } from '@gitlab/ui/dist/charts'; import PipelineCharts from './pipeline_charts.vue';
import { __, s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import StatisticsList from './statistics_list.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
import {
CHART_CONTAINER_HEIGHT,
INNER_CHART_HEIGHT,
X_AXIS_LABEL_ROTATION,
X_AXIS_TITLE_OFFSET,
CHART_DATE_FORMAT,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
} from '../constants';
export default { export default {
components: { components: {
StatisticsList, GlTabs,
GlColumnChart, GlTab,
CiCdAnalyticsAreaChart, PipelineCharts,
DeploymentFrequencyCharts: () => DeploymentFrequencyCharts: () =>
import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'),
}, },
...@@ -54,121 +40,41 @@ export default { ...@@ -54,121 +40,41 @@ export default {
}, },
data() { data() {
return { return {
timesChartTransformedData: [ // this loading flag gives the echarts library just enough time
{ // to ensure all DOM nodes have been mounted.
name: 'full', //
data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values), // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1131
}, loading: true,
],
};
},
computed: {
areaCharts() {
const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
return [
this.buildAreaChartData(lastWeek, this.lastWeekChartData),
this.buildAreaChartData(lastMonth, this.lastMonthChartData),
this.buildAreaChartData(lastYear, this.lastYearChartData),
];
},
},
methods: {
mergeLabelsAndValues(labels, values) {
return labels.map((label, index) => [label, values[index]]);
},
buildAreaChartData(title, data) {
const { labels, totals, success } = data;
return {
title,
data: [
{
name: 'all',
data: this.mergeLabelsAndValues(labels, totals),
},
{
name: 'success',
data: this.mergeLabelsAndValues(labels, success),
},
],
};
},
},
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
height: INNER_CHART_HEIGHT,
xAxis: {
axisLabel: {
rotate: X_AXIS_LABEL_ROTATION,
},
nameGap: X_AXIS_TITLE_OFFSET,
},
},
get chartTitles() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = (timeScale) =>
dateFormat(getDateInPast(new Date(), timeScale), CHART_DATE_FORMAT);
return {
lastWeek: sprintf(__('Pipelines for last week (%{oneWeekAgo} - %{today})'), {
oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS),
today,
}),
lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), {
oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS),
today,
}),
lastYear: __('Pipelines for last year'),
}; };
}, },
areaChartOptions: { async mounted() {
xAxis: { await this.$nextTick();
name: s__('Pipeline|Date'), this.loading = false;
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
},
}, },
}; };
</script> </script>
<template> <template>
<div> <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts">
<div class="mb-3"> <gl-tab :title="__('Pipelines')">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3> <pipeline-charts
</div> :counts="counts"
<h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4> :last-week="lastWeekChartData"
<div class="row"> :last-month="lastMonthChartData"
<div class="col-md-6"> :last-year="lastYearChartData"
<statistics-list :counts="counts" /> :times-chart="timesChartData"
</div> :loading="loading"
<div class="col-md-6">
<strong>
{{ __('Duration for the last 30 commits') }}
</strong>
<gl-column-chart
:height="$options.chartContainerHeight"
:option="$options.timesChartOptions"
:bars="timesChartTransformedData"
:y-axis-title="__('Minutes')"
:x-axis-title="__('Commit')"
x-axis-type="category"
/> />
</div> </gl-tab>
</div> <gl-tab :title="__('Deployments')">
<hr />
<h4 class="my-4">{{ __('Pipelines charts') }}</h4>
<ci-cd-analytics-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>
{{ chart.title }}
</ci-cd-analytics-area-chart>
<template v-if="shouldRenderDeploymentFrequencyCharts">
<hr />
<deployment-frequency-charts /> <deployment-frequency-charts />
</template> </gl-tab>
</div> </gl-tabs>
<pipeline-charts
v-else
:counts="counts"
:last-week="lastWeekChartData"
:last-month="lastMonthChartData"
:last-year="lastYearChartData"
:times-chart="timesChartData"
/>
</template> </template>
<script>
import dateFormat from 'dateformat';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import { GlSkeletonLoader } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import {
CHART_CONTAINER_HEIGHT,
CHART_DATE_FORMAT,
INNER_CHART_HEIGHT,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
X_AXIS_LABEL_ROTATION,
X_AXIS_TITLE_OFFSET,
PARSE_FAILURE,
} from '../constants';
import StatisticsList from './statistics_list.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
export default {
components: {
GlColumnChart,
GlSkeletonLoader,
StatisticsList,
CiCdAnalyticsAreaChart,
},
props: {
counts: {
required: true,
type: Object,
},
loading: {
required: false,
default: false,
type: Boolean,
},
lastWeek: {
required: true,
type: Object,
},
lastMonth: {
required: true,
type: Object,
},
lastYear: {
required: true,
type: Object,
},
timesChart: {
required: true,
type: Object,
},
},
computed: {
areaCharts() {
const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
const charts = [
{ title: lastWeek, data: this.lastWeek },
{ title: lastMonth, data: this.lastMonth },
{ title: lastYear, data: this.lastYear },
];
let areaChartsData = [];
try {
areaChartsData = charts.map(this.buildAreaChartData);
} catch {
areaChartsData = [];
this.vm.$emit('report-failure', PARSE_FAILURE);
}
return areaChartsData;
},
timesChartTransformedData() {
return [
{
name: 'full',
data: this.mergeLabelsAndValues(this.timesChart.labels, this.timesChart.values),
},
];
},
},
methods: {
mergeLabelsAndValues(labels, values) {
return labels.map((label, index) => [label, values[index]]);
},
buildAreaChartData({ title, data }) {
const { labels, totals, success } = data;
return {
title,
data: [
{
name: 'all',
data: this.mergeLabelsAndValues(labels, totals),
},
{
name: 'success',
data: this.mergeLabelsAndValues(labels, success),
},
],
};
},
},
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
height: INNER_CHART_HEIGHT,
xAxis: {
axisLabel: {
rotate: X_AXIS_LABEL_ROTATION,
},
nameGap: X_AXIS_TITLE_OFFSET,
},
},
areaChartOptions: {
xAxis: {
name: s__('Pipeline|Date'),
type: 'category',
},
yAxis: {
name: s__('Pipeline|Pipelines'),
},
},
get chartTitles() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = (timeScale) =>
dateFormat(getDateInPast(new Date(), timeScale), CHART_DATE_FORMAT);
return {
lastWeek: sprintf(__('Pipelines for last week (%{oneWeekAgo} - %{today})'), {
oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS),
today,
}),
lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), {
oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS),
today,
}),
lastYear: __('Pipelines for last year'),
};
},
};
</script>
<template>
<div>
<div class="gl-mb-3">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
</div>
<h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
<div class="row">
<div class="col-md-6">
<gl-skeleton-loader v-if="loading" :lines="5" />
<statistics-list v-else :counts="counts" />
</div>
<div v-if="!loading" class="col-md-6">
<strong>{{ __('Duration for the last 30 commits') }}</strong>
<gl-column-chart
:height="$options.chartContainerHeight"
:option="$options.timesChartOptions"
:bars="timesChartTransformedData"
:y-axis-title="__('Minutes')"
:x-axis-title="__('Commit')"
x-axis-type="category"
/>
</div>
</div>
<template v-if="!loading">
<hr />
<h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
<ci-cd-analytics-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
:chart-data="chart.data"
:area-chart-options="$options.areaChartOptions"
>{{ chart.title }}</ci-cd-analytics-area-chart
>
</template>
</div>
</template>
...@@ -9697,6 +9697,9 @@ msgstr "" ...@@ -9697,6 +9697,9 @@ msgstr ""
msgid "DeploymentFrequencyCharts|Something went wrong while getting deployment frequency data" msgid "DeploymentFrequencyCharts|Something went wrong while getting deployment frequency data"
msgstr "" msgstr ""
msgid "Deployments"
msgstr ""
msgid "Deployment|API" msgid "Deployment|API"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import Component from '~/projects/pipelines/charts/components/app_legacy.vue'; import Component from '~/projects/pipelines/charts/components/app_legacy.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import { import {
counts, counts,
timesChartData, timesChartData,
...@@ -38,41 +36,17 @@ describe('ProjectsPipelinesChartsApp', () => { ...@@ -38,41 +36,17 @@ describe('ProjectsPipelinesChartsApp', () => {
wrapper = null; wrapper = null;
}); });
describe('overall statistics', () => {
it('displays the statistics list', () => {
const list = wrapper.find(StatisticsList);
expect(list.exists()).toBeTruthy();
expect(list.props('counts')).toBe(counts);
});
it('displays the commit duration chart', () => {
const chart = wrapper.find(GlColumnChart);
expect(chart.exists()).toBeTruthy();
expect(chart.props('yAxisTitle')).toBe('Minutes');
expect(chart.props('xAxisTitle')).toBe('Commit');
expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions);
});
});
describe('pipelines charts', () => { describe('pipelines charts', () => {
it('displays 3 area charts', () => { it('displays the pipeline charts', () => {
expect(wrapper.findAll(CiCdAnalyticsAreaChart).length).toBe(3); const chart = wrapper.find(PipelineCharts);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
for (let i = 0; i < charts.length; i += 1) { expect(chart.exists()).toBe(true);
const chart = charts.at(i); expect(chart.props()).toMatchObject({
counts,
expect(chart.exists()).toBeTruthy(); lastWeek: lastWeekChartData,
expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data); lastMonth: lastMonthChartData,
expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title); lastYear: lastYearChartData,
} timesChart: timesChartData,
}); });
}); });
}); });
......
import { merge } from 'lodash'; import { merge } from 'lodash';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { GlTabs, GlTab } from '@gitlab/ui';
import createMockApollo from 'jest/helpers/mock_apollo_helper'; 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 Component from '~/projects/pipelines/charts/components/app.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.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 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 getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql';
import { mockPipelineCount, mockPipelineStatistics } from '../mock_data'; import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
...@@ -58,60 +57,62 @@ describe('ProjectsPipelinesChartsApp', () => { ...@@ -58,60 +57,62 @@ describe('ProjectsPipelinesChartsApp', () => {
wrapper = null; wrapper = null;
}); });
describe('overall statistics', () => {
it('displays the statistics list', () => {
const list = wrapper.find(StatisticsList);
expect(list.exists()).toBe(true);
expect(list.props('counts')).toMatchObject({
failed: 1,
success: 23,
total: 34,
successRatio: 95.83333333333334,
});
});
it('displays the commit duration chart', () => {
const chart = wrapper.find(GlColumnChart);
expect(chart.exists()).toBe(true);
expect(chart.props('yAxisTitle')).toBe('Minutes');
expect(chart.props('xAxisTitle')).toBe('Commit');
expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions);
});
});
describe('pipelines charts', () => { describe('pipelines charts', () => {
it('displays 3 area charts', () => { it('displays the pipeline charts', () => {
expect(wrapper.findAll(CiCdAnalyticsAreaChart)).toHaveLength(3); const chart = wrapper.find(PipelineCharts);
}); const analytics = mockPipelineStatistics.data.project.pipelineAnalytics;
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
for (let i = 0; i < charts.length; i += 1) { const {
const chart = charts.at(i); totalPipelines: total,
successfulPipelines: success,
failedPipelines: failed,
} = mockPipelineCount.data.project;
expect(chart.exists()).toBe(true); expect(chart.exists()).toBe(true);
// TODO: Refactor this to use the mocked data instead of the vm data expect(chart.props()).toMatchObject({
// https://gitlab.com/gitlab-org/gitlab/-/issues/292085 counts: {
expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data); failed: failed.count,
expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title); success: success.count,
} total: total.count,
successRatio: (success.count / (success.count + failed.count)) * 100,
},
lastWeek: {
labels: analytics.weekPipelinesLabels,
totals: analytics.weekPipelinesTotals,
success: analytics.weekPipelinesSuccessful,
},
lastMonth: {
labels: analytics.monthPipelinesLabels,
totals: analytics.monthPipelinesTotals,
success: analytics.monthPipelinesSuccessful,
},
lastYear: {
labels: analytics.yearPipelinesLabels,
totals: analytics.yearPipelinesTotals,
success: analytics.yearPipelinesSuccessful,
},
timesChart: {
labels: analytics.pipelineTimesLabels,
values: analytics.pipelineTimesValues,
},
}); });
}); });
}); });
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub); const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
const findGlTabs = () => wrapper.find(GlTabs);
const findAllGlTab = () => wrapper.findAll(GlTab);
const findGlTabAt = (i) => findAllGlTab().at(i);
describe('when shouldRenderDeploymentFrequencyCharts is true', () => { describe('when shouldRenderDeploymentFrequencyCharts is true', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } }); createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } });
}); });
it('renders the deployment frequency charts', () => { it('renders the deployment frequency charts in a tab', () => {
expect(findGlTabs().exists()).toBe(true);
expect(findGlTabAt(0).attributes('title')).toBe('Pipelines');
expect(findGlTabAt(1).attributes('title')).toBe('Deployments');
expect(findDeploymentFrequencyCharts().exists()).toBe(true); expect(findDeploymentFrequencyCharts().exists()).toBe(true);
}); });
}); });
...@@ -121,7 +122,8 @@ describe('ProjectsPipelinesChartsApp', () => { ...@@ -121,7 +122,8 @@ describe('ProjectsPipelinesChartsApp', () => {
createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: false } }); createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: false } });
}); });
it('does not render the deployment frequency charts', () => { it('does not render the deployment frequency charts in a tab', () => {
expect(findGlTabs().exists()).toBe(false);
expect(findDeploymentFrequencyCharts().exists()).toBe(false); expect(findDeploymentFrequencyCharts().exists()).toBe(false);
}); });
}); });
......
import { shallowMount } from '@vue/test-utils';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue';
import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
import {
counts,
timesChartData as timesChart,
areaChartData as lastWeek,
areaChartData as lastMonth,
lastYearChartData as lastYear,
} from '../mock_data';
describe('ProjectsPipelinesChartsApp', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(PipelineCharts, {
propsData: {
counts,
timesChart,
lastWeek,
lastMonth,
lastYear,
},
provide: {
projectPath: 'test/project',
shouldRenderDeploymentFrequencyCharts: true,
},
stubs: {
DeploymentFrequencyCharts: true,
},
});
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('overall statistics', () => {
it('displays the statistics list', () => {
const list = wrapper.find(StatisticsList);
expect(list.exists()).toBe(true);
expect(list.props('counts')).toBe(counts);
});
it('displays the commit duration chart', () => {
const chart = wrapper.find(GlColumnChart);
expect(chart.exists()).toBeTruthy();
expect(chart.props('yAxisTitle')).toBe('Minutes');
expect(chart.props('xAxisTitle')).toBe('Commit');
expect(chart.props('bars')).toBe(wrapper.vm.timesChartTransformedData);
expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions);
});
});
describe('pipelines charts', () => {
it('displays 3 area charts', () => {
expect(wrapper.findAll(CiCdAnalyticsAreaChart)).toHaveLength(3);
});
describe('displays individual correctly', () => {
it('renders with the correct data', () => {
const charts = wrapper.findAll(CiCdAnalyticsAreaChart);
for (let i = 0; i < charts.length; i += 1) {
const chart = charts.at(i);
expect(chart.exists()).toBeTruthy();
expect(chart.props('chartData')).toBe(wrapper.vm.areaCharts[i].data);
expect(chart.text()).toBe(wrapper.vm.areaCharts[i].title);
}
});
});
});
});
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