retention.rb 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. # frozen_string_literal: true
  2. class Admin::Metrics::Retention
  3. CACHE_TTL = 5.minutes.freeze
  4. class Cohort < ActiveModelSerializers::Model
  5. attributes :period, :frequency, :data
  6. end
  7. class CohortData < ActiveModelSerializers::Model
  8. attributes :date, :rate, :value
  9. end
  10. attr_reader :loaded
  11. alias loaded? loaded
  12. def initialize(start_at, end_at, frequency)
  13. @start_at = start_at&.to_date
  14. @end_at = end_at&.to_date
  15. @frequency = %w(day month).include?(frequency) ? frequency : 'day'
  16. @loaded = false
  17. end
  18. def cache_key
  19. ['metrics/retention', @start_at, @end_at, @frequency].join(';')
  20. end
  21. def cohorts
  22. load
  23. end
  24. protected
  25. def load
  26. unless loaded?
  27. @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
  28. @loaded = true
  29. end
  30. @values
  31. end
  32. def perform_query
  33. report_rows.each_with_object([]) do |row, arr|
  34. current_cohort = arr.last
  35. if current_cohort.nil? || current_cohort.period != row['cohort_period']
  36. current_cohort = Cohort.new(period: row['cohort_period'], frequency: @frequency, data: [])
  37. arr << current_cohort
  38. end
  39. value, rate = row['retention_value_and_rate'].delete('{}').split(',')
  40. current_cohort.data << CohortData.new(
  41. date: row['retention_period'],
  42. rate: rate.to_f,
  43. value: value.to_s
  44. )
  45. end
  46. end
  47. def report_rows
  48. ActiveRecord::Base.connection.select_all(sanitized_sql_string)
  49. end
  50. def sanitized_sql_string
  51. ActiveRecord::Base.sanitize_sql_array(
  52. [sql_query_string, { start_at: @start_at, end_at: @end_at, frequency: @frequency }]
  53. )
  54. end
  55. def sql_query_string
  56. <<~SQL.squish
  57. SELECT axis.*, (
  58. WITH new_users AS (
  59. SELECT users.id
  60. FROM users
  61. WHERE date_trunc(:frequency, users.created_at)::date = axis.cohort_period
  62. ),
  63. retained_users AS (
  64. SELECT users.id
  65. FROM users
  66. INNER JOIN new_users on new_users.id = users.id
  67. WHERE date_trunc(:frequency, users.current_sign_in_at) >= axis.retention_period
  68. )
  69. SELECT ARRAY[count(*), (count(*))::float / (SELECT GREATEST(count(*), 1) FROM new_users)] AS retention_value_and_rate
  70. FROM retained_users
  71. )
  72. FROM (
  73. WITH cohort_periods AS (
  74. SELECT generate_series(date_trunc(:frequency, :start_at::timestamp)::date, date_trunc(:frequency, :end_at::timestamp)::date, ('1 ' || :frequency)::interval) AS cohort_period
  75. ),
  76. retention_periods AS (
  77. SELECT cohort_period AS retention_period FROM cohort_periods
  78. )
  79. SELECT *
  80. FROM cohort_periods, retention_periods
  81. WHERE retention_period >= cohort_period
  82. ) as axis
  83. SQL
  84. end
  85. end