Commit 01231757 authored by Arturo Herrero's avatar Arturo Herrero

Merge branch '285438-rename-instance-statistics-to-usage-trends' into 'master'

Rename InstanceStatistics to UsageTrend [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!54153
parents 81b2ae7f 29e01668
<script> <script>
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants'; import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
import ChartsConfig from './charts_config'; import ChartsConfig from './charts_config';
import InstanceCounts from './instance_counts.vue';
import InstanceStatisticsCountChart from './instance_statistics_count_chart.vue';
import ProjectsAndGroupsChart from './projects_and_groups_chart.vue'; import ProjectsAndGroupsChart from './projects_and_groups_chart.vue';
import UsageCounts from './usage_counts.vue';
import UsageTrendsCountChart from './usage_trends_count_chart.vue';
import UsersChart from './users_chart.vue'; import UsersChart from './users_chart.vue';
export default { export default {
name: 'InstanceStatisticsApp', name: 'UsageTrendsApp',
components: { components: {
InstanceCounts, UsageCounts,
InstanceStatisticsCountChart, UsageTrendsCountChart,
UsersChart, UsersChart,
ProjectsAndGroupsChart, ProjectsAndGroupsChart,
}, },
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
<template> <template>
<div> <div>
<instance-counts /> <usage-counts />
<users-chart <users-chart
:start-date="$options.START_DATE" :start-date="$options.START_DATE"
:end-date="$options.TODAY" :end-date="$options.TODAY"
...@@ -34,7 +34,7 @@ export default { ...@@ -34,7 +34,7 @@ export default {
:end-date="$options.TODAY" :end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW" :total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/> />
<instance-statistics-count-chart <usage-trends-count-chart
v-for="chartOptions in $options.configs" v-for="chartOptions in $options.configs"
:key="chartOptions.chartTitle" :key="chartOptions.chartTitle"
:queries="chartOptions.queries" :queries="chartOptions.queries"
......
import { s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import query from '../graphql/queries/instance_count.query.graphql'; import query from '../graphql/queries/usage_count.query.graphql';
const noDataMessage = s__('InstanceStatistics|No data available.'); const noDataMessage = s__('UsageTrends|No data available.');
export default [ export default [
{ {
loadChartError: sprintf( loadChartError: sprintf(
s__( s__('UsageTrends|Could not load the pipelines chart. Please refresh the page to try again.'),
'InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again.',
),
), ),
noDataMessage, noDataMessage,
chartTitle: s__('InstanceStatistics|Pipelines'), chartTitle: s__('UsageTrends|Pipelines'),
yAxisTitle: s__('InstanceStatistics|Items'), yAxisTitle: s__('UsageTrends|Items'),
xAxisTitle: s__('InstanceStatistics|Month'), xAxisTitle: s__('UsageTrends|Month'),
queries: [ queries: [
{ {
query, query,
title: s__('InstanceStatistics|Pipelines total'), title: s__('UsageTrends|Pipelines total'),
identifier: 'PIPELINES', identifier: 'PIPELINES',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the total pipelines')),
s__('InstanceStatistics|There was an error fetching the total pipelines'),
),
}, },
{ {
query, query,
title: s__('InstanceStatistics|Pipelines succeeded'), title: s__('UsageTrends|Pipelines succeeded'),
identifier: 'PIPELINES_SUCCEEDED', identifier: 'PIPELINES_SUCCEEDED',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the successful pipelines')),
s__('InstanceStatistics|There was an error fetching the successful pipelines'),
),
}, },
{ {
query, query,
title: s__('InstanceStatistics|Pipelines failed'), title: s__('UsageTrends|Pipelines failed'),
identifier: 'PIPELINES_FAILED', identifier: 'PIPELINES_FAILED',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the failed pipelines')),
s__('InstanceStatistics|There was an error fetching the failed pipelines'),
),
}, },
{ {
query, query,
title: s__('InstanceStatistics|Pipelines canceled'), title: s__('UsageTrends|Pipelines canceled'),
identifier: 'PIPELINES_CANCELED', identifier: 'PIPELINES_CANCELED',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the cancelled pipelines')),
s__('InstanceStatistics|There was an error fetching the cancelled pipelines'),
),
}, },
{ {
query, query,
title: s__('InstanceStatistics|Pipelines skipped'), title: s__('UsageTrends|Pipelines skipped'),
identifier: 'PIPELINES_SKIPPED', identifier: 'PIPELINES_SKIPPED',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the skipped pipelines')),
s__('InstanceStatistics|There was an error fetching the skipped pipelines'),
),
}, },
], ],
}, },
{ {
loadChartError: sprintf( loadChartError: sprintf(
s__( s__(
'InstanceStatistics|Could not load the issues and merge requests chart. Please refresh the page to try again.', 'UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again.',
), ),
), ),
noDataMessage, noDataMessage,
chartTitle: s__('InstanceStatistics|Issues & Merge Requests'), chartTitle: s__('UsageTrends|Issues & Merge Requests'),
yAxisTitle: s__('InstanceStatistics|Items'), yAxisTitle: s__('UsageTrends|Items'),
xAxisTitle: s__('InstanceStatistics|Month'), xAxisTitle: s__('UsageTrends|Month'),
queries: [ queries: [
{ {
query, query,
title: __('Issues'), title: __('Issues'),
identifier: 'ISSUES', identifier: 'ISSUES',
loadError: sprintf(s__('InstanceStatistics|There was an error fetching the issues')), loadError: sprintf(s__('UsageTrends|There was an error fetching the issues')),
}, },
{ {
query, query,
title: __('Merge requests'), title: __('Merge requests'),
identifier: 'MERGE_REQUESTS', identifier: 'MERGE_REQUESTS',
loadError: sprintf( loadError: sprintf(s__('UsageTrends|There was an error fetching the merge requests')),
s__('InstanceStatistics|There was an error fetching the merge requests'),
),
}, },
], ],
}, },
......
...@@ -113,14 +113,14 @@ export default { ...@@ -113,14 +113,14 @@ export default {
}, },
}, },
i18n: { i18n: {
yAxisTitle: s__('InstanceStatistics|Total projects & groups'), yAxisTitle: s__('UsageTrends|Total projects & groups'),
xAxisTitle: __('Month'), xAxisTitle: __('Month'),
loadChartError: s__( loadChartError: s__(
'InstanceStatistics|Could not load the projects and groups chart. Please refresh the page to try again.', 'UsageTrends|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'), loadProjectsDataError: s__('UsageTrends|There was an error while loading the projects'),
loadGroupsDataError: s__('InstanceStatistics|There was an error while loading the groups'), loadGroupsDataError: s__('UsageTrends|There was an error while loading the groups'),
noDataMessage: s__('InstanceStatistics|No data available.'), noDataMessage: s__('UsageTrends|No data available.'),
}, },
computed: { computed: {
isLoadingGroups() { isLoadingGroups() {
...@@ -161,11 +161,11 @@ export default { ...@@ -161,11 +161,11 @@ export default {
chartData() { chartData() {
return [ return [
{ {
name: s__('InstanceStatistics|Total projects'), name: s__('UsageTrends|Total projects'),
data: this.projectChartData, data: this.projectChartData,
}, },
{ {
name: s__('InstanceStatistics|Total groups'), name: s__('UsageTrends|Total groups'),
data: this.groupChartData, data: this.groupChartData,
}, },
]; ];
......
...@@ -4,12 +4,12 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; ...@@ -4,12 +4,12 @@ import { deprecatedCreateFlash as createFlash } from '~/flash';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import instanceStatisticsCountQuery from '../graphql/queries/instance_statistics_count.query.graphql'; import usageTrendsCountQuery from '../graphql/queries/usage_trends_count.query.graphql';
const defaultPrecision = 0; const defaultPrecision = 0;
export default { export default {
name: 'InstanceCounts', name: 'UsageCounts',
components: { components: {
MetricCard, MetricCard,
}, },
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
}, },
apollo: { apollo: {
counts: { counts: {
query: instanceStatisticsCountQuery, query: usageTrendsCountQuery,
update(data) { update(data) {
return Object.entries(data).map(([key, obj]) => { return Object.entries(data).map(([key, obj]) => {
const label = this.$options.i18n.labels[key]; const label = this.$options.i18n.labels[key];
...@@ -42,14 +42,14 @@ export default { ...@@ -42,14 +42,14 @@ export default {
}, },
i18n: { i18n: {
labels: { labels: {
users: s__('InstanceStatistics|Users'), users: s__('UsageTrends|Users'),
projects: s__('InstanceStatistics|Projects'), projects: s__('UsageTrends|Projects'),
groups: s__('InstanceStatistics|Groups'), groups: s__('UsageTrends|Groups'),
issues: s__('InstanceStatistics|Issues'), issues: s__('UsageTrends|Issues'),
mergeRequests: s__('InstanceStatistics|Merge Requests'), mergeRequests: s__('UsageTrends|Merge Requests'),
pipelines: s__('InstanceStatistics|Pipelines'), pipelines: s__('UsageTrends|Pipelines'),
}, },
loadCountsError: s__('Could not load instance counts. Please refresh the page to try again.'), loadCountsError: s__('Could not load usage counts. Please refresh the page to try again.'),
}, },
}; };
</script> </script>
......
...@@ -12,10 +12,10 @@ import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleto ...@@ -12,10 +12,10 @@ import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleto
import { TODAY, START_DATE } from '../constants'; import { TODAY, START_DATE } from '../constants';
import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils'; import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';
const QUERY_DATA_KEY = 'instanceStatisticsMeasurements'; const QUERY_DATA_KEY = 'usageTrendsMeasurements';
export default { export default {
name: 'InstanceStatisticsCountChart', name: 'UsageTrendsCountChart',
components: { components: {
GlLineChart, GlLineChart,
GlAlert, GlAlert,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql" #import "../fragments/count.fragment.graphql"
query getGroupsCount($first: Int, $after: String) { query getGroupsCount($first: Int, $after: String) {
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: $first, after: $after) { groups: usageTrendsMeasurements(identifier: GROUPS, first: $first, after: $after) {
nodes { nodes {
...Count ...Count
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql" #import "../fragments/count.fragment.graphql"
query getProjectsCount($first: Int, $after: String) { query getProjectsCount($first: Int, $after: String) {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: $first, after: $after) { projects: usageTrendsMeasurements(identifier: PROJECTS, first: $first, after: $after) {
nodes { nodes {
...Count ...Count
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql" #import "../fragments/count.fragment.graphql"
query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) { query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) {
instanceStatisticsMeasurements(identifier: $identifier, first: $first, after: $after) { usageTrendsMeasurements(identifier: $identifier, first: $first, after: $after) {
nodes { nodes {
...Count ...Count
} }
......
#import "../fragments/count.fragment.graphql" #import "../fragments/count.fragment.graphql"
query getInstanceCounts { query getInstanceCounts {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: 1) { projects: usageTrendsMeasurements(identifier: PROJECTS, first: 1) {
nodes { nodes {
...Count ...Count
} }
} }
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: 1) { groups: usageTrendsMeasurements(identifier: GROUPS, first: 1) {
nodes { nodes {
...Count ...Count
} }
} }
users: instanceStatisticsMeasurements(identifier: USERS, first: 1) { users: usageTrendsMeasurements(identifier: USERS, first: 1) {
nodes { nodes {
...Count ...Count
} }
} }
issues: instanceStatisticsMeasurements(identifier: ISSUES, first: 1) { issues: usageTrendsMeasurements(identifier: ISSUES, first: 1) {
nodes { nodes {
...Count ...Count
} }
} }
mergeRequests: instanceStatisticsMeasurements(identifier: MERGE_REQUESTS, first: 1) { mergeRequests: usageTrendsMeasurements(identifier: MERGE_REQUESTS, first: 1) {
nodes { nodes {
...Count ...Count
} }
} }
pipelines: instanceStatisticsMeasurements(identifier: PIPELINES, first: 1) { pipelines: usageTrendsMeasurements(identifier: PIPELINES, first: 1) {
nodes { nodes {
...Count ...Count
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql" #import "../fragments/count.fragment.graphql"
query getUsersCount($first: Int, $after: String) { query getUsersCount($first: Int, $after: String) {
users: instanceStatisticsMeasurements(identifier: USERS, first: $first, after: $after) { users: usageTrendsMeasurements(identifier: USERS, first: $first, after: $after) {
nodes { nodes {
...Count ...Count
} }
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import InstanceStatisticsApp from './components/app.vue'; import UsageTrendsApp from './components/app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -10,7 +10,7 @@ const apolloProvider = new VueApollo({ ...@@ -10,7 +10,7 @@ const apolloProvider = new VueApollo({
}); });
export default () => { export default () => {
const el = document.getElementById('js-instance-statistics-app'); const el = document.getElementById('js-usage-trends-app');
if (!el) return false; if (!el) return false;
...@@ -18,7 +18,7 @@ export default () => { ...@@ -18,7 +18,7 @@ export default () => {
el, el,
apolloProvider, apolloProvider,
render(h) { render(h) {
return h(InstanceStatisticsApp); return h(UsageTrendsApp);
}, },
}); });
}; };
...@@ -41,8 +41,8 @@ export function getAverageByMonth(items = [], options = {}) { ...@@ -41,8 +41,8 @@ export function getAverageByMonth(items = [], options = {}) {
} }
/** /**
* Takes an array of instance counts and returns the last item in the list * Takes an array of usage counts and returns the last item in the list
* @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String } * @param {Array} arr array of usage counts in the form { count: Number, recordedAt: date String }
* @return {String} the 'recordedAt' value of the earliest item * @return {String} the 'recordedAt' value of the earliest item
*/ */
export const getEarliestDate = (arr = []) => { export const getEarliestDate = (arr = []) => {
...@@ -54,7 +54,7 @@ export const getEarliestDate = (arr = []) => { ...@@ -54,7 +54,7 @@ export const getEarliestDate = (arr = []) => {
* Takes an array of queries and produces an object with the query identifier as key * Takes an array of queries and produces an object with the query identifier as key
* and a supplied defaultValue as its value * and a supplied defaultValue as its value
* @param {Array} queries array of chart query configs, * @param {Array} queries array of chart query configs,
* see ./analytics/instance_statistics/components/charts_config.js * see ./analytics/usage_trends/components/charts_config.js
* @param {any} defaultValue value to set each identifier to * @param {any} defaultValue value to set each identifier to
* @return {Object} key value pair of the form { queryIdentifier: defaultValue } * @return {Object} key value pair of the form { queryIdentifier: defaultValue }
*/ */
......
import initInstanceStatisticsApp from '~/analytics/instance_statistics';
document.addEventListener('DOMContentLoaded', () => initInstanceStatisticsApp());
import initUsageTrendsApp from '~/analytics/usage_trends';
initUsageTrendsApp();
# frozen_string_literal: true # frozen_string_literal: true
class Admin::InstanceStatisticsController < Admin::ApplicationController class Admin::UsageTrendsController < Admin::ApplicationController
include Analytics::UniqueVisitsHelper include Analytics::UniqueVisitsHelper
before_action :check_feature_flag
track_unique_visits :index, target_id: 'i_analytics_instance_statistics' track_unique_visits :index, target_id: 'i_analytics_instance_statistics'
feature_category :devops_reports feature_category :devops_reports
def index def index
end end
def check_feature_flag
render_404 unless Feature.enabled?(:instance_statistics, default_enabled: true)
end
end end
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
module Resolvers module Resolvers
module Admin module Admin
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class MeasurementsResolver < BaseResolver class MeasurementsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Admin::Analytics::InstanceStatistics::MeasurementType, null: true type Types::Admin::Analytics::UsageTrends::MeasurementType, null: true
argument :identifier, Types::Admin::Analytics::InstanceStatistics::MeasurementIdentifierEnum, argument :identifier, Types::Admin::Analytics::UsageTrends::MeasurementIdentifierEnum,
required: true, required: true,
description: 'The type of measurement/statistics to retrieve.' description: 'The type of measurement/statistics to retrieve.'
...@@ -24,7 +24,7 @@ module Resolvers ...@@ -24,7 +24,7 @@ module Resolvers
def resolve(identifier:, recorded_before: nil, recorded_after: nil) def resolve(identifier:, recorded_before: nil, recorded_after: nil)
authorize! authorize!
::Analytics::InstanceStatistics::Measurement ::Analytics::UsageTrends::Measurement
.recorded_after(recorded_after) .recorded_after(recorded_after)
.recorded_before(recorded_before) .recorded_before(recorded_before)
.with_identifier(identifier) .with_identifier(identifier)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Types module Types
module Admin module Admin
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class MeasurementIdentifierEnum < BaseEnum class MeasurementIdentifierEnum < BaseEnum
graphql_name 'MeasurementIdentifier' graphql_name 'MeasurementIdentifier'
description 'Possible identifier types for a measurement' description 'Possible identifier types for a measurement'
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
module Types module Types
module Admin module Admin
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class MeasurementType < BaseObject class MeasurementType < BaseObject
include Gitlab::Graphql::Authorize::AuthorizeResource include Gitlab::Graphql::Authorize::AuthorizeResource
graphql_name 'InstanceStatisticsMeasurement' graphql_name 'UsageTrendsMeasurement'
description 'Represents a recorded measurement (object count) for the Admins' description 'Represents a recorded measurement (object count) for the Admins'
authorize :read_instance_statistics_measurements authorize :read_usage_trends_measurement
field :recorded_at, Types::TimeType, null: true, field :recorded_at, Types::TimeType, null: true,
description: 'The time the measurement was recorded.' description: 'The time the measurement was recorded.'
...@@ -17,7 +17,7 @@ module Types ...@@ -17,7 +17,7 @@ module Types
field :count, GraphQL::INT_TYPE, null: false, field :count, GraphQL::INT_TYPE, null: false,
description: 'Object count.' description: 'Object count.'
field :identifier, Types::Admin::Analytics::InstanceStatistics::MeasurementIdentifierEnum, null: false, field :identifier, Types::Admin::Analytics::UsageTrends::MeasurementIdentifierEnum, null: false,
description: 'The type of objects being measured.' description: 'The type of objects being measured.'
end end
end end
......
...@@ -82,10 +82,16 @@ module Types ...@@ -82,10 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.' argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
end end
field :instance_statistics_measurements, Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type, field :instance_statistics_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true, null: true,
description: 'Get statistics on the instance.', description: 'Get statistics on the instance.',
resolver: Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver deprecated: { reason: 'This field was renamed. Use the `usageTrendsMeasurements` field instead.', milestone: '13.10' },
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver
field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true,
description: 'Get statistics on the instance.',
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver
field :ci_application_settings, Types::Ci::ApplicationSettingType, field :ci_application_settings, Types::Ci::ApplicationSettingType,
null: true, null: true,
......
# frozen_string_literal: true
module Analytics
module InstanceStatistics
def self.table_name_prefix
'analytics_instance_statistics_'
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class Measurement < ApplicationRecord class Measurement < ApplicationRecord
self.table_name = 'analytics_instance_statistics_measurements'
EXPERIMENTAL_IDENTIFIERS = %i[pipelines_succeeded pipelines_failed pipelines_canceled pipelines_skipped].freeze EXPERIMENTAL_IDENTIFIERS = %i[pipelines_succeeded pipelines_failed pipelines_canceled pipelines_skipped].freeze
enum identifier: { enum identifier: {
...@@ -58,4 +60,4 @@ module Analytics ...@@ -58,4 +60,4 @@ module Analytics
end end
end end
Analytics::InstanceStatistics::Measurement.prepend_if_ee('EE::Analytics::InstanceStatistics::Measurement') Analytics::UsageTrends::Measurement.prepend_if_ee('EE::Analytics::UsageTrends::Measurement')
# frozen_string_literal: true # frozen_string_literal: true
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class MeasurementPolicy < BasePolicy class MeasurementPolicy < BasePolicy
delegate { :global } delegate { :global }
end end
......
...@@ -100,7 +100,7 @@ class GlobalPolicy < BasePolicy ...@@ -100,7 +100,7 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute enable :update_custom_attribute
enable :approve_user enable :approve_user
enable :reject_user enable :reject_user
enable :read_instance_statistics_measurements enable :read_usage_trends_measurement
end end
# We can't use `read_statistics` because the user may have different permissions for different projects # We can't use `read_statistics` because the user may have different permissions for different projects
......
- breadcrumb_title _("Usage Trends") - breadcrumb_title _("Usage Trends")
- page_title _("Usage Trends") - page_title _("Usage Trends")
#js-instance-statistics-app #js-usage-trends-app
...@@ -65,11 +65,10 @@ ...@@ -65,11 +65,10 @@
= link_to admin_dev_ops_report_path, title: _('DevOps Report') do = link_to admin_dev_ops_report_path, title: _('DevOps Report') do
%span %span
= _('DevOps Report') = _('DevOps Report')
- if Feature.enabled?(:instance_statistics, default_enabled: true) = nav_link(controller: :usage_trends) do
= nav_link(controller: :instance_statistics) do = link_to admin_usage_trends_path, title: _('Usage Trends') do
= link_to admin_instance_statistics_path, title: _('Usage Trends') do %span
%span = _('Usage Trends')
= _('Usage Trends')
= nav_link(controller: admin_monitoring_nav_links) do = nav_link(controller: admin_monitoring_nav_links) do
= link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' } do = link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' } do
......
...@@ -131,6 +131,14 @@ ...@@ -131,6 +131,14 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :tags: []
- :name: cronjob:analytics_usage_trends_count_job_trigger
:feature_category: :devops_reports
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: cronjob:authorized_project_update_periodic_recalculate - :name: cronjob:authorized_project_update_periodic_recalculate
:feature_category: :source_code_management :feature_category: :source_code_management
:has_external_dependencies: :has_external_dependencies:
...@@ -1413,6 +1421,14 @@ ...@@ -1413,6 +1421,14 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :tags: []
- :name: analytics_usage_trends_counter_job
:feature_category: :devops_reports
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: approve_blocked_pending_approval_users - :name: approve_blocked_pending_approval_users
:feature_category: :users :feature_category: :users
:has_external_dependencies: :has_external_dependencies:
......
...@@ -2,31 +2,19 @@ ...@@ -2,31 +2,19 @@
module Analytics module Analytics
module InstanceStatistics module InstanceStatistics
# This worker will be removed in 14.0
class CountJobTriggerWorker class CountJobTriggerWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
DEFAULT_DELAY = 3.minutes.freeze
feature_category :devops_reports feature_category :devops_reports
urgency :low urgency :low
idempotent! idempotent!
def perform def perform
recorded_at = Time.zone.now # Delegate to the new worker
Analytics::UsageTrends::CountJobTriggerWorker.new.perform
worker_arguments = Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder.new(
measurement_identifiers: ::Analytics::InstanceStatistics::Measurement.measurement_identifier_values,
recorded_at: recorded_at
).execute
perform_in = DEFAULT_DELAY.minutes.from_now
worker_arguments.each do |args|
CounterJobWorker.perform_in(perform_in, *args)
perform_in += DEFAULT_DELAY
end
end end
end end
end end
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module Analytics module Analytics
module InstanceStatistics module InstanceStatistics
# This worker will be removed in 14.0
class CounterJobWorker class CounterJobWorker
include ApplicationWorker include ApplicationWorker
...@@ -10,24 +11,9 @@ module Analytics ...@@ -10,24 +11,9 @@ module Analytics
idempotent! idempotent!
def perform(measurement_identifier, min_id, max_id, recorded_at) def perform(*args)
query_scope = ::Analytics::InstanceStatistics::Measurement.identifier_query_mapping[measurement_identifier].call # Delegate to the new worker
Analytics::UsageTrends::CounterJobWorker.new.perform(*args)
count = if min_id.nil? || max_id.nil? # table is empty
0
else
counter(query_scope, min_id, max_id)
end
return if count == Gitlab::Database::BatchCounter::FALLBACK
InstanceStatistics::Measurement.insert_all([{ recorded_at: recorded_at, count: count, identifier: measurement_identifier }])
end
private
def counter(query_scope, min_id, max_id)
Gitlab::Database::BatchCount.batch_count(query_scope, start: min_id, finish: max_id)
end end
end end
end end
......
# frozen_string_literal: true
module Analytics
module UsageTrends
class CountJobTriggerWorker
extend ::Gitlab::Utils::Override
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
DEFAULT_DELAY = 3.minutes.freeze
feature_category :devops_reports
urgency :low
idempotent!
def perform
recorded_at = Time.zone.now
worker_arguments = Gitlab::Analytics::UsageTrends::WorkersArgumentBuilder.new(
measurement_identifiers: ::Analytics::UsageTrends::Measurement.measurement_identifier_values,
recorded_at: recorded_at
).execute
perform_in = DEFAULT_DELAY.minutes.from_now
worker_arguments.each do |args|
CounterJobWorker.perform_in(perform_in, *args)
perform_in += DEFAULT_DELAY
end
end
end
end
end
# frozen_string_literal: true
module Analytics
module UsageTrends
class CounterJobWorker
extend ::Gitlab::Utils::Override
include ApplicationWorker
feature_category :devops_reports
urgency :low
idempotent!
def perform(measurement_identifier, min_id, max_id, recorded_at)
query_scope = ::Analytics::UsageTrends::Measurement.identifier_query_mapping[measurement_identifier].call
count = if min_id.nil? || max_id.nil? # table is empty
0
else
counter(query_scope, min_id, max_id)
end
return if count == Gitlab::Database::BatchCounter::FALLBACK
UsageTrends::Measurement.insert_all([{ recorded_at: recorded_at, count: count, identifier: measurement_identifier }])
end
private
def counter(query_scope, min_id, max_id)
Gitlab::Database::BatchCount.batch_count(query_scope, start: min_id, finish: max_id)
end
end
end
end
---
title: Deprecate instanceStatisticsMeasurements in favor of usageTrendsMeasurements
GraphQL field
merge_request: 54153
author:
type: changed
---
name: instance_statistics
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40583
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241711
milestone: '13.4'
type: development
group: group::optimize
default_enabled: true
...@@ -544,9 +544,9 @@ Settings.cron_jobs['postgres_dynamic_partitions_creator']['job_class'] ||= 'Part ...@@ -544,9 +544,9 @@ Settings.cron_jobs['postgres_dynamic_partitions_creator']['job_class'] ||= 'Part
Settings.cron_jobs['ci_platform_metrics_update_cron_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['ci_platform_metrics_update_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['cron'] ||= '47 9 * * *' Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['cron'] ||= '47 9 * * *'
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['job_class'] = 'CiPlatformMetricsUpdateCronWorker' Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['job_class'] = 'CiPlatformMetricsUpdateCronWorker'
Settings.cron_jobs['analytics_instance_statistics_count_job_trigger_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['analytics_usage_trends_count_job_trigger_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['analytics_instance_statistics_count_job_trigger_worker']['cron'] ||= '50 23 */1 * *' Settings.cron_jobs['analytics_usage_trends_count_job_trigger_worker']['cron'] ||= '50 23 */1 * *'
Settings.cron_jobs['analytics_instance_statistics_count_job_trigger_worker']['job_class'] ||= 'Analytics::InstanceStatistics::CountJobTriggerWorker' Settings.cron_jobs['analytics_usage_trends_count_job_trigger_worker']['job_class'] ||= 'Analytics::UsageTrends::CountJobTriggerWorker'
Settings.cron_jobs['member_invitation_reminder_emails_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['member_invitation_reminder_emails_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['member_invitation_reminder_emails_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['member_invitation_reminder_emails_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['member_invitation_reminder_emails_worker']['job_class'] = 'MemberInvitationReminderEmailsWorker' Settings.cron_jobs['member_invitation_reminder_emails_worker']['job_class'] = 'MemberInvitationReminderEmailsWorker'
......
...@@ -95,7 +95,8 @@ namespace :admin do ...@@ -95,7 +95,8 @@ namespace :admin do
resources :projects, only: [:index] resources :projects, only: [:index]
resources :instance_statistics, only: :index get '/instance_statistics', to: redirect('admin/usage_trends')
resources :usage_trends, only: :index
resource :dev_ops_report, controller: 'dev_ops_report', only: :show resource :dev_ops_report, controller: 'dev_ops_report', only: :show
resources :cohorts, only: :index resources :cohorts, only: :index
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
- 1 - 1
- - analytics_instance_statistics_counter_job - - analytics_instance_statistics_counter_job
- 1 - 1
- - analytics_usage_trends_counter_job
- 1
- - approve_blocked_pending_approval_users - - approve_blocked_pending_approval_users
- 1 - 1
- - authorized_keys - - authorized_keys
......
...@@ -7,7 +7,7 @@ Gitlab::Seeder.quiet do ...@@ -7,7 +7,7 @@ Gitlab::Seeder.quiet do
max_increase = 10000 max_increase = 10000
max_decrease = 1000 max_decrease = 1000
model_class = Analytics::InstanceStatistics::Measurement model_class = Analytics::UsageTrends::Measurement
measurements = model_class.identifiers.flat_map do |_, id| measurements = model_class.identifiers.flat_map do |_, id|
recorded_at = 60.days.ago recorded_at = 60.days.ago
......
# frozen_string_literal: true
class MigrateUsageTrendsSidekiqQueue < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
sidekiq_queue_migrate 'cronjob:analytics_instance_statistics_count_job_trigger', to: 'cronjob:analytics_usage_trends_count_job_trigger'
sidekiq_queue_migrate 'analytics_instance_statistics_counter_job', to: 'analytics_usage_trends_counter_job'
end
def down
sidekiq_queue_migrate 'cronjob:analytics_usage_trends_count_job_trigger', to: 'cronjob:analytics_instance_statistics_count_job_trigger'
sidekiq_queue_migrate 'analytics_usage_trends_counter_job', to: 'analytics_instance_statistics_counter_job'
end
end
2965d990ec9cf2edd610b063686f9a1d9de4c38bcba3c8439a18159812d529d4
\ No newline at end of file
...@@ -13061,61 +13061,6 @@ type InstanceSecurityDashboard { ...@@ -13061,61 +13061,6 @@ type InstanceSecurityDashboard {
): VulnerabilitySeveritiesCount ): VulnerabilitySeveritiesCount
} }
"""
Represents a recorded measurement (object count) for the Admins
"""
type InstanceStatisticsMeasurement {
"""
Object count.
"""
count: Int!
"""
The type of objects being measured.
"""
identifier: MeasurementIdentifier!
"""
The time the measurement was recorded.
"""
recordedAt: Time
}
"""
The connection type for InstanceStatisticsMeasurement.
"""
type InstanceStatisticsMeasurementConnection {
"""
A list of edges.
"""
edges: [InstanceStatisticsMeasurementEdge]
"""
A list of nodes.
"""
nodes: [InstanceStatisticsMeasurement]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type InstanceStatisticsMeasurementEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: InstanceStatisticsMeasurement
}
""" """
Incident severity Incident severity
""" """
...@@ -21681,7 +21626,8 @@ type Query { ...@@ -21681,7 +21626,8 @@ type Query {
instanceSecurityDashboard: InstanceSecurityDashboard instanceSecurityDashboard: InstanceSecurityDashboard
""" """
Get statistics on the instance. Get statistics on the instance. Deprecated in 13.10: This field was renamed.
Use the `usageTrendsMeasurements` field instead..
""" """
instanceStatisticsMeasurements( instanceStatisticsMeasurements(
""" """
...@@ -21718,7 +21664,7 @@ type Query { ...@@ -21718,7 +21664,7 @@ type Query {
Measurement recorded before this date. Measurement recorded before this date.
""" """
recordedBefore: Time recordedBefore: Time
): InstanceStatisticsMeasurementConnection ): UsageTrendsMeasurementConnection @deprecated(reason: "This field was renamed. Use the `usageTrendsMeasurements` field instead.. Deprecated in 13.10.")
""" """
Find an Issue. Find an Issue.
...@@ -21940,6 +21886,46 @@ type Query { ...@@ -21940,6 +21886,46 @@ type Query {
visibility: VisibilityScopesEnum visibility: VisibilityScopesEnum
): SnippetConnection ): SnippetConnection
"""
Get statistics on the instance.
"""
usageTrendsMeasurements(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
The type of measurement/statistics to retrieve.
"""
identifier: MeasurementIdentifier!
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Measurement recorded after this date.
"""
recordedAfter: Time
"""
Measurement recorded before this date.
"""
recordedBefore: Time
): UsageTrendsMeasurementConnection
""" """
Find a user. Find a user.
""" """
...@@ -27349,6 +27335,61 @@ type UpdateSnippetPayload { ...@@ -27349,6 +27335,61 @@ type UpdateSnippetPayload {
scalar Upload scalar Upload
"""
Represents a recorded measurement (object count) for the Admins
"""
type UsageTrendsMeasurement {
"""
Object count.
"""
count: Int!
"""
The type of objects being measured.
"""
identifier: MeasurementIdentifier!
"""
The time the measurement was recorded.
"""
recordedAt: Time
}
"""
The connection type for UsageTrendsMeasurement.
"""
type UsageTrendsMeasurementConnection {
"""
A list of edges.
"""
edges: [UsageTrendsMeasurementEdge]
"""
A list of nodes.
"""
nodes: [UsageTrendsMeasurement]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type UsageTrendsMeasurementEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: UsageTrendsMeasurement
}
type User { type User {
""" """
Merge Requests assigned to the user. Merge Requests assigned to the user.
......
...@@ -2132,16 +2132,6 @@ A block of time for which a participant is on-call. ...@@ -2132,16 +2132,6 @@ A block of time for which a participant is on-call.
| `vulnerabilityScanners` | VulnerabilityScannerConnection | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. | | `vulnerabilityScanners` | VulnerabilityScannerConnection | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity from projects selected in Instance Security Dashboard. | | `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity from projects selected in Instance Security Dashboard. |
### InstanceStatisticsMeasurement
Represents a recorded measurement (object count) for the Admins.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `count` | Int! | Object count. |
| `identifier` | MeasurementIdentifier! | The type of objects being measured. |
| `recordedAt` | Time | The time the measurement was recorded. |
### Issue ### Issue
| Field | Type | Description | | Field | Type | Description |
...@@ -4214,6 +4204,16 @@ Autogenerated return type of UpdateSnippet. ...@@ -4214,6 +4204,16 @@ Autogenerated return type of UpdateSnippet.
| `spam` | Boolean | Indicates whether the operation was detected as definite spam. There is no option to resubmit the request with a CAPTCHA response. | | `spam` | Boolean | Indicates whether the operation was detected as definite spam. There is no option to resubmit the request with a CAPTCHA response. |
| `spamLogId` | Int | The spam log ID which must be passed along with a valid CAPTCHA response for an operation to be completed. Included only when an operation was not completed because "NeedsCaptchaResponse" is true. | | `spamLogId` | Int | The spam log ID which must be passed along with a valid CAPTCHA response for an operation to be completed. Included only when an operation was not completed because "NeedsCaptchaResponse" is true. |
### UsageTrendsMeasurement
Represents a recorded measurement (object count) for the Admins.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `count` | Int! | Object count. |
| `identifier` | MeasurementIdentifier! | The type of objects being measured. |
| `recordedAt` | Time | The time the measurement was recorded. |
### User ### User
| Field | Type | Description | | Field | Type | Description |
......
...@@ -40,22 +40,3 @@ in the categories shown in [Total counts](#total-counts). ...@@ -40,22 +40,3 @@ in the categories shown in [Total counts](#total-counts).
These charts help you visualize how rapidly these records are being created on your instance. These charts help you visualize how rapidly these records are being created on your instance.
![Instance Activity Pipelines chart](img/instance_activity_pipelines_chart_v13_6.png) ![Instance Activity Pipelines chart](img/instance_activity_pipelines_chart_v13_6.png)
### Enable or disable Usage Trends
In GitLab version 13.5 only, Usage Trends was under development and not ready for production use.
It was deployed behind a feature flag that was **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to enable it.
To enable it:
```ruby
Feature.enable(:instance_statistics)
```
To disable it:
```ruby
Feature.disable(:instance_statistics)
```
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module EE module EE
module Analytics module Analytics
module InstanceStatistics module UsageTrends
# Measurement EE mixin # Measurement EE mixin
# #
# This module is intended to encapsulate EE-specific model logic # This module is intended to encapsulate EE-specific model logic
......
...@@ -464,7 +464,7 @@ class License < ApplicationRecord ...@@ -464,7 +464,7 @@ class License < ApplicationRecord
def daily_billable_users_count def daily_billable_users_count
strong_memoize(:daily_billable_users_count) do strong_memoize(:daily_billable_users_count) do
::Analytics::InstanceStatistics::Measurement.find_latest_or_fallback(:billable_users).count ::Analytics::UsageTrends::Measurement.find_latest_or_fallback(:billable_users).count
end end
end end
......
...@@ -144,7 +144,7 @@ RSpec.describe LicenseMonitoringHelper do ...@@ -144,7 +144,7 @@ RSpec.describe LicenseMonitoringHelper do
it 'reports overage when the most recent billable user count is higher than the historical max active users' do it 'reports overage when the most recent billable user count is higher than the historical max active users' do
license = setup_license(starts_at: now - 3.months, expires_at: now + 9.months, max_users: 40) license = setup_license(starts_at: now - 3.months, expires_at: now + 9.months, max_users: 40)
create(:historical_data, recorded_at: license.expires_at - 2.months, active_user_count: 45) create(:historical_data, recorded_at: license.expires_at - 2.months, active_user_count: 45)
create(:instance_statistics_measurement, recorded_at: now - 1.day, identifier: :billable_users, count: 70) create(:usage_trends_measurement, recorded_at: now - 1.day, identifier: :billable_users, count: 70)
expect(helper.users_over_license).to eq(30) expect(helper.users_over_license).to eq(30)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::InstanceStatistics::Measurement do RSpec.describe Analytics::UsageTrends::Measurement do
describe '.identifier_query_mapping' do describe '.identifier_query_mapping' do
subject { described_class.identifier_query_mapping.keys } subject { described_class.identifier_query_mapping.keys }
......
...@@ -1122,28 +1122,28 @@ RSpec.describe License do ...@@ -1122,28 +1122,28 @@ RSpec.describe License do
end end
it 'returns the billable users count' do it 'returns the billable users count' do
create(:instance_statistics_measurement, identifier: :billable_users, count: 2) create(:usage_trends_measurement, identifier: :billable_users, count: 2)
expect(license.maximum_user_count).to eq(2) expect(license.maximum_user_count).to eq(2)
end end
it 'returns the daily billable users count when it is higher than historical data' do it 'returns the daily billable users count when it is higher than historical data' do
create(:historical_data, active_user_count: 50) create(:historical_data, active_user_count: 50)
create(:instance_statistics_measurement, identifier: :billable_users, count: 100) create(:usage_trends_measurement, identifier: :billable_users, count: 100)
expect(license.maximum_user_count).to eq(100) expect(license.maximum_user_count).to eq(100)
end end
it 'returns historical data when it is higher than the billable users count' do it 'returns historical data when it is higher than the billable users count' do
create(:historical_data, active_user_count: 100) create(:historical_data, active_user_count: 100)
create(:instance_statistics_measurement, identifier: :billable_users, count: 50) create(:usage_trends_measurement, identifier: :billable_users, count: 50)
expect(license.maximum_user_count).to eq(100) expect(license.maximum_user_count).to eq(100)
end end
it 'returns the correct value when historical data and billable users are equal' do it 'returns the correct value when historical data and billable users are equal' do
create(:historical_data, active_user_count: 100) create(:historical_data, active_user_count: 100)
create(:instance_statistics_measurement, identifier: :billable_users, count: 100) create(:usage_trends_measurement, identifier: :billable_users, count: 100)
expect(license.maximum_user_count).to eq(100) expect(license.maximum_user_count).to eq(100)
end end
...@@ -1157,9 +1157,9 @@ RSpec.describe License do ...@@ -1157,9 +1157,9 @@ RSpec.describe License do
end end
it 'uses only the most recent billable users entry' do it 'uses only the most recent billable users entry' do
create(:instance_statistics_measurement, recorded_at: license.expires_at - 3.months, identifier: :billable_users, count: 150) create(:usage_trends_measurement, recorded_at: license.expires_at - 3.months, identifier: :billable_users, count: 150)
create(:historical_data, recorded_at: license.expires_at - 3.months, active_user_count: 140) create(:historical_data, recorded_at: license.expires_at - 3.months, active_user_count: 140)
create(:instance_statistics_measurement, recorded_at: license.expires_at - 2.months, identifier: :billable_users, count: 100) create(:usage_trends_measurement, recorded_at: license.expires_at - 2.months, identifier: :billable_users, count: 100)
expect(license.maximum_user_count).to eq(140) expect(license.maximum_user_count).to eq(140)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Gitlab module Gitlab
module Analytics module Analytics
module InstanceStatistics module UsageTrends
class WorkersArgumentBuilder class WorkersArgumentBuilder
def initialize(measurement_identifiers: [], recorded_at: Time.zone.now) def initialize(measurement_identifiers: [], recorded_at: Time.zone.now)
@measurement_identifiers = measurement_identifiers @measurement_identifiers = measurement_identifiers
...@@ -35,11 +35,11 @@ module Gitlab ...@@ -35,11 +35,11 @@ module Gitlab
end end
def custom_min_max_queries def custom_min_max_queries
::Analytics::InstanceStatistics::Measurement.identifier_min_max_queries ::Analytics::UsageTrends::Measurement.identifier_min_max_queries
end end
def query_mappings def query_mappings
::Analytics::InstanceStatistics::Measurement.identifier_query_mapping ::Analytics::UsageTrends::Measurement.identifier_query_mapping
end end
end end
end end
......
...@@ -8391,10 +8391,10 @@ msgstr "" ...@@ -8391,10 +8391,10 @@ msgstr ""
msgid "Could not find iteration" msgid "Could not find iteration"
msgstr "" msgstr ""
msgid "Could not load instance counts. Please refresh the page to try again." msgid "Could not load the user chart. Please refresh the page to try again."
msgstr "" msgstr ""
msgid "Could not load the user chart. Please refresh the page to try again." msgid "Could not load usage counts. Please refresh the page to try again."
msgstr "" msgstr ""
msgid "Could not remove the trigger." msgid "Could not remove the trigger."
...@@ -16018,96 +16018,6 @@ msgstr "" ...@@ -16018,96 +16018,6 @@ msgstr ""
msgid "Instance overview" msgid "Instance overview"
msgstr "" msgstr ""
msgid "InstanceStatistics|Could not load the issues and merge requests chart. Please refresh the page to try again."
msgstr ""
msgid "InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again."
msgstr ""
msgid "InstanceStatistics|Could not load the projects and groups chart. Please refresh the page to try again."
msgstr ""
msgid "InstanceStatistics|Groups"
msgstr ""
msgid "InstanceStatistics|Issues"
msgstr ""
msgid "InstanceStatistics|Issues & Merge Requests"
msgstr ""
msgid "InstanceStatistics|Items"
msgstr ""
msgid "InstanceStatistics|Merge Requests"
msgstr ""
msgid "InstanceStatistics|Month"
msgstr ""
msgid "InstanceStatistics|No data available."
msgstr ""
msgid "InstanceStatistics|Pipelines"
msgstr ""
msgid "InstanceStatistics|Pipelines canceled"
msgstr ""
msgid "InstanceStatistics|Pipelines failed"
msgstr ""
msgid "InstanceStatistics|Pipelines skipped"
msgstr ""
msgid "InstanceStatistics|Pipelines succeeded"
msgstr ""
msgid "InstanceStatistics|Pipelines total"
msgstr ""
msgid "InstanceStatistics|Projects"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the cancelled pipelines"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the failed pipelines"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the issues"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the merge requests"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the skipped pipelines"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the successful pipelines"
msgstr ""
msgid "InstanceStatistics|There was an error fetching the total pipelines"
msgstr ""
msgid "InstanceStatistics|There was an error while loading the groups"
msgstr ""
msgid "InstanceStatistics|There was an error while loading the projects"
msgstr ""
msgid "InstanceStatistics|Total groups"
msgstr ""
msgid "InstanceStatistics|Total projects"
msgstr ""
msgid "InstanceStatistics|Total projects & groups"
msgstr ""
msgid "InstanceStatistics|Users"
msgstr ""
msgid "Integration" msgid "Integration"
msgstr "" msgstr ""
...@@ -32040,6 +31950,96 @@ msgstr "" ...@@ -32040,6 +31950,96 @@ msgstr ""
msgid "UsageQuota|out of %{formattedLimit} of your namespace storage" msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
msgstr "" msgstr ""
msgid "UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again."
msgstr ""
msgid "UsageTrends|Could not load the pipelines chart. Please refresh the page to try again."
msgstr ""
msgid "UsageTrends|Could not load the projects and groups chart. Please refresh the page to try again."
msgstr ""
msgid "UsageTrends|Groups"
msgstr ""
msgid "UsageTrends|Issues"
msgstr ""
msgid "UsageTrends|Issues & Merge Requests"
msgstr ""
msgid "UsageTrends|Items"
msgstr ""
msgid "UsageTrends|Merge Requests"
msgstr ""
msgid "UsageTrends|Month"
msgstr ""
msgid "UsageTrends|No data available."
msgstr ""
msgid "UsageTrends|Pipelines"
msgstr ""
msgid "UsageTrends|Pipelines canceled"
msgstr ""
msgid "UsageTrends|Pipelines failed"
msgstr ""
msgid "UsageTrends|Pipelines skipped"
msgstr ""
msgid "UsageTrends|Pipelines succeeded"
msgstr ""
msgid "UsageTrends|Pipelines total"
msgstr ""
msgid "UsageTrends|Projects"
msgstr ""
msgid "UsageTrends|There was an error fetching the cancelled pipelines"
msgstr ""
msgid "UsageTrends|There was an error fetching the failed pipelines"
msgstr ""
msgid "UsageTrends|There was an error fetching the issues"
msgstr ""
msgid "UsageTrends|There was an error fetching the merge requests"
msgstr ""
msgid "UsageTrends|There was an error fetching the skipped pipelines"
msgstr ""
msgid "UsageTrends|There was an error fetching the successful pipelines"
msgstr ""
msgid "UsageTrends|There was an error fetching the total pipelines"
msgstr ""
msgid "UsageTrends|There was an error while loading the groups"
msgstr ""
msgid "UsageTrends|There was an error while loading the projects"
msgstr ""
msgid "UsageTrends|Total groups"
msgstr ""
msgid "UsageTrends|Total projects"
msgstr ""
msgid "UsageTrends|Total projects & groups"
msgstr ""
msgid "UsageTrends|Users"
msgstr ""
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})" msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr "" msgstr ""
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Admin::InstanceStatisticsController do RSpec.describe Admin::UsageTrendsController do
let(:admin) { create(:user, :admin) } let(:admin) { create(:user, :admin) }
before do before do
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :instance_statistics_measurement, class: 'Analytics::InstanceStatistics::Measurement' do factory :usage_trends_measurement, class: 'Analytics::UsageTrends::Measurement' do
recorded_at { Time.now } recorded_at { Time.now }
identifier { :projects } identifier { :projects }
count { 1_000 } count { 1_000 }
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InstanceStatisticsCountChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = ` exports[`UsageTrendsCountChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = `
Array [ Array [
Object { Object {
"data": Array [ "data": Array [
...@@ -22,7 +22,7 @@ Array [ ...@@ -22,7 +22,7 @@ Array [
] ]
`; `;
exports[`InstanceStatisticsCountChart with data passes the data to the line chart 1`] = ` exports[`UsageTrendsCountChart with data passes the data to the line chart 1`] = `
Array [ Array [
Object { Object {
"data": Array [ "data": Array [
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue'; import UsageTrendsApp from '~/analytics/usage_trends/components/app.vue';
import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue'; import ProjectsAndGroupsChart from '~/analytics/usage_trends/components/projects_and_groups_chart.vue';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue'; import UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import ProjectsAndGroupsChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue'; import UsageTrendsCountChart from '~/analytics/usage_trends/components/usage_trends_count_chart.vue';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue'; import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
describe('InstanceStatisticsApp', () => { describe('UsageTrendsApp', () => {
let wrapper; let wrapper;
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(InstanceStatisticsApp); wrapper = shallowMount(UsageTrendsApp);
}; };
beforeEach(() => { beforeEach(() => {
...@@ -21,17 +21,17 @@ describe('InstanceStatisticsApp', () => { ...@@ -21,17 +21,17 @@ describe('InstanceStatisticsApp', () => {
wrapper = null; wrapper = null;
}); });
it('displays the instance counts component', () => { it('displays the usage counts component', () => {
expect(wrapper.find(InstanceCounts).exists()).toBe(true); expect(wrapper.find(UsageCounts).exists()).toBe(true);
}); });
['Pipelines', 'Issues & Merge Requests'].forEach((instance) => { ['Pipelines', 'Issues & Merge Requests'].forEach((usage) => {
it(`displays the ${instance} chart`, () => { it(`displays the ${usage} chart`, () => {
const chartTitles = wrapper const chartTitles = wrapper
.findAll(InstanceStatisticsCountChart) .findAll(UsageTrendsCountChart)
.wrappers.map((chartComponent) => chartComponent.props('chartTitle')); .wrappers.map((chartComponent) => chartComponent.props('chartTitle'));
expect(chartTitles).toContain(instance); expect(chartTitles).toContain(usage);
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import InstanceCounts from '~/analytics/instance_statistics/components/instance_counts.vue';
import MetricCard from '~/analytics/shared/components/metric_card.vue'; import MetricCard from '~/analytics/shared/components/metric_card.vue';
import { mockInstanceCounts } from '../mock_data'; import UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import { mockUsageCounts } from '../mock_data';
describe('InstanceCounts', () => { describe('UsageCounts', () => {
let wrapper; let wrapper;
const createComponent = ({ loading = false, data = {} } = {}) => { const createComponent = ({ loading = false, data = {} } = {}) => {
...@@ -15,7 +15,7 @@ describe('InstanceCounts', () => { ...@@ -15,7 +15,7 @@ describe('InstanceCounts', () => {
}, },
}; };
wrapper = shallowMount(InstanceCounts, { wrapper = shallowMount(UsageCounts, {
mocks: { $apollo }, mocks: { $apollo },
data() { data() {
return { return {
...@@ -44,11 +44,11 @@ describe('InstanceCounts', () => { ...@@ -44,11 +44,11 @@ describe('InstanceCounts', () => {
describe('with data', () => { describe('with data', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ data: { counts: mockInstanceCounts } }); createComponent({ data: { counts: mockUsageCounts } });
}); });
it('passes the counts data to the metric card', () => { it('passes the counts data to the metric card', () => {
expect(findMetricCard().props('metrics')).toEqual(mockInstanceCounts); expect(findMetricCard().props('metrics')).toEqual(mockUsageCounts);
}); });
}); });
}); });
...@@ -3,9 +3,9 @@ import { GlLineChart } from '@gitlab/ui/dist/charts'; ...@@ -3,9 +3,9 @@ import { GlLineChart } from '@gitlab/ui/dist/charts';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import ProjectsAndGroupChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue'; import ProjectsAndGroupChart from '~/analytics/usage_trends/components/projects_and_groups_chart.vue';
import groupsQuery from '~/analytics/instance_statistics/graphql/queries/groups.query.graphql'; import groupsQuery from '~/analytics/usage_trends/graphql/queries/groups.query.graphql';
import projectsQuery from '~/analytics/instance_statistics/graphql/queries/projects.query.graphql'; import projectsQuery from '~/analytics/usage_trends/graphql/queries/projects.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse } from '../apollo_mock_data'; import { mockQueryResponse } from '../apollo_mock_data';
import { mockCountsData2, roundedSortedCountsMonthlyChartData2 } from '../mock_data'; import { mockCountsData2, roundedSortedCountsMonthlyChartData2 } from '../mock_data';
......
...@@ -3,8 +3,8 @@ import { GlLineChart } from '@gitlab/ui/dist/charts'; ...@@ -3,8 +3,8 @@ import { GlLineChart } from '@gitlab/ui/dist/charts';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue'; import UsageTrendsCountChart from '~/analytics/usage_trends/components/usage_trends_count_chart.vue';
import statsQuery from '~/analytics/instance_statistics/graphql/queries/instance_count.query.graphql'; import statsQuery from '~/analytics/usage_trends/graphql/queries/usage_count.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse, mockApolloResponse } from '../apollo_mock_data'; import { mockQueryResponse, mockApolloResponse } from '../apollo_mock_data';
import { mockCountsData1 } from '../mock_data'; import { mockCountsData1 } from '../mock_data';
...@@ -15,7 +15,7 @@ localVue.use(VueApollo); ...@@ -15,7 +15,7 @@ localVue.use(VueApollo);
const loadChartErrorMessage = 'My load error message'; const loadChartErrorMessage = 'My load error message';
const noDataMessage = 'My no data message'; const noDataMessage = 'My no data message';
const queryResponseDataKey = 'instanceStatisticsMeasurements'; const queryResponseDataKey = 'usageTrendsMeasurements';
const identifier = 'MOCK_QUERY'; const identifier = 'MOCK_QUERY';
const mockQueryConfig = { const mockQueryConfig = {
identifier, identifier,
...@@ -33,12 +33,12 @@ const mockChartConfig = { ...@@ -33,12 +33,12 @@ const mockChartConfig = {
queries: [mockQueryConfig], queries: [mockQueryConfig],
}; };
describe('InstanceStatisticsCountChart', () => { describe('UsageTrendsCountChart', () => {
let wrapper; let wrapper;
let queryHandler; let queryHandler;
const createComponent = ({ responseHandler }) => { const createComponent = ({ responseHandler }) => {
return shallowMount(InstanceStatisticsCountChart, { return shallowMount(UsageTrendsCountChart, {
localVue, localVue,
apolloProvider: createMockApollo([[statsQuery, responseHandler]]), apolloProvider: createMockApollo([[statsQuery, responseHandler]]),
propsData: { ...mockChartConfig }, propsData: { ...mockChartConfig },
......
...@@ -3,8 +3,8 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts'; ...@@ -3,8 +3,8 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue'; import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
import usersQuery from '~/analytics/instance_statistics/graphql/queries/users.query.graphql'; import usersQuery from '~/analytics/usage_trends/graphql/queries/users.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse } from '../apollo_mock_data'; import { mockQueryResponse } from '../apollo_mock_data';
import { import {
......
export const mockInstanceCounts = [ export const mockUsageCounts = [
{ key: 'projects', value: 10, label: 'Projects' }, { key: 'projects', value: 10, label: 'Projects' },
{ key: 'groups', value: 20, label: 'Group' }, { key: 'groups', value: 20, label: 'Group' },
]; ];
......
...@@ -2,7 +2,7 @@ import { ...@@ -2,7 +2,7 @@ import {
getAverageByMonth, getAverageByMonth,
getEarliestDate, getEarliestDate,
generateDataKeys, generateDataKeys,
} from '~/analytics/instance_statistics/utils'; } from '~/analytics/usage_trends/utils';
import { import {
mockCountsData1, mockCountsData1,
mockCountsData2, mockCountsData2,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver do RSpec.describe Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) } let_it_be(:admin_user) { create(:user, :admin) }
...@@ -11,8 +11,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso ...@@ -11,8 +11,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
describe '#resolve' do describe '#resolve' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project_measurement_new) { create(:instance_statistics_measurement, :project_count, recorded_at: 2.days.ago) } let_it_be(:project_measurement_new) { create(:usage_trends_measurement, :project_count, recorded_at: 2.days.ago) }
let_it_be(:project_measurement_old) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago) } let_it_be(:project_measurement_old) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago) }
let(:arguments) { { identifier: 'projects' } } let(:arguments) { { identifier: 'projects' } }
...@@ -63,8 +63,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso ...@@ -63,8 +63,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
end end
context 'when requesting pipeline counts by pipeline status' do context 'when requesting pipeline counts by pipeline status' do
let_it_be(:pipelines_succeeded_measurement) { create(:instance_statistics_measurement, :pipelines_succeeded_count, recorded_at: 2.days.ago) } let_it_be(:pipelines_succeeded_measurement) { create(:usage_trends_measurement, :pipelines_succeeded_count, recorded_at: 2.days.ago) }
let_it_be(:pipelines_skipped_measurement) { create(:instance_statistics_measurement, :pipelines_skipped_count, recorded_at: 2.days.ago) } let_it_be(:pipelines_skipped_measurement) { create(:usage_trends_measurement, :pipelines_skipped_count, recorded_at: 2.days.ago) }
subject { resolve_measurements({ identifier: identifier }, { current_user: current_user }) } subject { resolve_measurements({ identifier: identifier }, { current_user: current_user }) }
......
...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do ...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do
it 'exposes all the existing identifier values' do it 'exposes all the existing identifier values' do
ee_only_identifiers = %w[billable_users] ee_only_identifiers = %w[billable_users]
identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.reject do |x| identifiers = Analytics::UsageTrends::Measurement.identifiers.keys.reject do |x|
ee_only_identifiers.include?(x) ee_only_identifiers.include?(x)
end.map(&:upcase) end.map(&:upcase)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do
subject { described_class } subject { described_class }
it { is_expected.to have_graphql_field(:recorded_at) } it { is_expected.to have_graphql_field(:recorded_at) }
...@@ -10,13 +10,13 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do ...@@ -10,13 +10,13 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
it { is_expected.to have_graphql_field(:count) } it { is_expected.to have_graphql_field(:count) }
describe 'authorization' do describe 'authorization' do
let_it_be(:measurement) { create(:instance_statistics_measurement, :project_count) } let_it_be(:measurement) { create(:usage_trends_measurement, :project_count) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:query) do let(:query) do
<<~GRAPHQL <<~GRAPHQL
query instanceStatisticsMeasurements($identifier: MeasurementIdentifier!) { query usageTrendsMeasurements($identifier: MeasurementIdentifier!) {
instanceStatisticsMeasurements(identifier: $identifier) { usageTrendsMeasurements(identifier: $identifier) {
nodes { nodes {
count count
identifier identifier
...@@ -36,7 +36,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do ...@@ -36,7 +36,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
context 'when the user is not admin' do context 'when the user is not admin' do
it 'returns no data' do it 'returns no data' do
expect(subject.dig('data', 'instanceStatisticsMeasurements')).to be_nil expect(subject.dig('data', 'usageTrendsMeasurements')).to be_nil
end end
end end
...@@ -48,7 +48,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do ...@@ -48,7 +48,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
end end
it 'returns data' do it 'returns data' do
expect(subject.dig('data', 'instanceStatisticsMeasurements', 'nodes')).not_to be_empty expect(subject.dig('data', 'usageTrendsMeasurements', 'nodes')).not_to be_empty
end end
end end
end end
......
...@@ -21,7 +21,7 @@ RSpec.describe GitlabSchema.types['Query'] do ...@@ -21,7 +21,7 @@ RSpec.describe GitlabSchema.types['Query'] do
user user
users users
issue issue
instance_statistics_measurements usage_trends_measurements
runner_platforms runner_platforms
] ]
...@@ -65,11 +65,11 @@ RSpec.describe GitlabSchema.types['Query'] do ...@@ -65,11 +65,11 @@ RSpec.describe GitlabSchema.types['Query'] do
end end
end end
describe 'instance_statistics_measurements field' do describe 'usage_trends_measurements field' do
subject { described_class.fields['instanceStatisticsMeasurements'] } subject { described_class.fields['usageTrendsMeasurements'] }
it 'returns instance statistics measurements' do it 'returns usage trends measurements' do
is_expected.to have_graphql_type(Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type) is_expected.to have_graphql_type(Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type)
end end
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do RSpec.describe Gitlab::Analytics::UsageTrends::WorkersArgumentBuilder do
context 'when no measurement identifiers are given' do context 'when no measurement identifiers are given' do
it 'returns empty array' do it 'returns empty array' do
expect(described_class.new(measurement_identifiers: []).execute).to be_empty expect(described_class.new(measurement_identifiers: []).execute).to be_empty
...@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do ...@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do
let_it_be(:project_3) { create(:project, namespace: user_1.namespace, creator: user_1) } let_it_be(:project_3) { create(:project, namespace: user_1.namespace, creator: user_1) }
let(:recorded_at) { 2.days.ago } let(:recorded_at) { 2.days.ago }
let(:projects_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:projects) } let(:projects_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:projects) }
let(:users_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:users) } let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:users) }
let(:measurement_identifiers) { [projects_measurement_identifier, users_measurement_identifier] } let(:measurement_identifiers) { [projects_measurement_identifier, users_measurement_identifier] }
subject { described_class.new(measurement_identifiers: measurement_identifiers, recorded_at: recorded_at).execute } subject { described_class.new(measurement_identifiers: measurement_identifiers, recorded_at: recorded_at).execute }
...@@ -46,19 +46,19 @@ RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do ...@@ -46,19 +46,19 @@ RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do
context 'when custom min and max queries are present' do context 'when custom min and max queries are present' do
let(:min_id) { User.second.id } let(:min_id) { User.second.id }
let(:max_id) { User.maximum(:id) } let(:max_id) { User.maximum(:id) }
let(:users_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:users) } let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:users) }
before do before do
create_list(:user, 2) create_list(:user, 2)
min_max_queries = { min_max_queries = {
::Analytics::InstanceStatistics::Measurement.identifiers[:users] => { ::Analytics::UsageTrends::Measurement.identifiers[:users] => {
minimum_query: -> { min_id }, minimum_query: -> { min_id },
maximum_query: -> { max_id } maximum_query: -> { max_id }
} }
} }
allow(::Analytics::InstanceStatistics::Measurement).to receive(:identifier_min_max_queries) { min_max_queries } allow(::Analytics::UsageTrends::Measurement).to receive(:identifier_min_max_queries) { min_max_queries }
end end
subject do subject do
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do RSpec.describe Analytics::UsageTrends::Measurement, type: :model do
describe 'validation' do describe 'validation' do
let!(:measurement) { create(:instance_statistics_measurement) } let!(:measurement) { create(:usage_trends_measurement) }
it { is_expected.to validate_presence_of(:recorded_at) } it { is_expected.to validate_presence_of(:recorded_at) }
it { is_expected.to validate_presence_of(:identifier) } it { is_expected.to validate_presence_of(:identifier) }
...@@ -33,9 +33,9 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do ...@@ -33,9 +33,9 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
end end
describe 'scopes' do describe 'scopes' do
let_it_be(:measurement_1) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago) } let_it_be(:measurement_1) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago) }
let_it_be(:measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 2.days.ago) } let_it_be(:measurement_2) { create(:usage_trends_measurement, :project_count, recorded_at: 2.days.ago) }
let_it_be(:measurement_3) { create(:instance_statistics_measurement, :group_count, recorded_at: 5.days.ago) } let_it_be(:measurement_3) { create(:usage_trends_measurement, :group_count, recorded_at: 5.days.ago) }
describe '.order_by_latest' do describe '.order_by_latest' do
subject { described_class.order_by_latest } subject { described_class.order_by_latest }
...@@ -101,15 +101,15 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do ...@@ -101,15 +101,15 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
describe '.find_latest_or_fallback' do describe '.find_latest_or_fallback' do
subject(:count) { described_class.find_latest_or_fallback(:pipelines_skipped).count } subject(:count) { described_class.find_latest_or_fallback(:pipelines_skipped).count }
context 'with instance statistics' do context 'with usage statistics' do
let!(:measurement) { create(:instance_statistics_measurement, :pipelines_skipped_count) } let!(:measurement) { create(:usage_trends_measurement, :pipelines_skipped_count) }
it 'returns the latest stored measurement' do it 'returns the latest stored measurement' do
expect(count).to eq measurement.count expect(count).to eq measurement.count
end end
end end
context 'without instance statistics' do context 'without usage statistics' do
it 'returns the realtime query of the measurement' do it 'returns the realtime query of the measurement' do
expect(count).to eq 0 expect(count).to eq 0
end end
......
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'InstanceStatisticsMeasurements' do RSpec.describe 'UsageTrendsMeasurements' do
include GraphqlHelpers include GraphqlHelpers
let(:current_user) { create(:user, :admin) } let(:current_user) { create(:user, :admin) }
let!(:instance_statistics_measurement_1) { create(:instance_statistics_measurement, :project_count, recorded_at: 20.days.ago, count: 5) } let!(:usage_trends_measurement_1) { create(:usage_trends_measurement, :project_count, recorded_at: 20.days.ago, count: 5) }
let!(:instance_statistics_measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago, count: 10) } let!(:usage_trends_measurement_2) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago, count: 10) }
let(:arguments) { 'identifier: PROJECTS' } let(:arguments) { 'identifier: PROJECTS' }
let(:query) { graphql_query_for(:instanceStatisticsMeasurements, arguments, 'nodes { count identifier }') } let(:query) { graphql_query_for(:UsageTrendsMeasurements, arguments, 'nodes { count identifier }') }
before do before do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
it 'returns measurement objects' do it 'returns measurement objects' do
expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([ expect(graphql_data.dig('usageTrendsMeasurements', 'nodes')).to eq([
{ "count" => 10, 'identifier' => 'PROJECTS' }, { "count" => 10, 'identifier' => 'PROJECTS' },
{ "count" => 5, 'identifier' => 'PROJECTS' } { "count" => 5, 'identifier' => 'PROJECTS' }
]) ])
...@@ -27,7 +27,7 @@ RSpec.describe 'InstanceStatisticsMeasurements' do ...@@ -27,7 +27,7 @@ RSpec.describe 'InstanceStatisticsMeasurements' do
let(:arguments) { %(identifier: PROJECTS, recordedAfter: "#{15.days.ago.to_date}", recordedBefore: "#{5.days.ago.to_date}") } let(:arguments) { %(identifier: PROJECTS, recordedAfter: "#{15.days.ago.to_date}", recordedBefore: "#{5.days.ago.to_date}") }
it 'returns filtered measurement objects' do it 'returns filtered measurement objects' do
expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([ expect(graphql_data.dig('usageTrendsMeasurements', 'nodes')).to eq([
{ "count" => 10, 'identifier' => 'PROJECTS' } { "count" => 10, 'identifier' => 'PROJECTS' }
]) ])
end end
......
...@@ -6,12 +6,12 @@ RSpec.describe Analytics::InstanceStatistics::CountJobTriggerWorker do ...@@ -6,12 +6,12 @@ RSpec.describe Analytics::InstanceStatistics::CountJobTriggerWorker do
it_behaves_like 'an idempotent worker' it_behaves_like 'an idempotent worker'
context 'triggers a job for each measurement identifiers' do context 'triggers a job for each measurement identifiers' do
let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifier_query_mapping.keys.size } let(:expected_count) { Analytics::UsageTrends::Measurement.identifier_query_mapping.keys.size }
it 'triggers CounterJobWorker jobs' do it 'triggers CounterJobWorker jobs' do
subject.perform subject.perform
expect(Analytics::InstanceStatistics::CounterJobWorker.jobs.count).to eq(expected_count) expect(Analytics::UsageTrends::CounterJobWorker.jobs.count).to eq(expected_count)
end end
end end
end end
...@@ -6,7 +6,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do ...@@ -6,7 +6,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
let_it_be(:user_1) { create(:user) } let_it_be(:user_1) { create(:user) }
let_it_be(:user_2) { create(:user) } let_it_be(:user_2) { create(:user) }
let(:users_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:users) } let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:users) }
let(:recorded_at) { Time.zone.now } let(:recorded_at) { Time.zone.now }
let(:job_args) { [users_measurement_identifier, user_1.id, user_2.id, recorded_at] } let(:job_args) { [users_measurement_identifier, user_1.id, user_2.id, recorded_at] }
...@@ -18,7 +18,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do ...@@ -18,7 +18,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
it 'counts a scope and stores the result' do it 'counts a scope and stores the result' do
subject subject
measurement = Analytics::InstanceStatistics::Measurement.users.first measurement = Analytics::UsageTrends::Measurement.users.first
expect(measurement.recorded_at).to be_like_time(recorded_at) expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('users') expect(measurement.identifier).to eq('users')
expect(measurement.count).to eq(2) expect(measurement.count).to eq(2)
...@@ -26,14 +26,14 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do ...@@ -26,14 +26,14 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
end end
context 'when no records are in the database' do context 'when no records are in the database' do
let(:users_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:groups) } let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:groups) }
subject { described_class.new.perform(users_measurement_identifier, nil, nil, recorded_at) } subject { described_class.new.perform(users_measurement_identifier, nil, nil, recorded_at) }
it 'sets 0 as the count' do it 'sets 0 as the count' do
subject subject
measurement = Analytics::InstanceStatistics::Measurement.groups.first measurement = Analytics::UsageTrends::Measurement.groups.first
expect(measurement.recorded_at).to be_like_time(recorded_at) expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('groups') expect(measurement.identifier).to eq('groups')
expect(measurement.count).to eq(0) expect(measurement.count).to eq(0)
...@@ -49,19 +49,19 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do ...@@ -49,19 +49,19 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
it 'does not insert anything when BatchCount returns error' do it 'does not insert anything when BatchCount returns error' do
allow(Gitlab::Database::BatchCount).to receive(:batch_count).and_return(Gitlab::Database::BatchCounter::FALLBACK) allow(Gitlab::Database::BatchCount).to receive(:batch_count).and_return(Gitlab::Database::BatchCounter::FALLBACK)
expect { subject }.not_to change { Analytics::InstanceStatistics::Measurement.count } expect { subject }.not_to change { Analytics::UsageTrends::Measurement.count }
end end
context 'when pipelines_succeeded identifier is passed' do context 'when pipelines_succeeded identifier is passed' do
let_it_be(:pipeline) { create(:ci_pipeline, :success) } let_it_be(:pipeline) { create(:ci_pipeline, :success) }
let(:successful_pipelines_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:pipelines_succeeded) } let(:successful_pipelines_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:pipelines_succeeded) }
let(:job_args) { [successful_pipelines_measurement_identifier, pipeline.id, pipeline.id, recorded_at] } let(:job_args) { [successful_pipelines_measurement_identifier, pipeline.id, pipeline.id, recorded_at] }
it 'counts successful pipelines' do it 'counts successful pipelines' do
subject subject
measurement = Analytics::InstanceStatistics::Measurement.pipelines_succeeded.first measurement = Analytics::UsageTrends::Measurement.pipelines_succeeded.first
expect(measurement.recorded_at).to be_like_time(recorded_at) expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('pipelines_succeeded') expect(measurement.identifier).to eq('pipelines_succeeded')
expect(measurement.count).to eq(1) expect(measurement.count).to eq(1)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::UsageTrends::CountJobTriggerWorker do
it_behaves_like 'an idempotent worker'
context 'triggers a job for each measurement identifiers' do
let(:expected_count) { Analytics::UsageTrends::Measurement.identifier_query_mapping.keys.size }
it 'triggers CounterJobWorker jobs' do
subject.perform
expect(Analytics::UsageTrends::CounterJobWorker.jobs.count).to eq(expected_count)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::UsageTrends::CounterJobWorker do
let_it_be(:user_1) { create(:user) }
let_it_be(:user_2) { create(:user) }
let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:users) }
let(:recorded_at) { Time.zone.now }
let(:job_args) { [users_measurement_identifier, user_1.id, user_2.id, recorded_at] }
before do
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
end
include_examples 'an idempotent worker' do
it 'counts a scope and stores the result' do
subject
measurement = Analytics::UsageTrends::Measurement.users.first
expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('users')
expect(measurement.count).to eq(2)
end
end
context 'when no records are in the database' do
let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:groups) }
subject { described_class.new.perform(users_measurement_identifier, nil, nil, recorded_at) }
it 'sets 0 as the count' do
subject
measurement = Analytics::UsageTrends::Measurement.groups.first
expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('groups')
expect(measurement.count).to eq(0)
end
end
it 'does not raise error when inserting duplicated measurement' do
subject
expect { subject }.not_to raise_error
end
it 'does not insert anything when BatchCount returns error' do
allow(Gitlab::Database::BatchCount).to receive(:batch_count).and_return(Gitlab::Database::BatchCounter::FALLBACK)
expect { subject }.not_to change { Analytics::UsageTrends::Measurement.count }
end
context 'when pipelines_succeeded identifier is passed' do
let_it_be(:pipeline) { create(:ci_pipeline, :success) }
let(:successful_pipelines_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:pipelines_succeeded) }
let(:job_args) { [successful_pipelines_measurement_identifier, pipeline.id, pipeline.id, recorded_at] }
it 'counts successful pipelines' do
subject
measurement = Analytics::UsageTrends::Measurement.pipelines_succeeded.first
expect(measurement.recorded_at).to be_like_time(recorded_at)
expect(measurement.identifier).to eq('pipelines_succeeded')
expect(measurement.count).to eq(1)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment