debian-mirror-gitlab/app/assets/javascripts/analytics/instance_statistics/components/pipelines_chart.vue

216 lines
6.1 KiB
Vue

<script>
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
import { mapKeys, mapValues, pick, some, sum } from 'lodash';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { s__ } from '~/locale';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import { getAverageByMonth, sortByDate, extractValues } from '../utils';
import pipelineStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
import { TODAY, START_DATE } from '../constants';
const DATA_KEYS = [
'pipelinesTotal',
'pipelinesSucceeded',
'pipelinesFailed',
'pipelinesCanceled',
'pipelinesSkipped',
];
const PREFIX = 'pipelines';
export default {
name: 'PipelinesChart',
components: {
GlLineChart,
GlAlert,
ChartSkeletonLoader,
},
startDate: START_DATE,
endDate: TODAY,
i18n: {
loadPipelineChartError: s__(
'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.',
),
noDataMessage: s__('InstanceAnalytics|There is no data available.'),
total: s__('InstanceAnalytics|Total'),
succeeded: s__('InstanceAnalytics|Succeeded'),
failed: s__('InstanceAnalytics|Failed'),
canceled: s__('InstanceAnalytics|Canceled'),
skipped: s__('InstanceAnalytics|Skipped'),
chartTitle: s__('InstanceAnalytics|Pipelines'),
yAxisTitle: s__('InstanceAnalytics|Items'),
xAxisTitle: s__('InstanceAnalytics|Month'),
},
data() {
return {
loading: true,
loadingError: null,
};
},
apollo: {
pipelineStats: {
query: pipelineStatsQuery,
variables() {
return {
firstTotal: this.totalDaysToShow,
firstSucceeded: this.totalDaysToShow,
firstFailed: this.totalDaysToShow,
firstCanceled: this.totalDaysToShow,
firstSkipped: this.totalDaysToShow,
};
},
update(data) {
const allData = extractValues(data, DATA_KEYS, PREFIX, 'nodes');
const allPageInfo = extractValues(data, DATA_KEYS, PREFIX, 'pageInfo');
return {
...mapValues(allData, sortByDate),
...allPageInfo,
};
},
result() {
if (this.hasNextPage) {
this.fetchNextPage();
}
},
error() {
this.handleError();
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.pipelineStats.loading;
},
totalDaysToShow() {
return getDayDifference(this.$options.startDate, this.$options.endDate);
},
firstVariables() {
const allData = pick(this.pipelineStats, [
'nodesTotal',
'nodesSucceeded',
'nodesFailed',
'nodesCanceled',
'nodesSkipped',
]);
const allDayDiffs = mapValues(allData, data => {
const firstdataPoint = data[0];
if (!firstdataPoint) {
return 0;
}
return Math.max(
0,
getDayDifference(this.$options.startDate, new Date(firstdataPoint.recordedAt)),
);
});
return mapKeys(allDayDiffs, (value, key) => key.replace('nodes', 'first'));
},
cursorVariables() {
const pageInfoKeys = [
'pageInfoTotal',
'pageInfoSucceeded',
'pageInfoFailed',
'pageInfoCanceled',
'pageInfoSkipped',
];
return extractValues(this.pipelineStats, pageInfoKeys, 'pageInfo', 'endCursor');
},
hasNextPage() {
return (
sum(Object.values(this.firstVariables)) > 0 &&
some(this.pipelineStats, ({ hasNextPage }) => hasNextPage)
);
},
hasEmptyDataSet() {
return this.chartData.every(({ data }) => data.length === 0);
},
chartData() {
const allData = pick(this.pipelineStats, [
'nodesTotal',
'nodesSucceeded',
'nodesFailed',
'nodesCanceled',
'nodesSkipped',
]);
const options = { shouldRound: true };
return Object.keys(allData).map(key => {
const i18nName = key.slice('nodes'.length).toLowerCase();
return {
name: this.$options.i18n[i18nName],
data: getAverageByMonth(allData[key], options),
};
});
},
range() {
return {
min: this.$options.startDate,
max: this.$options.endDate,
};
},
chartOptions() {
const { endDate, startDate, i18n } = this.$options;
return {
xAxis: {
...this.range,
name: i18n.xAxisTitle,
type: 'time',
splitNumber: differenceInMonths(startDate, endDate) + 1,
axisLabel: {
interval: 0,
showMinLabel: false,
showMaxLabel: false,
align: 'right',
formatter: formatDateAsMonth,
},
},
yAxis: {
name: i18n.yAxisTitle,
},
};
},
},
methods: {
handleError() {
this.loadingError = true;
},
fetchNextPage() {
this.$apollo.queries.pipelineStats
.fetchMore({
variables: {
...this.firstVariables,
...this.cursorVariables,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
return Object.keys(fetchMoreResult).reduce((memo, key) => {
const { nodes, ...rest } = fetchMoreResult[key];
const previousNodes = previousResult[key].nodes;
return { ...memo, [key]: { ...rest, nodes: [...previousNodes, ...nodes] } };
}, {});
},
})
.catch(this.handleError);
},
},
};
</script>
<template>
<div>
<h3>{{ $options.i18n.chartTitle }}</h3>
<gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
{{ this.$options.i18n.loadPipelineChartError }}
</gl-alert>
<chart-skeleton-loader v-else-if="isLoading" />
<gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.noDataMessage }}
</gl-alert>
<gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" />
</div>
</template>