summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/analytics
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-18 18:10:26 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-18 18:10:26 +0000
commit96c78a921fc87226239fe6a8ea89a518731dc152 (patch)
tree8cfb6d13c61d859eab529a2ad75b0603f7e1b78f /app/assets/javascripts/analytics
parent80d252c8e25dc88023e750cf2a22be6186cfd6aa (diff)
downloadgitlab-ce-96c78a921fc87226239fe6a8ea89a518731dc152.tar.gz
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.vue14
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue64
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql32
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/index.js24
-rw-r--r--app/assets/javascripts/analytics/shared/components/metric_card.vue80
5 files changed, 214 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
new file mode 100644
index 00000000000..eb0b67a1629
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
@@ -0,0 +1,14 @@
+<script>
+import InstanceCounts from './instance_counts.vue';
+
+export default {
+ name: 'InstanceStatisticsApp',
+ components: {
+ InstanceCounts,
+ },
+};
+</script>
+
+<template>
+ <instance-counts />
+</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue b/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue
new file mode 100644
index 00000000000..1147ce9af73
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue
@@ -0,0 +1,64 @@
+<script>
+import * as Sentry from '@sentry/browser';
+import { s__ } from '~/locale';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
+import MetricCard from '~/analytics/shared/components/metric_card.vue';
+import instanceStatisticsCountQuery from '../graphql/queries/instance_statistics_count.query.graphql';
+
+const defaultPrecision = 0;
+
+export default {
+ name: 'InstanceCounts',
+ components: {
+ MetricCard,
+ },
+ data() {
+ return {
+ counts: [],
+ };
+ },
+ apollo: {
+ counts: {
+ query: instanceStatisticsCountQuery,
+ update(data) {
+ return Object.entries(data).map(([key, obj]) => {
+ const label = this.$options.i18n.labels[key];
+ const formatter = getFormatter(SUPPORTED_FORMATS.number);
+ const value = obj.nodes?.length ? formatter(obj.nodes[0].count, defaultPrecision) : null;
+
+ return {
+ key,
+ value,
+ label,
+ };
+ });
+ },
+ error(error) {
+ createFlash(this.$options.i18n.loadCountsError);
+ Sentry.captureException(error);
+ },
+ },
+ },
+ i18n: {
+ labels: {
+ users: s__('InstanceStatistics|Users'),
+ projects: s__('InstanceStatistics|Projects'),
+ groups: s__('InstanceStatistics|Groups'),
+ issues: s__('InstanceStatistics|Issues'),
+ mergeRequests: s__('InstanceStatistics|Merge Requests'),
+ pipelines: s__('InstanceStatistics|Pipelines'),
+ },
+ loadCountsError: s__('Could not load instance counts. Please refresh the page to try again.'),
+ },
+};
+</script>
+
+<template>
+ <metric-card
+ :title="__('Instance Statistics')"
+ :metrics="counts"
+ :is-loading="$apollo.queries.counts.loading"
+ class="gl-mt-4"
+ />
+</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql
new file mode 100644
index 00000000000..fd8282683d9
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql
@@ -0,0 +1,32 @@
+query getInstanceCounts {
+ projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: 1) {
+ nodes {
+ count
+ }
+ }
+ groups: instanceStatisticsMeasurements(identifier: GROUPS, first: 1) {
+ nodes {
+ count
+ }
+ }
+ users: instanceStatisticsMeasurements(identifier: USERS, first: 1) {
+ nodes {
+ count
+ }
+ }
+ issues: instanceStatisticsMeasurements(identifier: ISSUES, first: 1) {
+ nodes {
+ count
+ }
+ }
+ mergeRequests: instanceStatisticsMeasurements(identifier: MERGE_REQUESTS, first: 1) {
+ nodes {
+ count
+ }
+ }
+ pipelines: instanceStatisticsMeasurements(identifier: PIPELINES, first: 1) {
+ nodes {
+ count
+ }
+ }
+}
diff --git a/app/assets/javascripts/analytics/instance_statistics/index.js b/app/assets/javascripts/analytics/instance_statistics/index.js
new file mode 100644
index 00000000000..0d7dcf6ace8
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import InstanceStatisticsApp from './components/app.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default () => {
+ const el = document.getElementById('js-instance-statistics-app');
+
+ if (!el) return false;
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(InstanceStatisticsApp);
+ },
+ });
+};
diff --git a/app/assets/javascripts/analytics/shared/components/metric_card.vue b/app/assets/javascripts/analytics/shared/components/metric_card.vue
new file mode 100644
index 00000000000..cee186c057c
--- /dev/null
+++ b/app/assets/javascripts/analytics/shared/components/metric_card.vue
@@ -0,0 +1,80 @@
+<script>
+import {
+ GlCard,
+ GlDeprecatedSkeletonLoading as GlSkeletonLoading,
+ GlLink,
+ GlIcon,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+
+export default {
+ name: 'MetricCard',
+ components: {
+ GlCard,
+ GlSkeletonLoading,
+ GlLink,
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ metrics: {
+ type: Array,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ methods: {
+ valueText(metric) {
+ const { value = null, unit = null } = metric;
+ if (!value || value === '-') return '-';
+ return unit && value ? `${value} ${unit}` : value;
+ },
+ },
+};
+</script>
+<template>
+ <gl-card>
+ <template #header>
+ <strong ref="title">{{ title }}</strong>
+ </template>
+ <template #default>
+ <gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3" />
+ <div v-else ref="metricsWrapper" class="gl-display-flex">
+ <div
+ v-for="metric in metrics"
+ :key="metric.key"
+ ref="metricItem"
+ class="js-metric-card-item gl-flex-grow-1 gl-text-center"
+ >
+ <gl-link v-if="metric.link" :href="metric.link">
+ <h3 class="gl-my-2 gl-text-blue-700">{{ valueText(metric) }}</h3>
+ </gl-link>
+ <h3 v-else class="gl-my-2">{{ valueText(metric) }}</h3>
+ <p class="text-secondary gl-font-sm gl-mb-2">
+ {{ metric.label }}
+ <span v-if="metric.tooltipText">
+ &nbsp;
+ <gl-icon
+ v-gl-tooltip="{ title: metric.tooltipText }"
+ :size="14"
+ class="gl-vertical-align-middle"
+ name="question"
+ data-testid="tooltip"
+ />
+ </span>
+ </p>
+ </div>
+ </div>
+ </template>
+ </gl-card>
+</template>