104 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
class Admin::Metrics::Retention
 | 
						|
  CACHE_TTL = 5.minutes.freeze
 | 
						|
 | 
						|
  class Cohort < ActiveModelSerializers::Model
 | 
						|
    attributes :period, :frequency, :data
 | 
						|
  end
 | 
						|
 | 
						|
  class CohortData < ActiveModelSerializers::Model
 | 
						|
    attributes :date, :rate, :value
 | 
						|
  end
 | 
						|
 | 
						|
  attr_reader :loaded
 | 
						|
 | 
						|
  alias loaded? loaded
 | 
						|
 | 
						|
  def initialize(start_at, end_at, frequency)
 | 
						|
    @start_at  = start_at&.to_date
 | 
						|
    @end_at    = end_at&.to_date
 | 
						|
    @frequency = %w(day month).include?(frequency) ? frequency : 'day'
 | 
						|
    @loaded    = false
 | 
						|
  end
 | 
						|
 | 
						|
  def cache_key
 | 
						|
    ['metrics/retention', @start_at, @end_at, @frequency].join(';')
 | 
						|
  end
 | 
						|
 | 
						|
  def cohorts
 | 
						|
    load
 | 
						|
  end
 | 
						|
 | 
						|
  protected
 | 
						|
 | 
						|
  def load
 | 
						|
    unless loaded?
 | 
						|
      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
 | 
						|
      @loaded = true
 | 
						|
    end
 | 
						|
 | 
						|
    @values
 | 
						|
  end
 | 
						|
 | 
						|
  def perform_query
 | 
						|
    report_rows.each_with_object([]) do |row, arr|
 | 
						|
      current_cohort = arr.last
 | 
						|
 | 
						|
      if current_cohort.nil? || current_cohort.period != row['cohort_period']
 | 
						|
        current_cohort = Cohort.new(period: row['cohort_period'], frequency: @frequency, data: [])
 | 
						|
        arr << current_cohort
 | 
						|
      end
 | 
						|
 | 
						|
      value, rate = row['retention_value_and_rate'].delete('{}').split(',')
 | 
						|
 | 
						|
      current_cohort.data << CohortData.new(
 | 
						|
        date: row['retention_period'],
 | 
						|
        rate: rate.to_f,
 | 
						|
        value: value.to_s
 | 
						|
      )
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def report_rows
 | 
						|
    ActiveRecord::Base.connection.select_all(sanitized_sql_string)
 | 
						|
  end
 | 
						|
 | 
						|
  def sanitized_sql_string
 | 
						|
    ActiveRecord::Base.sanitize_sql_array(
 | 
						|
      [sql_query_string, { start_at: @start_at, end_at: @end_at, frequency: @frequency }]
 | 
						|
    )
 | 
						|
  end
 | 
						|
 | 
						|
  def sql_query_string
 | 
						|
    <<~SQL.squish
 | 
						|
      SELECT axis.*, (
 | 
						|
        WITH new_users AS (
 | 
						|
          SELECT users.id
 | 
						|
          FROM users
 | 
						|
          WHERE date_trunc(:frequency, users.created_at)::date = axis.cohort_period
 | 
						|
        ),
 | 
						|
        retained_users AS (
 | 
						|
          SELECT users.id
 | 
						|
          FROM users
 | 
						|
          INNER JOIN new_users on new_users.id = users.id
 | 
						|
          WHERE date_trunc(:frequency, users.current_sign_in_at) >= axis.retention_period
 | 
						|
        )
 | 
						|
        SELECT ARRAY[count(*), (count(*))::float / (SELECT GREATEST(count(*), 1) FROM new_users)] AS retention_value_and_rate
 | 
						|
        FROM retained_users
 | 
						|
      )
 | 
						|
      FROM (
 | 
						|
        WITH cohort_periods AS (
 | 
						|
          SELECT generate_series(date_trunc(:frequency, :start_at::timestamp)::date, date_trunc(:frequency, :end_at::timestamp)::date, ('1 ' || :frequency)::interval) AS cohort_period
 | 
						|
        ),
 | 
						|
        retention_periods AS (
 | 
						|
          SELECT cohort_period AS retention_period FROM cohort_periods
 | 
						|
        )
 | 
						|
        SELECT *
 | 
						|
        FROM cohort_periods, retention_periods
 | 
						|
        WHERE retention_period >= cohort_period
 | 
						|
      ) as axis
 | 
						|
    SQL
 | 
						|
  end
 | 
						|
end
 |