summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/projects/pipelines/charts/components/app.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/projects/pipelines/charts/components/app.vue')
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue234
1 files changed, 196 insertions, 38 deletions
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
index c6e2b2e1140..4bf837faed1 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -1,66 +1,203 @@
<script>
import dateFormat from 'dateformat';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { __, sprintf } 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 getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
import StatisticsList from './statistics_list.vue';
import PipelinesAreaChart from './pipelines_area_chart.vue';
import {
CHART_CONTAINER_HEIGHT,
- INNER_CHART_HEIGHT,
- X_AXIS_LABEL_ROTATION,
- X_AXIS_TITLE_OFFSET,
CHART_DATE_FORMAT,
+ DEFAULT,
+ INNER_CHART_HEIGHT,
+ LOAD_ANALYTICS_FAILURE,
+ LOAD_PIPELINES_FAILURE,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
+ PARSE_FAILURE,
+ UNSUPPORTED_DATA,
+ X_AXIS_LABEL_ROTATION,
+ X_AXIS_TITLE_OFFSET,
} from '../constants';
+const defaultCountValues = {
+ totalPipelines: {
+ count: 0,
+ },
+ successfulPipelines: {
+ count: 0,
+ },
+};
+
+const defaultAnalyticsValues = {
+ weekPipelinesTotals: [],
+ weekPipelinesLabels: [],
+ weekPipelinesSuccessful: [],
+ monthPipelinesLabels: [],
+ monthPipelinesTotals: [],
+ monthPipelinesSuccessful: [],
+ yearPipelinesLabels: [],
+ yearPipelinesTotals: [],
+ yearPipelinesSuccessful: [],
+ pipelineTimesLabels: [],
+ pipelineTimesValues: [],
+};
+
export default {
components: {
- StatisticsList,
+ GlAlert,
GlColumnChart,
+ GlSkeletonLoader,
+ StatisticsList,
PipelinesAreaChart,
},
- props: {
- counts: {
- type: Object,
- required: true,
- },
- timesChartData: {
- type: Object,
- required: true,
- },
- lastWeekChartData: {
- type: Object,
- required: true,
- },
- lastMonthChartData: {
- type: Object,
- required: true,
- },
- lastYearChartData: {
- type: Object,
- required: true,
+ inject: {
+ projectPath: {
+ type: String,
+ default: '',
},
},
data() {
return {
- timesChartTransformedData: [
- {
- name: 'full',
- data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values),
- },
- ],
+ counts: {
+ ...defaultCountValues,
+ },
+ analytics: {
+ ...defaultAnalyticsValues,
+ },
+ showFailureAlert: false,
+ failureType: null,
};
},
+ apollo: {
+ counts: {
+ query: getPipelineCountByStatus,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project;
+ },
+ error() {
+ this.reportFailure(LOAD_PIPELINES_FAILURE);
+ },
+ },
+ analytics: {
+ query: getProjectPipelineStatistics,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project?.pipelineAnalytics;
+ },
+ error() {
+ this.reportFailure(LOAD_ANALYTICS_FAILURE);
+ },
+ },
+ },
computed: {
+ failure() {
+ switch (this.failureType) {
+ case LOAD_ANALYTICS_FAILURE:
+ return {
+ text: this.$options.errorTexts[LOAD_ANALYTICS_FAILURE],
+ variant: 'danger',
+ };
+ case PARSE_FAILURE:
+ return {
+ text: this.$options.errorTexts[PARSE_FAILURE],
+ variant: 'danger',
+ };
+ case UNSUPPORTED_DATA:
+ return {
+ text: this.$options.errorTexts[UNSUPPORTED_DATA],
+ variant: 'info',
+ };
+ default:
+ return {
+ text: this.$options.errorTexts[DEFAULT],
+ variant: 'danger',
+ };
+ }
+ },
+ 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,
+ totalPipelineDuration,
+ } = this.counts;
+
+ return {
+ total: totalPipelines?.count,
+ success: successfulPipelines?.count,
+ failed: failedPipelines?.count,
+ successRatio: this.successRatio,
+ totalDuration: totalPipelineDuration,
+ };
+ },
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() {
+ return {
+ labels: this.analytics.weekPipelinesLabels,
+ totals: this.analytics.weekPipelinesTotals,
+ success: this.analytics.weekPipelinesSuccessful,
+ };
+ },
+ lastMonthChartData() {
+ return {
+ labels: this.analytics.monthPipelinesLabels,
+ totals: this.analytics.monthPipelinesTotals,
+ success: this.analytics.monthPipelinesSuccessful,
+ };
+ },
+ lastYearChartData() {
+ return {
+ labels: this.analytics.yearPipelinesLabels,
+ totals: this.analytics.yearPipelinesTotals,
+ success: this.analytics.yearPipelinesSuccessful,
+ };
+ },
+ timesChartTransformedData() {
return [
- this.buildAreaChartData(lastWeek, this.lastWeekChartData),
- this.buildAreaChartData(lastMonth, this.lastMonthChartData),
- this.buildAreaChartData(lastYear, this.lastYearChartData),
+ {
+ name: 'full',
+ data: this.mergeLabelsAndValues(
+ this.analytics.pipelineTimesLabels,
+ this.analytics.pipelineTimesValues,
+ ),
+ },
];
},
},
@@ -85,6 +222,13 @@ export default {
],
};
},
+ hideAlert() {
+ this.showFailureAlert = false;
+ },
+ reportFailure(type) {
+ this.showFailureAlert = true;
+ this.failureType = type;
+ },
},
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
@@ -96,6 +240,16 @@ export default {
nameGap: X_AXIS_TITLE_OFFSET,
},
},
+ errorTexts: {
+ [LOAD_ANALYTICS_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the analytics data',
+ ),
+ [LOAD_PIPELINES_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the pipelines data',
+ ),
+ [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.'),
+ },
get chartTitles() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = timeScale =>
@@ -116,13 +270,17 @@ export default {
</script>
<template>
<div>
- <div class="mb-3">
+ <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">
+ {{ failure.text }}
+ </gl-alert>
+ <div class="gl-mb-3">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
</div>
- <h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
+ <h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
<div class="row">
<div class="col-md-6">
- <statistics-list :counts="counts" />
+ <gl-skeleton-loader v-if="$apollo.queries.counts.loading" :lines="5" />
+ <statistics-list v-else :counts="formattedCounts" />
</div>
<div class="col-md-6">
<strong>
@@ -139,7 +297,7 @@ export default {
</div>
</div>
<hr />
- <h4 class="my-4">{{ __('Pipelines charts') }}</h4>
+ <h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
<pipelines-area-chart
v-for="(chart, index) in areaCharts"
:key="index"