Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-02 18:08:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-02 18:08:52 +0300
commit215cb099344f3e59304064c9fffea9c3489d31c0 (patch)
tree186585d360e44aefed78106e7b23b8ae726f547e /app/assets/javascripts/analytics
parentc80b69a93fb4a6708b3337aa6e243ad09fe1d295 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/analytics')
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/app.vue7
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue224
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql13
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql13
4 files changed, 257 insertions, 0 deletions
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/app.vue b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
index 2533854119f..abe9b45ed79 100644
--- a/app/assets/javascripts/analytics/instance_statistics/components/app.vue
+++ b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
@@ -5,6 +5,7 @@ import InstanceStatisticsCountChart from './instance_statistics_count_chart.vue'
import UsersChart from './users_chart.vue';
import pipelinesStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
import issuesAndMergeRequestsQuery from '../graphql/queries/issues_and_merge_requests.query.graphql';
+import ProjectsAndGroupsChart from './projects_and_groups_chart.vue';
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
const PIPELINES_KEY_TO_NAME_MAP = {
@@ -32,6 +33,7 @@ export default {
InstanceCounts,
InstanceStatisticsCountChart,
UsersChart,
+ ProjectsAndGroupsChart,
},
TOTAL_DAYS_TO_SHOW,
START_DATE,
@@ -69,6 +71,11 @@ export default {
:end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/>
+ <projects-and-groups-chart
+ :start-date="$options.START_DATE"
+ :end-date="$options.TODAY"
+ :total-data-points="$options.TOTAL_DAYS_TO_SHOW"
+ />
<instance-statistics-count-chart
v-for="chartOptions in $options.configs"
:key="chartOptions.chartTitle"
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue
new file mode 100644
index 00000000000..e8e35c22fe1
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue
@@ -0,0 +1,224 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { GlLineChart } from '@gitlab/ui/dist/charts';
+import produce from 'immer';
+import { sortBy } from 'lodash';
+import * as Sentry from '~/sentry/wrapper';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { s__, __ } from '~/locale';
+import { formatDateAsMonth } from '~/lib/utils/datetime_utility';
+import latestGroupsQuery from '../graphql/queries/groups.query.graphql';
+import latestProjectsQuery from '../graphql/queries/projects.query.graphql';
+import { getAverageByMonth } from '../utils';
+
+const sortByDate = data => sortBy(data, item => new Date(item[0]).getTime());
+
+const averageAndSortData = (data = [], maxDataPoints) => {
+ const averaged = getAverageByMonth(
+ data.length > maxDataPoints ? data.slice(0, maxDataPoints) : data,
+ { shouldRound: true },
+ );
+ return sortByDate(averaged);
+};
+
+export default {
+ name: 'ProjectsAndGroupsChart',
+ components: { GlAlert, GlLineChart, ChartSkeletonLoader },
+ props: {
+ startDate: {
+ type: Date,
+ required: true,
+ },
+ endDate: {
+ type: Date,
+ required: true,
+ },
+ totalDataPoints: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loadingError: false,
+ errorMessage: '',
+ groups: [],
+ projects: [],
+ groupsPageInfo: null,
+ projectsPageInfo: null,
+ };
+ },
+ apollo: {
+ groups: {
+ query: latestGroupsQuery,
+ variables() {
+ return {
+ first: this.totalDataPoints,
+ after: null,
+ };
+ },
+ update(data) {
+ return data.groups?.nodes || [];
+ },
+ result({ data }) {
+ const {
+ groups: { pageInfo },
+ } = data;
+ this.groupsPageInfo = pageInfo;
+ this.fetchNextPage({
+ query: this.$apollo.queries.groups,
+ pageInfo: this.groupsPageInfo,
+ dataKey: 'groups',
+ errorMessage: this.$options.i18n.loadGroupsDataError,
+ });
+ },
+ error(error) {
+ this.handleError({
+ message: this.$options.i18n.loadGroupsDataError,
+ error,
+ dataKey: 'groups',
+ });
+ },
+ },
+ projects: {
+ query: latestProjectsQuery,
+ variables() {
+ return {
+ first: this.totalDataPoints,
+ after: null,
+ };
+ },
+ update(data) {
+ return data.projects?.nodes || [];
+ },
+ result({ data }) {
+ const {
+ projects: { pageInfo },
+ } = data;
+ this.projectsPageInfo = pageInfo;
+ this.fetchNextPage({
+ query: this.$apollo.queries.projects,
+ pageInfo: this.projectsPageInfo,
+ dataKey: 'projects',
+ errorMessage: this.$options.i18n.loadProjectsDataError,
+ });
+ },
+ error(error) {
+ this.handleError({
+ message: this.$options.i18n.loadProjectsDataError,
+ error,
+ dataKey: 'projects',
+ });
+ },
+ },
+ },
+ i18n: {
+ yAxisTitle: s__('InstanceStatistics|Total projects & groups'),
+ xAxisTitle: __('Month'),
+ loadChartError: s__(
+ 'InstanceStatistics|Could not load the projects and groups chart. Please refresh the page to try again.',
+ ),
+ loadProjectsDataError: s__('InstanceStatistics|There was an error while loading the projects'),
+ loadGroupsDataError: s__('InstanceStatistics|There was an error while loading the groups'),
+ noDataMessage: s__('InstanceStatistics|No data available.'),
+ },
+ computed: {
+ isLoadingGroups() {
+ return this.$apollo.queries.groups.loading || this.groupsPageInfo?.hasNextPage;
+ },
+ isLoadingProjects() {
+ return this.$apollo.queries.projects.loading || this.projectsPageInfo?.hasNextPage;
+ },
+ isLoading() {
+ return this.isLoadingProjects && this.isLoadingGroups;
+ },
+ groupChartData() {
+ return averageAndSortData(this.groups, this.totalDataPoints);
+ },
+ projectChartData() {
+ return averageAndSortData(this.projects, this.totalDataPoints);
+ },
+ hasNoData() {
+ const { projectChartData, groupChartData } = this;
+ return Boolean(!projectChartData.length && !groupChartData.length);
+ },
+ options() {
+ return {
+ xAxis: {
+ name: this.$options.i18n.xAxisTitle,
+ type: 'category',
+ axisLabel: {
+ formatter: value => {
+ return formatDateAsMonth(value);
+ },
+ },
+ },
+ yAxis: {
+ name: this.$options.i18n.yAxisTitle,
+ },
+ };
+ },
+ chartData() {
+ return [
+ {
+ name: s__('InstanceStatistics|Total projects'),
+ data: this.projectChartData,
+ },
+ {
+ name: s__('InstanceStatistics|Total groups'),
+ data: this.groupChartData,
+ },
+ ];
+ },
+ },
+ methods: {
+ handleError({ error, message = this.$options.i18n.loadChartError, dataKey = null }) {
+ this.loadingError = true;
+ this.errorMessage = message;
+ if (!dataKey) {
+ this.projects = [];
+ this.groups = [];
+ } else {
+ this[dataKey] = [];
+ }
+ Sentry.captureException(error);
+ },
+ fetchNextPage({ pageInfo, query, dataKey, errorMessage }) {
+ if (pageInfo?.hasNextPage) {
+ query
+ .fetchMore({
+ variables: { first: this.totalDataPoints, after: pageInfo.endCursor },
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ const results = produce(fetchMoreResult, newData => {
+ // eslint-disable-next-line no-param-reassign
+ newData[dataKey].nodes = [
+ ...previousResult[dataKey].nodes,
+ ...newData[dataKey].nodes,
+ ];
+ });
+ return results;
+ },
+ })
+ .catch(error => {
+ this.handleError({ error, message: errorMessage, dataKey });
+ });
+ }
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <h3>{{ $options.i18n.yAxisTitle }}</h3>
+ <chart-skeleton-loader v-if="isLoading" />
+ <gl-alert v-else-if="hasNoData" variant="info" :dismissible="false" class="gl-mt-3">
+ {{ $options.i18n.noDataMessage }}
+ </gl-alert>
+ <div v-else>
+ <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">{{
+ errorMessage
+ }}</gl-alert>
+ <gl-line-chart :option="options" :include-legend-avg-max="true" :data="chartData" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql
new file mode 100644
index 00000000000..ec56d91ffaa
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql
@@ -0,0 +1,13 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "../fragments/count.fragment.graphql"
+
+query getGroupsCount($first: Int, $after: String) {
+ groups: instanceStatisticsMeasurements(identifier: GROUPS, first: $first, after: $after) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+}
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql
new file mode 100644
index 00000000000..0845b703435
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql
@@ -0,0 +1,13 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "../fragments/count.fragment.graphql"
+
+query getProjectsCount($first: Int, $after: String) {
+ projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: $first, after: $after) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+}