debian-mirror-gitlab/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue
2021-03-08 18:12:59 +05:30

207 lines
5.6 KiB
Vue

<script>
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
import { some, every } from 'lodash';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';
import { TODAY, START_DATE } from '../constants';
const QUERY_DATA_KEY = 'instanceStatisticsMeasurements';
export default {
name: 'InstanceStatisticsCountChart',
components: {
GlLineChart,
GlAlert,
ChartSkeletonLoader,
},
startDate: START_DATE,
endDate: TODAY,
props: {
chartTitle: {
type: String,
required: true,
},
loadChartErrorMessage: {
type: String,
required: true,
},
noDataMessage: {
type: String,
required: true,
},
xAxisTitle: {
type: String,
required: true,
},
yAxisTitle: {
type: String,
required: true,
},
queries: {
type: Array,
required: true,
},
},
data() {
return {
errors: { ...generateDataKeys(this.queries, '') },
...generateDataKeys(this.queries, []),
};
},
computed: {
errorMessages() {
return Object.values(this.errors);
},
isLoading() {
return some(this.$apollo.queries, (query) => query?.loading);
},
allQueriesFailed() {
return every(this.errorMessages, (message) => message.length);
},
hasLoadingErrors() {
return some(this.errorMessages, (message) => message.length);
},
errorMessage() {
// show the generic loading message if all requests fail
return this.allQueriesFailed ? this.loadChartErrorMessage : this.errorMessages.join('\n\n');
},
hasEmptyDataSet() {
return this.chartData.every(({ data }) => data.length === 0);
},
totalDaysToShow() {
return getDayDifference(this.$options.startDate, this.$options.endDate);
},
chartData() {
const options = { shouldRound: true };
return this.queries.map(({ identifier, title }) => ({
name: title,
data: getAverageByMonth(this[identifier]?.nodes, options),
}));
},
range() {
return {
min: this.$options.startDate,
max: this.$options.endDate,
};
},
chartOptions() {
const { endDate, startDate } = this.$options;
return {
xAxis: {
...this.range,
name: this.xAxisTitle,
type: 'time',
splitNumber: differenceInMonths(startDate, endDate) + 1,
axisLabel: {
interval: 0,
showMinLabel: false,
showMaxLabel: false,
align: 'right',
formatter: formatDateAsMonth,
},
},
yAxis: {
name: this.yAxisTitle,
},
};
},
},
created() {
this.queries.forEach(({ query, identifier, loadError }) => {
this.$apollo.addSmartQuery(identifier, {
query,
variables() {
return {
identifier,
first: this.totalDaysToShow,
after: null,
};
},
update(data) {
const { nodes = [], pageInfo } = data[QUERY_DATA_KEY] || {};
return {
nodes,
pageInfo,
};
},
result() {
const { pageInfo, nodes } = this[identifier];
if (pageInfo?.hasNextPage && this.calculateDaysToFetch(getEarliestDate(nodes)) > 0) {
this.fetchNextPage({
query: this.$apollo.queries[identifier],
errorMessage: loadError,
pageInfo,
identifier,
});
}
},
error(error) {
this.handleError({
message: loadError,
identifier,
error,
});
},
});
});
},
methods: {
calculateDaysToFetch(firstDataPointDate = null) {
return firstDataPointDate
? Math.max(0, getDayDifference(this.$options.startDate, new Date(firstDataPointDate)))
: 0;
},
handleError({ identifier, error, message }) {
this.loadingError = true;
this.errors = { ...this.errors, [identifier]: message };
Sentry.captureException(error);
},
fetchNextPage({ query, pageInfo, identifier, errorMessage }) {
query
.fetchMore({
variables: {
identifier,
first: this.calculateDaysToFetch(getEarliestDate(this[identifier].nodes)),
after: pageInfo.endCursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const { nodes, ...rest } = fetchMoreResult[QUERY_DATA_KEY];
const { nodes: previousNodes } = previousResult[QUERY_DATA_KEY];
return {
[QUERY_DATA_KEY]: { ...rest, nodes: [...previousNodes, ...nodes] },
};
},
})
.catch((error) => this.handleError({ identifier, error, message: errorMessage }));
},
},
};
</script>
<template>
<div>
<h3>{{ chartTitle }}</h3>
<gl-alert v-if="hasLoadingErrors" variant="danger" :dismissible="false" class="gl-mt-3">
{{ errorMessage }}
</gl-alert>
<div v-if="!allQueriesFailed">
<chart-skeleton-loader v-if="isLoading" />
<gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
{{ noDataMessage }}
</gl-alert>
<gl-line-chart
v-else
:option="chartOptions"
:include-legend-avg-max="true"
:data="chartData"
/>
</div>
</div>
</template>