From af135bddd0a17be9a1345216db7986b593d51151 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Mon, 5 Jun 2023 14:46:04 -0400
Subject: [PATCH] Update `Admin::Metrics::Measure` classes for Rails 7 (#25236)

---
 .../measure/instance_accounts_measure.rb      | 24 ++++++---------
 .../measure/instance_followers_measure.rb     | 24 ++++++---------
 .../measure/instance_follows_measure.rb       | 24 ++++++---------
 .../instance_media_attachments_measure.rb     | 23 +++++---------
 .../measure/instance_reports_measure.rb       | 24 ++++++---------
 .../measure/instance_statuses_measure.rb      | 30 ++++++++++---------
 .../metrics/measure/new_users_measure.rb      | 16 +++++-----
 .../metrics/measure/opened_reports_measure.rb | 16 +++++-----
 app/lib/admin/metrics/measure/query_helper.rb | 25 ++++++++++++++++
 .../measure/resolved_reports_measure.rb       | 16 +++++-----
 .../metrics/measure/tag_servers_measure.rb    | 24 ++++++++++-----
 .../measure/active_users_measure_spec.rb      | 17 +++++++++++
 .../measure/instance_accounts_measure_spec.rb |  6 ++++
 .../instance_followers_measure_spec.rb        |  6 ++++
 .../measure/instance_follows_measure_spec.rb  |  6 ++++
 ...instance_media_attachments_measure_spec.rb |  6 ++++
 .../measure/instance_reports_measure_spec.rb  |  6 ++++
 .../measure/instance_statuses_measure_spec.rb |  6 ++++
 .../measure/interactions_measure_spec.rb      | 17 +++++++++++
 .../metrics/measure/new_users_measure_spec.rb | 17 +++++++++++
 .../measure/opened_reports_measure_spec.rb    | 17 +++++++++++
 .../measure/resolved_reports_measure_spec.rb  | 17 +++++++++++
 .../measure/tag_accounts_measure_spec.rb      | 19 ++++++++++++
 .../measure/tag_servers_measure_spec.rb       | 19 ++++++++++++
 .../metrics/measure/tag_uses_measure_spec.rb  | 19 ++++++++++++
 25 files changed, 307 insertions(+), 117 deletions(-)
 create mode 100644 app/lib/admin/metrics/measure/query_helper.rb
 create mode 100644 spec/lib/admin/metrics/measure/active_users_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/interactions_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/new_users_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb
 create mode 100644 spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb

diff --git a/app/lib/admin/metrics/measure/instance_accounts_measure.rb b/app/lib/admin/metrics/measure/instance_accounts_measure.rb
index 14a61de88c..3d081fdd90 100644
--- a/app/lib/admin/metrics/measure/instance_accounts_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_accounts_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -25,33 +27,25 @@ class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
-      else
-        'accounts.domain = $3::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_accounts AS (
           SELECT accounts.id
           FROM accounts
           WHERE date_trunc('day', accounts.created_at)::date = axis.period
-            AND #{account_matching_sql}
+            AND #{account_domain_sql(params[:include_subdomains])}
         )
         SELECT count(*) FROM new_accounts
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/instance_followers_measure.rb b/app/lib/admin/metrics/measure/instance_followers_measure.rb
index dc0f5492c9..378c6754d9 100644
--- a/app/lib/admin/metrics/measure/instance_followers_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_followers_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measur
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
-      else
-        'accounts.domain = $3::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_followers AS (
           SELECT follows.id
           FROM follows
           INNER JOIN accounts ON follows.account_id = accounts.id
           WHERE date_trunc('day', follows.created_at)::date = axis.period
-            AND #{account_matching_sql}
+            AND #{account_domain_sql(params[:include_subdomains])}
         )
         SELECT count(*) FROM new_followers
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/instance_follows_measure.rb b/app/lib/admin/metrics/measure/instance_follows_measure.rb
index f2088ffb30..e213348fbc 100644
--- a/app/lib/admin/metrics/measure/instance_follows_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_follows_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure:
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
-      else
-        'accounts.domain = $3::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_follows AS (
           SELECT follows.id
           FROM follows
           INNER JOIN accounts ON follows.target_account_id = accounts.id
           WHERE date_trunc('day', follows.created_at)::date = axis.period
-            AND #{account_matching_sql}
+            AND #{account_domain_sql(params[:include_subdomains])}
         )
         SELECT count(*) FROM new_follows
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb
index 779883e031..2d4b5f56b0 100644
--- a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
   include ActionView::Helpers::NumberHelper
 
   def self.with_params?
@@ -35,34 +36,26 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics:
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
-      else
-        'accounts.domain = $3::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_media_attachments AS (
           SELECT COALESCE(media_attachments.file_file_size, 0) + COALESCE(media_attachments.thumbnail_file_size, 0) AS size
           FROM media_attachments
           INNER JOIN accounts ON accounts.id = media_attachments.account_id
           WHERE date_trunc('day', media_attachments.created_at)::date = axis.period
-            AND #{account_matching_sql}
+            AND #{account_domain_sql(params[:include_subdomains])}
         )
         SELECT SUM(size) FROM new_media_attachments
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/instance_reports_measure.rb b/app/lib/admin/metrics/measure/instance_reports_measure.rb
index c1f7189bfe..9da3d53e34 100644
--- a/app/lib/admin/metrics/measure/instance_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_reports_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure:
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
-      else
-        'accounts.domain = $3::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_reports AS (
           SELECT reports.id
           FROM reports
           INNER JOIN accounts ON accounts.id = reports.target_account_id
           WHERE date_trunc('day', reports.created_at)::date = axis.period
-            AND #{account_matching_sql}
+            AND #{account_domain_sql(params[:include_subdomains])}
         )
         SELECT count(*) FROM new_reports
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/instance_statuses_measure.rb b/app/lib/admin/metrics/measure/instance_statuses_measure.rb
index 1b38b40c55..8c71c66145 100644
--- a/app/lib/admin/metrics/measure/instance_statuses_measure.rb
+++ b/app/lib/admin/metrics/measure/instance_statuses_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -25,35 +27,35 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure
     nil
   end
 
-  def perform_data_query
-    account_matching_sql = begin
-      if params[:include_subdomains]
-        "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $5::text))"
-      else
-        'accounts.domain = $5::text'
-      end
-    end
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
+  end
 
-    sql = <<-SQL.squish
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_statuses AS (
           SELECT statuses.id
           FROM statuses
           INNER JOIN accounts ON accounts.id = statuses.account_id
-          WHERE statuses.id BETWEEN $3 AND $4
-            AND #{account_matching_sql}
+          WHERE statuses.id BETWEEN :earliest_status_id AND :latest_status_id
+            AND #{account_domain_sql(params[:include_subdomains])}
             AND date_trunc('day', statuses.created_at)::date = axis.period
         )
         SELECT count(*) FROM new_statuses
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
+  end
 
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, params[:domain]]])
+  def earliest_status_id
+    Mastodon::Snowflake.id_at(@start_at, with_random: false)
+  end
 
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
+  def latest_status_id
+    Mastodon::Snowflake.id_at(@end_at, with_random: false)
   end
 
   def time_period
diff --git a/app/lib/admin/metrics/measure/new_users_measure.rb b/app/lib/admin/metrics/measure/new_users_measure.rb
index 71191f1a22..6837c14c82 100644
--- a/app/lib/admin/metrics/measure/new_users_measure.rb
+++ b/app/lib/admin/metrics/measure/new_users_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def key
     'new_users'
   end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
     User.where(created_at: previous_time_period).count
   end
 
-  def perform_data_query
-    sql = <<-SQL.squish
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at }]
+  end
+
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_users AS (
           SELECT users.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
         SELECT count(*) FROM new_users
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 end
diff --git a/app/lib/admin/metrics/measure/opened_reports_measure.rb b/app/lib/admin/metrics/measure/opened_reports_measure.rb
index 4b80a0c8c3..c395c46341 100644
--- a/app/lib/admin/metrics/measure/opened_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/opened_reports_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def key
     'opened_reports'
   end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
     Report.where(created_at: previous_time_period).count
   end
 
-  def perform_data_query
-    sql = <<-SQL.squish
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at }]
+  end
+
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH new_reports AS (
           SELECT reports.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
         SELECT count(*) FROM new_reports
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 end
diff --git a/app/lib/admin/metrics/measure/query_helper.rb b/app/lib/admin/metrics/measure/query_helper.rb
new file mode 100644
index 0000000000..969065f73f
--- /dev/null
+++ b/app/lib/admin/metrics/measure/query_helper.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Admin::Metrics::Measure::QueryHelper
+  protected
+
+  def perform_data_query
+    measurement_data_rows.map { |row| { date: row['period'], value: row['value'].to_s } }
+  end
+
+  def measurement_data_rows
+    ActiveRecord::Base.connection.select_all(sanitized_sql_string)
+  end
+
+  def sanitized_sql_string
+    ActiveRecord::Base.sanitize_sql_array(sql_array)
+  end
+
+  def account_domain_sql(include_subdomains)
+    if include_subdomains
+      "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || :domain::text))"
+    else
+      'accounts.domain = :domain::text'
+    end
+  end
+end
diff --git a/app/lib/admin/metrics/measure/resolved_reports_measure.rb b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
index 4ab746c8fa..780db75a10 100644
--- a/app/lib/admin/metrics/measure/resolved_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def key
     'resolved_reports'
   end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
     Report.resolved.where(action_taken_at: previous_time_period).count
   end
 
-  def perform_data_query
-    sql = <<-SQL.squish
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at }]
+  end
+
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         WITH resolved_reports AS (
           SELECT reports.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
         SELECT count(*) FROM resolved_reports
       ) AS value
       FROM (
-        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
       ) AS axis
     SQL
-
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
-
-    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
   end
 end
diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb
index 11f229602e..e6378b8021 100644
--- a/app/lib/admin/metrics/measure/tag_servers_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::BaseMeasure
+  include Admin::Metrics::Measure::QueryHelper
+
   def self.with_params?
     true
   end
@@ -19,25 +21,33 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
     tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at - length_of_period, with_random: false), Mastodon::Snowflake.id_at(@end_at - length_of_period, with_random: false)).joins(:account).count('distinct accounts.domain')
   end
 
-  def perform_data_query
-    sql = <<-SQL.squish
+  def sql_array
+    [sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
+  end
+
+  def sql_query_string
+    <<~SQL.squish
       SELECT axis.*, (
         SELECT count(distinct accounts.domain) AS value
         FROM statuses
         INNER JOIN statuses_tags ON statuses.id = statuses_tags.status_id
         INNER JOIN accounts ON statuses.account_id = accounts.id
-        WHERE statuses_tags.tag_id = $1
-          AND statuses.id BETWEEN $2 AND $3
+        WHERE statuses_tags.tag_id = :tag_id
+          AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
           AND date_trunc('day', statuses.created_at)::date = axis.day
       )
       FROM (
-        SELECT generate_series(date_trunc('day', $4::timestamp)::date, date_trunc('day', $5::timestamp)::date, ('1 day')::interval) AS day
+        SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, ('1 day')::interval) AS day
       ) as axis
     SQL
+  end
 
-    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id].to_i], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @start_at], [nil, @end_at]])
+  def earliest_status_id
+    Mastodon::Snowflake.id_at(@start_at, with_random: false)
+  end
 
-    rows.map { |row| { date: row['day'], value: row['value'].to_s } }
+  def latest_status_id
+    Mastodon::Snowflake.id_at(@end_at, with_random: false)
   end
 
   def tag
diff --git a/spec/lib/admin/metrics/measure/active_users_measure_spec.rb b/spec/lib/admin/metrics/measure/active_users_measure_spec.rb
new file mode 100644
index 0000000000..55164ed88a
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/active_users_measure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::ActiveUsersMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb
index 29a157491e..8e414963f3 100644
--- a/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb
@@ -37,4 +37,10 @@ describe Admin::Metrics::Measure::InstanceAccountsMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb
index ebf789c1b3..c627e6cede 100644
--- a/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb
@@ -39,4 +39,10 @@ describe Admin::Metrics::Measure::InstanceFollowersMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb
index 335e3c7321..42f33dfc35 100644
--- a/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb
@@ -39,4 +39,10 @@ describe Admin::Metrics::Measure::InstanceFollowsMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb
index 711a2aff05..c103307f97 100644
--- a/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb
@@ -40,4 +40,10 @@ describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb
index f0ffd39cfb..62fcf84ac1 100644
--- a/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb
@@ -36,4 +36,10 @@ describe Admin::Metrics::Measure::InstanceReportsMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb
index c1425ecdb9..df4cfe207b 100644
--- a/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb
+++ b/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb
@@ -36,4 +36,10 @@ describe Admin::Metrics::Measure::InstanceStatusesMeasure do
       end
     end
   end
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/lib/admin/metrics/measure/interactions_measure_spec.rb b/spec/lib/admin/metrics/measure/interactions_measure_spec.rb
new file mode 100644
index 0000000000..e98c830598
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/interactions_measure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::InteractionsMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/new_users_measure_spec.rb b/spec/lib/admin/metrics/measure/new_users_measure_spec.rb
new file mode 100644
index 0000000000..fe82f8219b
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/new_users_measure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::NewUsersMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb
new file mode 100644
index 0000000000..deed64ae88
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::OpenedReportsMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb
new file mode 100644
index 0000000000..cb98df2dc2
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::ResolvedReportsMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb
new file mode 100644
index 0000000000..938b67afa3
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::TagAccountsMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let!(:tag) { Fabricate(:tag) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new(id: tag.id) }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb
new file mode 100644
index 0000000000..e09a2b04e5
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::TagServersMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let!(:tag) { Fabricate(:tag) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new(id: tag.id) }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb
new file mode 100644
index 0000000000..869e937445
--- /dev/null
+++ b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::Metrics::Measure::TagUsesMeasure do
+  subject(:measure) { described_class.new(start_at, end_at, params) }
+
+  let!(:tag) { Fabricate(:tag) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at)   { Time.now.utc }
+  let(:params) { ActionController::Parameters.new(id: tag.id) }
+
+  describe '#data' do
+    it 'runs data query without error' do
+      expect { measure.data }.to_not raise_error
+    end
+  end
+end