* Proposing a new feed experiment This commit involves tweaking a few subtle aspects of the challenger: 1) Remove the sort by published date on the relevance score. The articles will now be listed in the order of perceived relevance. 2) Disable a few levers that did little. There really aren't any `articles.featured = true` articles in the database. 3) Gently increase the weight of each comment in a linear manner. 4) Give a little more weight to posts that don't have followed tags. In addition, it involves renaming the conversions to better convey their implementation. This renaming helps create more descriptive labels for the test results at `/admin/abtests`. This commit also adds logic to repurpose the AbExperiment feed_strategy logic; I envision adjusting the weighted feed strategy levers with some frequency. Per discussions, I'm also disabling the "not logged in" feed testing. We'll use the LargeForemExperimental for this. * Flipping attribute name to positive Mentally "not disabled" is harder to parse than "enabled". This change helps with setting an optimistic attribute. * Bump for travis * Bump for travis * Removing file committed by mistake * Extracting method
97 lines
4 KiB
Ruby
97 lines
4 KiB
Ruby
# This object wraps the FieldTest logic to provide a bit of insulation
|
|
# between the FieldTest implementation and the application logic.
|
|
#
|
|
# This is a SimpleDelegator because the field_test method requires
|
|
# controller context when the participating user is nil.
|
|
#
|
|
# Use the AbExperiment.get method to interact with this class.
|
|
class AbExperiment < SimpleDelegator
|
|
# Don't allow someone to circumvent the .get class method. I want
|
|
# to follow the Law of Demeter and require that we us the @api
|
|
# public
|
|
private_class_method :new
|
|
|
|
# Sometimes we might want to repurpose the same AbExperiment logic
|
|
# for different experiments. This provides the tooling for that
|
|
# exact thing.
|
|
EXPERIMENT_TO_METHOD_NAME_MAP = {
|
|
feed_strategy_round_2: :feed_strategy
|
|
}.freeze
|
|
|
|
# This method helps us leverage existing methods for different
|
|
# experiments.
|
|
#
|
|
# @param experiment [Symbol] the name of the experiment
|
|
# @return [Symbol] the method name associated with this experiment.
|
|
#
|
|
# @see EXPERIMENT_TO_METHOD_NAME_MAP
|
|
def self.method_name_for(experiment)
|
|
EXPERIMENT_TO_METHOD_NAME_MAP.fetch(experiment, experiment)
|
|
end
|
|
|
|
# @api public
|
|
#
|
|
# A convenience method to insulate against the implementation
|
|
# details of the field_test gem.
|
|
#
|
|
# @param experiment [Symbol] the named method we'll call for an
|
|
# experiment. It should be a method name defined on this
|
|
# object.
|
|
# @param controller [ApplicationController] the request context of
|
|
# the experiment.
|
|
# @note We need the controller object due to the implementation of
|
|
# the `field_test` method. The `field_test` method is defined
|
|
# in the FieldTest::Helpers module. If we have a user, the
|
|
# field_test method works great with just the
|
|
# FieldTest::Helpers methods. However, if we don't have a
|
|
# user, then the field_test method calls
|
|
# `field_test_participant` which is defined in the
|
|
# `FieldTest::Controller` (which calls the `request` and
|
|
# `cookies`).
|
|
# @param user [User] who are we running the experiment with
|
|
# @param config [Hash] container for possible ENV override of strategy.
|
|
# @param default_value [Object] the caller is making decisions based
|
|
# on a configured set of values. They know which one they
|
|
# likely want. Let them give us a hint.
|
|
#
|
|
# @return [Object] the experimenter should know what it wants
|
|
#
|
|
# @see config/field_test.yml file for configured experiments.
|
|
#
|
|
# @note You can force a named strategy by setting an ENV variable.
|
|
# This forced strategy might be super useful for anyone performing
|
|
# QA testing on AB Testing scenarios.
|
|
#
|
|
# @todo If we make heavy use of this class, consider guarding for
|
|
# valid experiment methods.
|
|
def self.get(experiment:, controller:, user:, default_value:, config: ApplicationConfig)
|
|
method_name = method_name_for(experiment)
|
|
new(controller: controller)
|
|
.public_send(method_name, user: user, default_value: default_value, experiment: experiment, config: config)
|
|
end
|
|
|
|
# @api private
|
|
# @param controller [ApplicationController] the current controller
|
|
# that's handling the current request.
|
|
def initialize(controller:)
|
|
super(controller)
|
|
end
|
|
|
|
# @api private
|
|
# @note Called via AbExperiment.get
|
|
def feed_strategy(user:, config:, default_value:, experiment: :feed_strategy)
|
|
return default_value.inquiry unless FeatureFlag.accessible?(:ab_experiment_feed_strategy)
|
|
|
|
(config["AB_EXPERIMENT_FEED_STRATEGY"] || field_test(experiment, participant: user)).inquiry
|
|
rescue FieldTest::ExperimentNotFound
|
|
# rubocop:disable Layout/LineLength
|
|
Rails.logger.warn do
|
|
"Upstream request #{experiment.inspect} experiment. There are no registered #{experiment.inspect} experiments. Using the default value of #{default_value.inspect} for #{experiment.inspect} experiment."
|
|
end
|
|
# rubocop:enable Layout/LineLength
|
|
|
|
# Because we should have a fall back plan in case the field test
|
|
# has an odd configuration.
|
|
default_value.inquiry
|
|
end
|
|
end
|