diff options
Diffstat (limited to 'app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue')
-rw-r--r-- | app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue | 233 |
1 files changed, 189 insertions, 44 deletions
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue index bec4ab407f0..733f833d51a 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue @@ -1,63 +1,184 @@ <script> -import dateFormat from 'dateformat'; +import { GlAlert, GlSkeletonLoader } from '@gitlab/ui'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; -import { GlSkeletonLoader } from '@gitlab/ui'; -import { __, s__, sprintf } from '~/locale'; +import dateFormat from 'dateformat'; import { getDateInPast } from '~/lib/utils/datetime_utility'; +import { __, s__, sprintf } from '~/locale'; import { + DEFAULT, CHART_CONTAINER_HEIGHT, CHART_DATE_FORMAT, INNER_CHART_HEIGHT, ONE_WEEK_AGO_DAYS, ONE_MONTH_AGO_DAYS, + ONE_YEAR_AGO_DAYS, X_AXIS_LABEL_ROTATION, X_AXIS_TITLE_OFFSET, PARSE_FAILURE, + LOAD_ANALYTICS_FAILURE, + LOAD_PIPELINES_FAILURE, + UNSUPPORTED_DATA, } from '../constants'; +import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql'; +import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql'; +import CiCdAnalyticsCharts from './ci_cd_analytics_charts.vue'; import StatisticsList from './statistics_list.vue'; -import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue'; + +const defaultAnalyticsValues = { + weekPipelinesTotals: [], + weekPipelinesLabels: [], + weekPipelinesSuccessful: [], + monthPipelinesLabels: [], + monthPipelinesTotals: [], + monthPipelinesSuccessful: [], + yearPipelinesLabels: [], + yearPipelinesTotals: [], + yearPipelinesSuccessful: [], + pipelineTimesLabels: [], + pipelineTimesValues: [], +}; + +const defaultCountValues = { + totalPipelines: { + count: 0, + }, + successfulPipelines: { + count: 0, + }, +}; export default { components: { + GlAlert, GlColumnChart, GlSkeletonLoader, StatisticsList, - CiCdAnalyticsAreaChart, + CiCdAnalyticsCharts, + }, + inject: { + projectPath: { + type: String, + default: '', + }, }, - props: { + data() { + return { + showFailureAlert: false, + failureType: null, + analytics: { ...defaultAnalyticsValues }, + counts: { ...defaultCountValues }, + }; + }, + apollo: { counts: { - required: true, - type: Object, + 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: { + loading() { + return this.$apollo.queries.counts.loading; }, - loading: { - required: false, - default: false, - type: Boolean, + 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', + }; + } }, - lastWeek: { - required: true, - type: Object, + lastWeekChartData() { + return { + labels: this.analytics.weekPipelinesLabels, + totals: this.analytics.weekPipelinesTotals, + success: this.analytics.weekPipelinesSuccessful, + }; }, - lastMonth: { - required: true, - type: Object, + lastMonthChartData() { + return { + labels: this.analytics.monthPipelinesLabels, + totals: this.analytics.monthPipelinesTotals, + success: this.analytics.monthPipelinesSuccessful, + }; }, - lastYear: { - required: true, - type: Object, + lastYearChartData() { + return { + labels: this.analytics.yearPipelinesLabels, + totals: this.analytics.yearPipelinesTotals, + success: this.analytics.yearPipelinesSuccessful, + }; }, - timesChart: { - required: true, - type: Object, + timesChartData() { + return { + labels: this.analytics.pipelineTimesLabels, + values: this.analytics.pipelineTimesValues, + }; + }, + 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, + }; }, - }, - computed: { areaCharts() { const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles; + const { lastWeekRange, lastMonthRange, lastYearRange } = this.$options.chartRanges; const charts = [ - { title: lastWeek, data: this.lastWeek }, - { title: lastMonth, data: this.lastMonth }, - { title: lastYear, data: this.lastYear }, + { title: lastWeek, range: lastWeekRange, data: this.lastWeekChartData }, + { title: lastMonth, range: lastMonthRange, data: this.lastMonthChartData }, + { title: lastYear, range: lastYearRange, data: this.lastYearChartData }, ]; let areaChartsData = []; @@ -65,7 +186,7 @@ export default { areaChartsData = charts.map(this.buildAreaChartData); } catch { areaChartsData = []; - this.vm.$emit('report-failure', PARSE_FAILURE); + this.reportFailure(PARSE_FAILURE); } return areaChartsData; @@ -74,20 +195,28 @@ export default { return [ { name: 'full', - data: this.mergeLabelsAndValues(this.timesChart.labels, this.timesChart.values), + data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values), }, ]; }, }, methods: { + hideAlert() { + this.showFailureAlert = false; + }, + reportFailure(type) { + this.showFailureAlert = true; + this.failureType = type; + }, mergeLabelsAndValues(labels, values) { return labels.map((label, index) => [label, values[index]]); }, - buildAreaChartData({ title, data }) { + buildAreaChartData({ title, data, range }) { const { labels, totals, success } = data; return { title, + range, data: [ { name: 'all', @@ -118,28 +247,50 @@ export default { }, yAxis: { name: s__('Pipeline|Pipelines'), + minInterval: 1, }, }, - get chartTitles() { + 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.'), + }, + chartTitles: { + lastWeek: __('Last week'), + lastMonth: __('Last month'), + lastYear: __('Last year'), + }, + get chartRanges() { 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})'), { + lastWeekRange: sprintf(__('%{oneWeekAgo} - %{today}'), { oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS), today, }), - lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), { + lastMonthRange: sprintf(__('%{oneMonthAgo} - %{today}'), { oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS), today, }), - lastYear: __('Pipelines for last year'), + lastYearRange: sprintf(__('%{oneYearAgo} - %{today}'), { + oneYearAgo: pastDate(ONE_YEAR_AGO_DAYS), + today, + }), }; }, }; </script> <template> <div> + <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> @@ -147,7 +298,7 @@ export default { <div class="row"> <div class="col-md-6"> <gl-skeleton-loader v-if="loading" :lines="5" /> - <statistics-list v-else :counts="counts" /> + <statistics-list v-else :counts="formattedCounts" /> </div> <div v-if="!loading" class="col-md-6"> <strong>{{ __('Duration for the last 30 commits') }}</strong> @@ -164,13 +315,7 @@ export default { <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 - > + <ci-cd-analytics-charts :charts="areaCharts" :chart-options="$options.areaChartOptions" /> </template> </div> </template> |