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>
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
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 UsageCounts from './usage_counts.vue';
import UsageTrendsCountChart from './usage_trends_count_chart.vue';
import UsersChart from './users_chart.vue';
export default {
name: 'InstanceStatisticsApp',
name: 'UsageTrendsApp',
components: {
InstanceCounts,
InstanceStatisticsCountChart,
UsageCounts,
UsageTrendsCountChart,
UsersChart,
ProjectsAndGroupsChart,
},
......@@ -23,7 +23,7 @@ export default {
<template>
<div>
<instance-counts />
<usage-counts />
<users-chart
:start-date="$options.START_DATE"
:end-date="$options.TODAY"
......@@ -34,7 +34,7 @@ export default {
:end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/>
<instance-statistics-count-chart
<usage-trends-count-chart
v-for="chartOptions in $options.configs"
:key="chartOptions.chartTitle"
:queries="chartOptions.queries"
......
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 [
{
loadChartError: sprintf(
s__(
'InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again.',
),
s__('UsageTrends|Could not load the pipelines chart. Please refresh the page to try again.'),
),
noDataMessage,
chartTitle: s__('InstanceStatistics|Pipelines'),
yAxisTitle: s__('InstanceStatistics|Items'),
xAxisTitle: s__('InstanceStatistics|Month'),
chartTitle: s__('UsageTrends|Pipelines'),
yAxisTitle: s__('UsageTrends|Items'),
xAxisTitle: s__('UsageTrends|Month'),
queries: [
{
query,
title: s__('InstanceStatistics|Pipelines total'),
title: s__('UsageTrends|Pipelines total'),
identifier: 'PIPELINES',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the total pipelines'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the total pipelines')),
},
{
query,
title: s__('InstanceStatistics|Pipelines succeeded'),
title: s__('UsageTrends|Pipelines succeeded'),
identifier: 'PIPELINES_SUCCEEDED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the successful pipelines'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the successful pipelines')),
},
{
query,
title: s__('InstanceStatistics|Pipelines failed'),
title: s__('UsageTrends|Pipelines failed'),
identifier: 'PIPELINES_FAILED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the failed pipelines'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the failed pipelines')),
},
{
query,
title: s__('InstanceStatistics|Pipelines canceled'),
title: s__('UsageTrends|Pipelines canceled'),
identifier: 'PIPELINES_CANCELED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the cancelled pipelines'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the cancelled pipelines')),
},
{
query,
title: s__('InstanceStatistics|Pipelines skipped'),
title: s__('UsageTrends|Pipelines skipped'),
identifier: 'PIPELINES_SKIPPED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the skipped pipelines'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the skipped pipelines')),
},
],
},
{
loadChartError: sprintf(
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,
chartTitle: s__('InstanceStatistics|Issues & Merge Requests'),
yAxisTitle: s__('InstanceStatistics|Items'),
xAxisTitle: s__('InstanceStatistics|Month'),
chartTitle: s__('UsageTrends|Issues & Merge Requests'),
yAxisTitle: s__('UsageTrends|Items'),
xAxisTitle: s__('UsageTrends|Month'),
queries: [
{
query,
title: __('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,
title: __('Merge requests'),
identifier: 'MERGE_REQUESTS',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the merge requests'),
),
loadError: sprintf(s__('UsageTrends|There was an error fetching the merge requests')),
},
],
},
......
......@@ -113,14 +113,14 @@ export default {
},
},
i18n: {
yAxisTitle: s__('InstanceStatistics|Total projects & groups'),
yAxisTitle: s__('UsageTrends|Total projects & groups'),
xAxisTitle: __('Month'),
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'),
loadGroupsDataError: s__('InstanceStatistics|There was an error while loading the groups'),
noDataMessage: s__('InstanceStatistics|No data available.'),
loadProjectsDataError: s__('UsageTrends|There was an error while loading the projects'),
loadGroupsDataError: s__('UsageTrends|There was an error while loading the groups'),
noDataMessage: s__('UsageTrends|No data available.'),
},
computed: {
isLoadingGroups() {
......@@ -161,11 +161,11 @@ export default {
chartData() {
return [
{
name: s__('InstanceStatistics|Total projects'),
name: s__('UsageTrends|Total projects'),
data: this.projectChartData,
},
{
name: s__('InstanceStatistics|Total groups'),
name: s__('UsageTrends|Total groups'),
data: this.groupChartData,
},
];
......
......@@ -4,12 +4,12 @@ import { deprecatedCreateFlash as createFlash } from '~/flash';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { s__ } from '~/locale';
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;
export default {
name: 'InstanceCounts',
name: 'UsageCounts',
components: {
MetricCard,
},
......@@ -20,7 +20,7 @@ export default {
},
apollo: {
counts: {
query: instanceStatisticsCountQuery,
query: usageTrendsCountQuery,
update(data) {
return Object.entries(data).map(([key, obj]) => {
const label = this.$options.i18n.labels[key];
......@@ -42,14 +42,14 @@ export default {
},
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'),
users: s__('UsageTrends|Users'),
projects: s__('UsageTrends|Projects'),
groups: s__('UsageTrends|Groups'),
issues: s__('UsageTrends|Issues'),
mergeRequests: s__('UsageTrends|Merge Requests'),
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>
......
......@@ -12,10 +12,10 @@ import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleto
import { TODAY, START_DATE } from '../constants';
import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';
const QUERY_DATA_KEY = 'instanceStatisticsMeasurements';
const QUERY_DATA_KEY = 'usageTrendsMeasurements';
export default {
name: 'InstanceStatisticsCountChart',
name: 'UsageTrendsCountChart',
components: {
GlLineChart,
GlAlert,
......
......@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql"
query getGroupsCount($first: Int, $after: String) {
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: $first, after: $after) {
groups: usageTrendsMeasurements(identifier: GROUPS, first: $first, after: $after) {
nodes {
...Count
}
......
......@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql"
query getProjectsCount($first: Int, $after: String) {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: $first, after: $after) {
projects: usageTrendsMeasurements(identifier: PROJECTS, first: $first, after: $after) {
nodes {
...Count
}
......
......@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql"
query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) {
instanceStatisticsMeasurements(identifier: $identifier, first: $first, after: $after) {
usageTrendsMeasurements(identifier: $identifier, first: $first, after: $after) {
nodes {
...Count
}
......
#import "../fragments/count.fragment.graphql"
query getInstanceCounts {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: 1) {
projects: usageTrendsMeasurements(identifier: PROJECTS, first: 1) {
nodes {
...Count
}
}
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: 1) {
groups: usageTrendsMeasurements(identifier: GROUPS, first: 1) {
nodes {
...Count
}
}
users: instanceStatisticsMeasurements(identifier: USERS, first: 1) {
users: usageTrendsMeasurements(identifier: USERS, first: 1) {
nodes {
...Count
}
}
issues: instanceStatisticsMeasurements(identifier: ISSUES, first: 1) {
issues: usageTrendsMeasurements(identifier: ISSUES, first: 1) {
nodes {
...Count
}
}
mergeRequests: instanceStatisticsMeasurements(identifier: MERGE_REQUESTS, first: 1) {
mergeRequests: usageTrendsMeasurements(identifier: MERGE_REQUESTS, first: 1) {
nodes {
...Count
}
}
pipelines: instanceStatisticsMeasurements(identifier: PIPELINES, first: 1) {
pipelines: usageTrendsMeasurements(identifier: PIPELINES, first: 1) {
nodes {
...Count
}
......
......@@ -2,7 +2,7 @@
#import "../fragments/count.fragment.graphql"
query getUsersCount($first: Int, $after: String) {
users: instanceStatisticsMeasurements(identifier: USERS, first: $first, after: $after) {
users: usageTrendsMeasurements(identifier: USERS, first: $first, after: $after) {
nodes {
...Count
}
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import InstanceStatisticsApp from './components/app.vue';
import UsageTrendsApp from './components/app.vue';
Vue.use(VueApollo);
......@@ -10,7 +10,7 @@ const apolloProvider = new VueApollo({
});
export default () => {
const el = document.getElementById('js-instance-statistics-app');
const el = document.getElementById('js-usage-trends-app');
if (!el) return false;
......@@ -18,7 +18,7 @@ export default () => {
el,
apolloProvider,
render(h) {
return h(InstanceStatisticsApp);
return h(UsageTrendsApp);
},
});
};
......@@ -41,8 +41,8 @@ export function getAverageByMonth(items = [], options = {}) {
}
/**
* Takes an array of instance counts and returns the last item in the list
* @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String }
* Takes an array of usage counts and returns the last item in the list
* @param {Array} arr array of usage counts in the form { count: Number, recordedAt: date String }
* @return {String} the 'recordedAt' value of the earliest item
*/
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
* and a supplied defaultValue as its value
* @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
* @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
class Admin::InstanceStatisticsController < Admin::ApplicationController
class Admin::UsageTrendsController < Admin::ApplicationController
include Analytics::UniqueVisitsHelper
before_action :check_feature_flag
track_unique_visits :index, target_id: 'i_analytics_instance_statistics'
feature_category :devops_reports
def index
end
def check_feature_flag
render_404 unless Feature.enabled?(:instance_statistics, default_enabled: true)
end
end
......@@ -3,13 +3,13 @@
module Resolvers
module Admin
module Analytics
module InstanceStatistics
module UsageTrends
class MeasurementsResolver < BaseResolver
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,
description: 'The type of measurement/statistics to retrieve.'
......@@ -24,7 +24,7 @@ module Resolvers
def resolve(identifier:, recorded_before: nil, recorded_after: nil)
authorize!
::Analytics::InstanceStatistics::Measurement
::Analytics::UsageTrends::Measurement
.recorded_after(recorded_after)
.recorded_before(recorded_before)
.with_identifier(identifier)
......
......@@ -3,7 +3,7 @@
module Types
module Admin
module Analytics
module InstanceStatistics
module UsageTrends
class MeasurementIdentifierEnum < BaseEnum
graphql_name 'MeasurementIdentifier'
description 'Possible identifier types for a measurement'
......
......@@ -3,13 +3,13 @@
module Types
module Admin
module Analytics
module InstanceStatistics
module UsageTrends
class MeasurementType < BaseObject
include Gitlab::Graphql::Authorize::AuthorizeResource
graphql_name 'InstanceStatisticsMeasurement'
graphql_name 'UsageTrendsMeasurement'
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,
description: 'The time the measurement was recorded.'
......@@ -17,7 +17,7 @@ module Types
field :count, GraphQL::INT_TYPE, null: false,
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.'
end
end
......
......@@ -82,10 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
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,
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,
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
module Analytics
module InstanceStatistics
module UsageTrends
class Measurement < ApplicationRecord
self.table_name = 'analytics_instance_statistics_measurements'
EXPERIMENTAL_IDENTIFIERS = %i[pipelines_succeeded pipelines_failed pipelines_canceled pipelines_skipped].freeze
enum identifier: {
......@@ -58,4 +60,4 @@ module Analytics
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
module Analytics
module InstanceStatistics
module UsageTrends
class MeasurementPolicy < BasePolicy
delegate { :global }
end
......
......@@ -100,7 +100,7 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute
enable :approve_user
enable :reject_user
enable :read_instance_statistics_measurements
enable :read_usage_trends_measurement
end
# We can't use `read_statistics` because the user may have different permissions for different projects
......
- breadcrumb_title _("Usage Trends")
- page_title _("Usage Trends")
#js-instance-statistics-app
#js-usage-trends-app
......@@ -65,11 +65,10 @@
= link_to admin_dev_ops_report_path, title: _('DevOps Report') do
%span
= _('DevOps Report')
- if Feature.enabled?(:instance_statistics, default_enabled: true)
= nav_link(controller: :instance_statistics) do
= link_to admin_instance_statistics_path, title: _('Usage Trends') do
%span
= _('Usage Trends')
= nav_link(controller: :usage_trends) do
= link_to admin_usage_trends_path, title: _('Usage Trends') do
%span
= _('Usage Trends')
= nav_link(controller: admin_monitoring_nav_links) do
= link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' } do
......
......@@ -131,6 +131,14 @@
:weight: 1
:idempotent: true
: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
:feature_category: :source_code_management
:has_external_dependencies:
......@@ -1413,6 +1421,14 @@
:weight: 1
:idempotent: true
: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
:feature_category: :users
:has_external_dependencies:
......
......@@ -2,31 +2,19 @@
module Analytics
module InstanceStatistics
# This worker will be removed in 14.0
class CountJobTriggerWorker
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::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
# Delegate to the new worker
Analytics::UsageTrends::CountJobTriggerWorker.new.perform
end
end
end
......
......@@ -2,6 +2,7 @@
module Analytics
module InstanceStatistics
# This worker will be removed in 14.0
class CounterJobWorker
include ApplicationWorker
......@@ -10,24 +11,9 @@ module Analytics
idempotent!
def perform(measurement_identifier, min_id, max_id, recorded_at)
query_scope = ::Analytics::InstanceStatistics::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
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)
def perform(*args)
# Delegate to the new worker
Analytics::UsageTrends::CounterJobWorker.new.perform(*args)
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
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']['job_class'] = 'CiPlatformMetricsUpdateCronWorker'
Settings.cron_jobs['analytics_instance_statistics_count_job_trigger_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['analytics_instance_statistics_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'] ||= Settingslogic.new({})
Settings.cron_jobs['analytics_usage_trends_count_job_trigger_worker']['cron'] ||= '50 23 */1 * *'
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']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['member_invitation_reminder_emails_worker']['job_class'] = 'MemberInvitationReminderEmailsWorker'
......
......@@ -95,7 +95,8 @@ namespace :admin do
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
resources :cohorts, only: :index
......
......@@ -34,6 +34,8 @@
- 1
- - analytics_instance_statistics_counter_job
- 1
- - analytics_usage_trends_counter_job
- 1
- - approve_blocked_pending_approval_users
- 1
- - authorized_keys
......
......@@ -7,7 +7,7 @@ Gitlab::Seeder.quiet do
max_increase = 10000
max_decrease = 1000
model_class = Analytics::InstanceStatistics::Measurement
model_class = Analytics::UsageTrends::Measurement
measurements = model_class.identifiers.flat_map do |_, id|
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 {
): 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
"""
......@@ -21681,7 +21626,8 @@ type Query {
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(
"""
......@@ -21718,7 +21664,7 @@ type Query {
Measurement recorded before this date.
"""
recordedBefore: Time
): InstanceStatisticsMeasurementConnection
): UsageTrendsMeasurementConnection @deprecated(reason: "This field was renamed. Use the `usageTrendsMeasurements` field instead.. Deprecated in 13.10.")
"""
Find an Issue.
......@@ -21940,6 +21886,46 @@ type Query {
visibility: VisibilityScopesEnum
): 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.
"""
......@@ -27349,6 +27335,61 @@ type UpdateSnippetPayload {
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 {
"""
Merge Requests assigned to the user.
......
......@@ -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. |
| `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
| Field | Type | Description |
......@@ -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. |
| `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
| Field | Type | Description |
......
......@@ -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.
![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 @@
module EE
module Analytics
module InstanceStatistics
module UsageTrends
# Measurement EE mixin
#
# This module is intended to encapsulate EE-specific model logic
......
......@@ -464,7 +464,7 @@ class License < ApplicationRecord
def daily_billable_users_count
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
......
......@@ -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
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(: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)
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::InstanceStatistics::Measurement do
RSpec.describe Analytics::UsageTrends::Measurement do
describe '.identifier_query_mapping' do
subject { described_class.identifier_query_mapping.keys }
......
......@@ -1122,28 +1122,28 @@ RSpec.describe License do
end
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)
end
it 'returns the daily billable users count when it is higher than historical data' do
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)
end
it 'returns historical data when it is higher than the billable users count' do
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)
end
it 'returns the correct value when historical data and billable users are equal' do
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)
end
......@@ -1157,9 +1157,9 @@ RSpec.describe License do
end
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(: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)
end
......
......@@ -2,7 +2,7 @@
module Gitlab
module Analytics
module InstanceStatistics
module UsageTrends
class WorkersArgumentBuilder
def initialize(measurement_identifiers: [], recorded_at: Time.zone.now)
@measurement_identifiers = measurement_identifiers
......@@ -35,11 +35,11 @@ module Gitlab
end
def custom_min_max_queries
::Analytics::InstanceStatistics::Measurement.identifier_min_max_queries
::Analytics::UsageTrends::Measurement.identifier_min_max_queries
end
def query_mappings
::Analytics::InstanceStatistics::Measurement.identifier_query_mapping
::Analytics::UsageTrends::Measurement.identifier_query_mapping
end
end
end
......
......@@ -8391,10 +8391,10 @@ msgstr ""
msgid "Could not find iteration"
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 ""
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 ""
msgid "Could not remove the trigger."
......@@ -16018,96 +16018,6 @@ msgstr ""
msgid "Instance overview"
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"
msgstr ""
......@@ -32040,6 +31950,96 @@ msgstr ""
msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
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})"
msgstr ""
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Admin::InstanceStatisticsController do
RSpec.describe Admin::UsageTrendsController do
let(:admin) { create(:user, :admin) }
before do
......
# frozen_string_literal: true
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 }
identifier { :projects }
count { 1_000 }
......
// 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 [
Object {
"data": 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 [
Object {
"data": Array [
......
import { shallowMount } from '@vue/test-utils';
import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue';
import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
import ProjectsAndGroupsChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
import UsageTrendsApp from '~/analytics/usage_trends/components/app.vue';
import ProjectsAndGroupsChart from '~/analytics/usage_trends/components/projects_and_groups_chart.vue';
import UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import UsageTrendsCountChart from '~/analytics/usage_trends/components/usage_trends_count_chart.vue';
import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
describe('InstanceStatisticsApp', () => {
describe('UsageTrendsApp', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(InstanceStatisticsApp);
wrapper = shallowMount(UsageTrendsApp);
};
beforeEach(() => {
......@@ -21,17 +21,17 @@ describe('InstanceStatisticsApp', () => {
wrapper = null;
});
it('displays the instance counts component', () => {
expect(wrapper.find(InstanceCounts).exists()).toBe(true);
it('displays the usage counts component', () => {
expect(wrapper.find(UsageCounts).exists()).toBe(true);
});
['Pipelines', 'Issues & Merge Requests'].forEach((instance) => {
it(`displays the ${instance} chart`, () => {
['Pipelines', 'Issues & Merge Requests'].forEach((usage) => {
it(`displays the ${usage} chart`, () => {
const chartTitles = wrapper
.findAll(InstanceStatisticsCountChart)
.findAll(UsageTrendsCountChart)
.wrappers.map((chartComponent) => chartComponent.props('chartTitle'));
expect(chartTitles).toContain(instance);
expect(chartTitles).toContain(usage);
});
});
......
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 { 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;
const createComponent = ({ loading = false, data = {} } = {}) => {
......@@ -15,7 +15,7 @@ describe('InstanceCounts', () => {
},
};
wrapper = shallowMount(InstanceCounts, {
wrapper = shallowMount(UsageCounts, {
mocks: { $apollo },
data() {
return {
......@@ -44,11 +44,11 @@ describe('InstanceCounts', () => {
describe('with data', () => {
beforeEach(() => {
createComponent({ data: { counts: mockInstanceCounts } });
createComponent({ data: { counts: mockUsageCounts } });
});
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';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import ProjectsAndGroupChart from '~/analytics/instance_statistics/components/projects_and_groups_chart.vue';
import groupsQuery from '~/analytics/instance_statistics/graphql/queries/groups.query.graphql';
import projectsQuery from '~/analytics/instance_statistics/graphql/queries/projects.query.graphql';
import ProjectsAndGroupChart from '~/analytics/usage_trends/components/projects_and_groups_chart.vue';
import groupsQuery from '~/analytics/usage_trends/graphql/queries/groups.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 { mockQueryResponse } from '../apollo_mock_data';
import { mockCountsData2, roundedSortedCountsMonthlyChartData2 } from '../mock_data';
......
......@@ -3,8 +3,8 @@ import { GlLineChart } from '@gitlab/ui/dist/charts';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
import statsQuery from '~/analytics/instance_statistics/graphql/queries/instance_count.query.graphql';
import UsageTrendsCountChart from '~/analytics/usage_trends/components/usage_trends_count_chart.vue';
import statsQuery from '~/analytics/usage_trends/graphql/queries/usage_count.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse, mockApolloResponse } from '../apollo_mock_data';
import { mockCountsData1 } from '../mock_data';
......@@ -15,7 +15,7 @@ localVue.use(VueApollo);
const loadChartErrorMessage = 'My load error message';
const noDataMessage = 'My no data message';
const queryResponseDataKey = 'instanceStatisticsMeasurements';
const queryResponseDataKey = 'usageTrendsMeasurements';
const identifier = 'MOCK_QUERY';
const mockQueryConfig = {
identifier,
......@@ -33,12 +33,12 @@ const mockChartConfig = {
queries: [mockQueryConfig],
};
describe('InstanceStatisticsCountChart', () => {
describe('UsageTrendsCountChart', () => {
let wrapper;
let queryHandler;
const createComponent = ({ responseHandler }) => {
return shallowMount(InstanceStatisticsCountChart, {
return shallowMount(UsageTrendsCountChart, {
localVue,
apolloProvider: createMockApollo([[statsQuery, responseHandler]]),
propsData: { ...mockChartConfig },
......
......@@ -3,8 +3,8 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
import usersQuery from '~/analytics/instance_statistics/graphql/queries/users.query.graphql';
import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
import usersQuery from '~/analytics/usage_trends/graphql/queries/users.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockQueryResponse } from '../apollo_mock_data';
import {
......
export const mockInstanceCounts = [
export const mockUsageCounts = [
{ key: 'projects', value: 10, label: 'Projects' },
{ key: 'groups', value: 20, label: 'Group' },
];
......
......@@ -2,7 +2,7 @@ import {
getAverageByMonth,
getEarliestDate,
generateDataKeys,
} from '~/analytics/instance_statistics/utils';
} from '~/analytics/usage_trends/utils';
import {
mockCountsData1,
mockCountsData2,
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver do
RSpec.describe Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver do
include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) }
......@@ -11,8 +11,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
describe '#resolve' do
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_old) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.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(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago) }
let(:arguments) { { identifier: 'projects' } }
......@@ -63,8 +63,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
end
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_skipped_measurement) { create(:instance_statistics_measurement, :pipelines_skipped_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(:usage_trends_measurement, :pipelines_skipped_count, recorded_at: 2.days.ago) }
subject { resolve_measurements({ identifier: identifier }, { current_user: current_user }) }
......
......@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do
it 'exposes all the existing identifier values' do
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)
end.map(&:upcase)
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do
subject { described_class }
it { is_expected.to have_graphql_field(:recorded_at) }
......@@ -10,13 +10,13 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
it { is_expected.to have_graphql_field(:count) }
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(:query) do
<<~GRAPHQL
query instanceStatisticsMeasurements($identifier: MeasurementIdentifier!) {
instanceStatisticsMeasurements(identifier: $identifier) {
query usageTrendsMeasurements($identifier: MeasurementIdentifier!) {
usageTrendsMeasurements(identifier: $identifier) {
nodes {
count
identifier
......@@ -36,7 +36,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
context 'when the user is not admin' do
it 'returns no data' do
expect(subject.dig('data', 'instanceStatisticsMeasurements')).to be_nil
expect(subject.dig('data', 'usageTrendsMeasurements')).to be_nil
end
end
......@@ -48,7 +48,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
end
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
......
......@@ -21,7 +21,7 @@ RSpec.describe GitlabSchema.types['Query'] do
user
users
issue
instance_statistics_measurements
usage_trends_measurements
runner_platforms
]
......@@ -65,11 +65,11 @@ RSpec.describe GitlabSchema.types['Query'] do
end
end
describe 'instance_statistics_measurements field' do
subject { described_class.fields['instanceStatisticsMeasurements'] }
describe 'usage_trends_measurements field' do
subject { described_class.fields['usageTrendsMeasurements'] }
it 'returns instance statistics measurements' do
is_expected.to have_graphql_type(Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type)
it 'returns usage trends measurements' do
is_expected.to have_graphql_type(Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type)
end
end
......
......@@ -2,7 +2,7 @@
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
it 'returns empty array' do
expect(described_class.new(measurement_identifiers: []).execute).to be_empty
......@@ -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(:recorded_at) { 2.days.ago }
let(:projects_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:projects) }
let(:users_measurement_identifier) { ::Analytics::InstanceStatistics::Measurement.identifiers.fetch(:users) }
let(:projects_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:projects) }
let(:users_measurement_identifier) { ::Analytics::UsageTrends::Measurement.identifiers.fetch(:users) }
let(:measurement_identifiers) { [projects_measurement_identifier, users_measurement_identifier] }
subject { described_class.new(measurement_identifiers: measurement_identifiers, recorded_at: recorded_at).execute }
......@@ -46,19 +46,19 @@ RSpec.describe Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder do
context 'when custom min and max queries are present' do
let(:min_id) { User.second.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
create_list(:user, 2)
min_max_queries = {
::Analytics::InstanceStatistics::Measurement.identifiers[:users] => {
::Analytics::UsageTrends::Measurement.identifiers[:users] => {
minimum_query: -> { min_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
subject do
......
......@@ -2,9 +2,9 @@
require 'spec_helper'
RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
RSpec.describe Analytics::UsageTrends::Measurement, type: :model 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(:identifier) }
......@@ -33,9 +33,9 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
end
describe 'scopes' do
let_it_be(:measurement_1) { create(:instance_statistics_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_3) { create(:instance_statistics_measurement, :group_count, recorded_at: 5.days.ago) }
let_it_be(:measurement_1) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago) }
let_it_be(:measurement_2) { create(:usage_trends_measurement, :project_count, recorded_at: 2.days.ago) }
let_it_be(:measurement_3) { create(:usage_trends_measurement, :group_count, recorded_at: 5.days.ago) }
describe '.order_by_latest' do
subject { described_class.order_by_latest }
......@@ -101,15 +101,15 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
describe '.find_latest_or_fallback' do
subject(:count) { described_class.find_latest_or_fallback(:pipelines_skipped).count }
context 'with instance statistics' do
let!(:measurement) { create(:instance_statistics_measurement, :pipelines_skipped_count) }
context 'with usage statistics' do
let!(:measurement) { create(:usage_trends_measurement, :pipelines_skipped_count) }
it 'returns the latest stored measurement' do
expect(count).to eq measurement.count
end
end
context 'without instance statistics' do
context 'without usage statistics' do
it 'returns the realtime query of the measurement' do
expect(count).to eq 0
end
......
......@@ -2,22 +2,22 @@
require 'spec_helper'
RSpec.describe 'InstanceStatisticsMeasurements' do
RSpec.describe 'UsageTrendsMeasurements' do
include GraphqlHelpers
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!(:instance_statistics_measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago, count: 10) }
let!(:usage_trends_measurement_1) { create(:usage_trends_measurement, :project_count, recorded_at: 20.days.ago, count: 5) }
let!(:usage_trends_measurement_2) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago, count: 10) }
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
post_graphql(query, current_user: current_user)
end
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" => 5, 'identifier' => 'PROJECTS' }
])
......@@ -27,7 +27,7 @@ RSpec.describe 'InstanceStatisticsMeasurements' do
let(:arguments) { %(identifier: PROJECTS, recordedAfter: "#{15.days.ago.to_date}", recordedBefore: "#{5.days.ago.to_date}") }
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' }
])
end
......
......@@ -6,12 +6,12 @@ RSpec.describe Analytics::InstanceStatistics::CountJobTriggerWorker do
it_behaves_like 'an idempotent worker'
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
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
......@@ -6,7 +6,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
let_it_be(:user_1) { 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(:job_args) { [users_measurement_identifier, user_1.id, user_2.id, recorded_at] }
......@@ -18,7 +18,7 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
it 'counts a scope and stores the result' do
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.identifier).to eq('users')
expect(measurement.count).to eq(2)
......@@ -26,14 +26,14 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker do
end
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) }
it 'sets 0 as the count' do
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.identifier).to eq('groups')
expect(measurement.count).to eq(0)
......@@ -49,19 +49,19 @@ RSpec.describe Analytics::InstanceStatistics::CounterJobWorker 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)
expect { subject }.not_to change { Analytics::InstanceStatistics::Measurement.count }
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::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] }
it 'counts successful pipelines' do
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.identifier).to eq('pipelines_succeeded')
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