summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/projects/pipelines
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
commit859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch)
treed7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/projects/pipelines
parent446d496a6d000c73a304be52587cd9bbc7493136 (diff)
downloadgitlab-ce-859a6fb938bb9ee2a317c46dfa4fcc1af49608f0.tar.gz
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects/pipelines')
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue203
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue50
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue233
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/constants.js2
4 files changed, 262 insertions, 226 deletions
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
index 7bb62cf4a73..4a8e1424fa8 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -1,44 +1,12 @@
<script>
-import { GlAlert, GlTabs, GlTab } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
-import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
+import { GlTabs, GlTab } from '@gitlab/ui';
+import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
import PipelineCharts from './pipeline_charts.vue';
-import {
- DEFAULT,
- LOAD_ANALYTICS_FAILURE,
- LOAD_PIPELINES_FAILURE,
- PARSE_FAILURE,
- UNSUPPORTED_DATA,
-} from '../constants';
-
-const defaultAnalyticsValues = {
- weekPipelinesTotals: [],
- weekPipelinesLabels: [],
- weekPipelinesSuccessful: [],
- monthPipelinesLabels: [],
- monthPipelinesTotals: [],
- monthPipelinesSuccessful: [],
- yearPipelinesLabels: [],
- yearPipelinesTotals: [],
- yearPipelinesSuccessful: [],
- pipelineTimesLabels: [],
- pipelineTimesValues: [],
-};
-
-const defaultCountValues = {
- totalPipelines: {
- count: 0,
- },
- successfulPipelines: {
- count: 0,
- },
-};
+const charts = ['pipelines', 'deployments'];
export default {
components: {
- GlAlert,
GlTabs,
GlTab,
PipelineCharts,
@@ -50,171 +18,42 @@ export default {
type: Boolean,
default: false,
},
- projectPath: {
- type: String,
- default: '',
- },
},
data() {
return {
- showFailureAlert: false,
- failureType: null,
- analytics: { ...defaultAnalyticsValues },
- counts: { ...defaultCountValues },
+ selectedTab: 0,
};
},
- 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',
- };
- }
- },
- 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,
- };
- },
- 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,
- };
- },
+ created() {
+ this.selectTab();
+ window.addEventListener('popstate', this.selectTab);
},
methods: {
- hideAlert() {
- this.showFailureAlert = false;
- },
- reportFailure(type) {
- this.showFailureAlert = true;
- this.failureType = type;
+ selectTab() {
+ const [chart] = getParameterValues('chart') || charts;
+ const tab = charts.indexOf(chart);
+ this.selectedTab = tab >= 0 ? tab : 0;
+ },
+ onTabChange(index) {
+ if (index !== this.selectedTab) {
+ this.selectedTab = index;
+ const path = mergeUrlParams({ chart: charts[index] }, window.location.pathname);
+ updateHistory({ url: path, title: window.title });
+ }
},
},
- 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.'),
- },
};
</script>
<template>
<div>
- <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">{{
- failure.text
- }}</gl-alert>
- <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts">
+ <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts" :value="selectedTab" @input="onTabChange">
<gl-tab :title="__('Pipelines')">
- <pipeline-charts
- :counts="formattedCounts"
- :last-week="lastWeekChartData"
- :last-month="lastMonthChartData"
- :last-year="lastYearChartData"
- :times-chart="timesChartData"
- :loading="$apollo.queries.counts.loading"
- @report-failure="reportFailure"
- />
+ <pipeline-charts />
</gl-tab>
<gl-tab :title="__('Deployments')">
<deployment-frequency-charts />
</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"
- />
+ <pipeline-charts v-else />
</div>
</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue
new file mode 100644
index 00000000000..43b36da8b2c
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlSegmentedControl } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
+
+export default {
+ components: {
+ GlSegmentedControl,
+ CiCdAnalyticsAreaChart,
+ },
+ props: {
+ charts: {
+ required: true,
+ type: Array,
+ },
+ chartOptions: {
+ required: true,
+ type: Object,
+ },
+ },
+ data() {
+ return {
+ selectedChart: 0,
+ };
+ },
+ computed: {
+ chartRanges() {
+ return this.charts.map(({ title }, index) => ({ text: title, value: index }));
+ },
+ chart() {
+ return this.charts[this.selectedChart];
+ },
+ dateRange() {
+ return sprintf(s__('CiCdAnalytics|Date range: %{range}'), { range: this.chart.range });
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-segmented-control v-model="selectedChart" :options="chartRanges" class="gl-mb-4" />
+ <ci-cd-analytics-area-chart
+ v-if="chart"
+ :chart-data="chart.data"
+ :area-chart-options="chartOptions"
+ >
+ {{ dateRange }}
+ </ci-cd-analytics-area-chart>
+ </div>
+</template>
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>
diff --git a/app/assets/javascripts/projects/pipelines/charts/constants.js b/app/assets/javascripts/projects/pipelines/charts/constants.js
index 079e23943c1..41fe81f21ea 100644
--- a/app/assets/javascripts/projects/pipelines/charts/constants.js
+++ b/app/assets/javascripts/projects/pipelines/charts/constants.js
@@ -10,6 +10,8 @@ export const ONE_WEEK_AGO_DAYS = 7;
export const ONE_MONTH_AGO_DAYS = 31;
+export const ONE_YEAR_AGO_DAYS = 365;
+
export const CHART_DATE_FORMAT = 'dd mmm';
export const DEFAULT = 'default';