* 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>
93 lines
3.6 KiB
Ruby
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
|