docbrown/app/models/ab_experiment/goal_conversion_handler.rb
Jeremy Friesen fe2e53cc50
Moving Experiments into AbExperiment namespace (#17532)
* Moving Experiments into AbExperiment namespace

This commit entails two major concepts:

1. Extracting logic out of a worker.
2. Cleaning the conditions in which we convert experiments.

In addition, I revisited some spec names to tidy them up for
clarification and structure.

**Extracting Logic...**

For workers, my preference is that they be a simple router to another
object.  A non-worker object, with it's initialize method, can both
better manage instance variables.  Further, workers are somewhat "flat"
in hierarchy,

In this case, having the AbExperiment be knowledgeable both in setting
the experiment and handling conversions makes (to me) organizational
sense.  For example, the AbExperiment is constructed as a wrapper to the
FieldTest gem.  But the RecordFieldTestEventWorker had knowledge of
FieldTest.

With this refactor, it does not have that knowledge.  This also provided
an opportunity to replace magic strings with constants.

**Cleaning the conditions...**

This relates directly to and closes forem/forem#17530.  I was looking at
the experiments after 2 days, and realized that there shouldn't be
results for
`user_views_pages_on_at_least_nine_different_days_within_two_weeks`;
after all the experiment started 2 days ago, how can we have results
that look at 2 weeks.

By adding the "max" function calls, we ensure a clear boundary of
"before the experiment began" versus "while the experiment is running".

Closes forem/forem#17530

* Update spec/models/ab_experiment_spec.rb

Co-authored-by: Mac Siri <krairit.siri@gmail.com>

* Apply suggestions from code review

Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com>

Co-authored-by: Mac Siri <krairit.siri@gmail.com>
Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com>
2022-05-03 21:34:12 -04:00

93 lines
3.6 KiB
Ruby

class AbExperiment
# Responsible for checking if a given :user has "accomplished" the state :goal for any of the
# active :experiments. We scope our tests to events that happened on or after the experiment's
# start date.
#
# @note It is required that each experiment have a start date (in CCYY-MM-DD format).
class GoalConversionHandler
include FieldTest::Helpers
USER_CREATES_PAGEVIEW_GOAL = "user_creates_pageview".freeze
USER_CREATES_COMMENT_GOAL = "user_creates_comment".freeze
def self.call(...)
new(...).call
end
def initialize(user:, goal:, experiments:)
@user = user
@goal = goal
@experiments = experiments
end
attr_reader :experiments, :user, :goal
def call
# It's okay that there are no experiments.
return if experiments.nil?
experiments.each do |key, data|
experiment_start_date = data.fetch("start_date").beginning_of_day
experiment = key.to_sym
convert(experiment: experiment, experiment_start_date: experiment_start_date)
end
end
private
def convert(experiment:, experiment_start_date:)
case goal
# We have special conditional goals for some where we look for past events for cummulative wins
# Otherwise we convert the goal as given.
when USER_CREATES_PAGEVIEW_GOAL
pageview_goal(experiment,
[7.days.ago, experiment_start_date].max,
"DATE(created_at)",
4,
"user_views_pages_on_at_least_four_different_days_within_a_week")
pageview_goal(experiment,
[24.hours.ago, experiment_start_date].max,
"DATE_PART('hour', created_at)",
4,
"user_views_pages_on_at_least_four_different_hours_within_a_day")
pageview_goal(experiment,
[14.days.ago, experiment_start_date].max,
"DATE(created_at)",
9,
"user_views_pages_on_at_least_nine_different_days_within_two_weeks")
pageview_goal(experiment,
[5.days.ago, experiment_start_date].max,
"DATE_PART('hour', created_at)",
12,
"user_views_pages_on_at_least_twelve_different_hours_within_five_days")
when USER_CREATES_COMMENT_GOAL # comments goal. Only page views and comments are currently active.
field_test_converted(experiment, participant: user, goal: goal) # base single comment goal.
comment_goal(experiment,
[7.days.ago, experiment_start_date].max,
"DATE(created_at)",
4,
"user_creates_comment_on_at_least_four_different_days_within_a_week")
else
field_test_converted(experiment, participant: user, goal: goal) # base single comment goal.
end
end
def pageview_goal(experiment, time_start, group_value, min_count, goal)
page_view_counts = user.page_views.where("created_at > ?", time_start)
.group(group_value).count.values
page_view_counts.delete(0)
return unless page_view_counts.size >= min_count
field_test_converted(experiment, participant: user, goal: goal)
end
def comment_goal(experiment, time_start, group_value, min_count, goal)
comment_counts = user.comments.where("created_at > ?", time_start)
.group(group_value).count.values
comment_counts.delete(0)
return unless comment_counts.size >= min_count
field_test_converted(experiment, participant: user, goal: goal)
end
end
end