Add graphs and retention metrics to admin dashboard (#16829)
This commit is contained in:
parent
959f7fc580
commit
07341e7aa6
46 changed files with 1650 additions and 257 deletions
31
app/lib/admin/metrics/dimension/base_dimension.rb
Normal file
31
app/lib/admin/metrics/dimension/base_dimension.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::BaseDimension
|
||||
def initialize(start_at, end_at, limit)
|
||||
@start_at = start_at&.to_datetime
|
||||
@end_at = end_at&.to_datetime
|
||||
@limit = limit&.to_i
|
||||
end
|
||||
|
||||
def key
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def data
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def self.model_name
|
||||
self.class.name
|
||||
end
|
||||
|
||||
def read_attribute_for_serialization(key)
|
||||
send(key) if respond_to?(key)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def time_period
|
||||
(@start_at...@end_at)
|
||||
end
|
||||
end
|
23
app/lib/admin/metrics/dimension/languages_dimension.rb
Normal file
23
app/lib/admin/metrics/dimension/languages_dimension.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
def key
|
||||
'languages'
|
||||
end
|
||||
|
||||
def data
|
||||
sql = <<-SQL.squish
|
||||
SELECT locale, count(*) AS value
|
||||
FROM users
|
||||
WHERE current_sign_in_at BETWEEN $1 AND $2
|
||||
AND locale IS NOT NULL
|
||||
GROUP BY locale
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['locale'], human_key: SettingsHelper::HUMAN_LOCALES[row['locale'].to_sym], value: row['value'].to_s } }
|
||||
end
|
||||
end
|
23
app/lib/admin/metrics/dimension/servers_dimension.rb
Normal file
23
app/lib/admin/metrics/dimension/servers_dimension.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
def key
|
||||
'servers'
|
||||
end
|
||||
|
||||
def data
|
||||
sql = <<-SQL.squish
|
||||
SELECT accounts.domain, count(*) AS value
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
WHERE statuses.id BETWEEN $1 AND $2
|
||||
GROUP BY accounts.domain
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, Mastodon::Snowflake.id_at(@start_at)], [nil, Mastodon::Snowflake.id_at(@end_at)], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Redisable
|
||||
|
||||
def key
|
||||
'software_versions'
|
||||
end
|
||||
|
||||
def data
|
||||
[mastodon_version, ruby_version, postgresql_version, redis_version]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mastodon_version
|
||||
value = Mastodon::Version.to_s
|
||||
|
||||
{
|
||||
key: 'mastodon',
|
||||
human_key: 'Mastodon',
|
||||
value: value,
|
||||
human_value: value,
|
||||
}
|
||||
end
|
||||
|
||||
def ruby_version
|
||||
value = "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
|
||||
|
||||
{
|
||||
key: 'ruby',
|
||||
human_key: 'Ruby',
|
||||
value: value,
|
||||
human_value: value,
|
||||
}
|
||||
end
|
||||
|
||||
def postgresql_version
|
||||
value = ActiveRecord::Base.connection.execute('SELECT VERSION()').first['version'].match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
|
||||
|
||||
{
|
||||
key: 'postgresql',
|
||||
human_key: 'PostgreSQL',
|
||||
value: value,
|
||||
human_value: value,
|
||||
}
|
||||
end
|
||||
|
||||
def redis_version
|
||||
value = redis_info['redis_version']
|
||||
|
||||
{
|
||||
key: 'redis',
|
||||
human_key: 'Redis',
|
||||
value: value,
|
||||
human_value: value,
|
||||
}
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= begin
|
||||
if redis.is_a?(Redis::Namespace)
|
||||
redis.redis.info
|
||||
else
|
||||
redis.info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
app/lib/admin/metrics/dimension/sources_dimension.rb
Normal file
23
app/lib/admin/metrics/dimension/sources_dimension.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
def key
|
||||
'sources'
|
||||
end
|
||||
|
||||
def data
|
||||
sql = <<-SQL.squish
|
||||
SELECT oauth_applications.name, count(*) AS value
|
||||
FROM users
|
||||
LEFT JOIN oauth_applications ON oauth_applications.id = users.created_by_application_id
|
||||
WHERE users.created_at BETWEEN $1 AND $2
|
||||
GROUP BY oauth_applications.name
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['name'] || 'web', human_key: row['name'] || I18n.t('admin.dashboard.website'), value: row['value'].to_s } }
|
||||
end
|
||||
end
|
70
app/lib/admin/metrics/dimension/space_usage_dimension.rb
Normal file
70
app/lib/admin/metrics/dimension/space_usage_dimension.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Redisable
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
def key
|
||||
'space_usage'
|
||||
end
|
||||
|
||||
def data
|
||||
[postgresql_size, redis_size, media_size]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def postgresql_size
|
||||
value = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
|
||||
|
||||
{
|
||||
key: 'postgresql',
|
||||
human_key: 'PostgreSQL',
|
||||
value: value.to_s,
|
||||
unit: 'bytes',
|
||||
human_value: number_to_human_size(value),
|
||||
}
|
||||
end
|
||||
|
||||
def redis_size
|
||||
value = redis_info['used_memory']
|
||||
|
||||
{
|
||||
key: 'redis',
|
||||
human_key: 'Redis',
|
||||
value: value.to_s,
|
||||
unit: 'bytes',
|
||||
human_value: number_to_human_size(value),
|
||||
}
|
||||
end
|
||||
|
||||
def media_size
|
||||
value = [
|
||||
MediaAttachment.sum(Arel.sql('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)')),
|
||||
CustomEmoji.sum(:image_file_size),
|
||||
PreviewCard.sum(:image_file_size),
|
||||
Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')),
|
||||
Backup.sum(:dump_file_size),
|
||||
Import.sum(:data_file_size),
|
||||
SiteUpload.sum(:file_file_size),
|
||||
].sum
|
||||
|
||||
{
|
||||
key: 'media',
|
||||
human_key: I18n.t('admin.dashboard.media_storage'),
|
||||
value: value.to_s,
|
||||
unit: 'bytes',
|
||||
human_value: number_to_human_size(value),
|
||||
}
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= begin
|
||||
if redis.is_a?(Redis::Namespace)
|
||||
redis.redis.info
|
||||
else
|
||||
redis.info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue