Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

213 changes: 0 additions & 213 deletions .eslintrc

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pkg/*
*.rbc
.idea
coverage
node_modules
issues.rtf
dump.rdb
.gitignore
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ If you have an experiment called `button_color` with alternatives called `red` a

will always have red buttons. This won't be stored in your session or count towards to results, unless you set the `store_override` configuration option.

The dashboard's "Force for current user" button does the same thing for your browser, by setting a `split_override` cookie. Because an override is not counted, an experiment you have pinned can keep reading zero participants. The dashboard lists any overrides active in your browser at the top of the page, and lets you clear them individually or all at once.

In the event you want to disable all tests without having to know the individual experiment names, add a `SPLIT_DISABLE` query parameter.

http://myawesomesite.com?SPLIT_DISABLE=true
Expand Down
22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const js = require("@eslint/js");
const globals = require("globals");

module.exports = [
{ ignores: ["coverage/"] },
js.configs.recommended,
{
files: ["lib/**/*.js"],
languageOptions: {
globals: globals.browser
},
rules: {
"no-empty": ["error", { allowEmptyCatch: true }]
}
},
{
files: ["eslint.config.js"],
languageOptions: {
globals: globals.node
}
}
];
2 changes: 2 additions & 0 deletions lib/split.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module Split
extend self
attr_accessor :configuration

OVERRIDE_COOKIE_NAME = "split_override"

# Accepts:
# 1. A redis URL (valid for `Redis.new(url: url)`)
# 2. an options hash compatible with `Redis.new`
Expand Down
19 changes: 16 additions & 3 deletions lib/split/dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "sinatra/base"
require "split"
require "bigdecimal"
require "split/dashboard/experiment"
require "split/dashboard/helpers"
require "split/dashboard/pagination_helpers"

Expand Down Expand Up @@ -44,9 +45,21 @@ class Dashboard < Sinatra::Base
experiment = Split::ExperimentCatalog.find(params[:experiment])
alternative = Split::Alternative.new(params[:alternative], experiment.name)

cookies = JSON.parse(request.cookies["split_override"]) rescue {}
cookies[experiment.name] = alternative.name
response.set_cookie("split_override", { value: cookies.to_json, path: "/" })
write_overrides(active_overrides.merge(experiment.name => alternative.name))

redirect url("/")
end

post "/clear_override" do
overrides = active_overrides
overrides.delete(params[:experiment])
write_overrides(overrides)

redirect url("/")
end

post "/clear_overrides" do
response.delete_cookie(Split::OVERRIDE_COOKIE_NAME, path: "/")

redirect url("/")
end
Expand Down
84 changes: 84 additions & 0 deletions lib/split/dashboard/experiment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

require "forwardable"

module Split
class DashboardExperiment
def initialize(experiment, goal = nil)
@experiment = experiment
@goal = goal
end

def alternatives
@alternatives ||= @experiment.alternatives.map do |alternative|
DashboardAlternative.new(alternative, @experiment, @goal)
end
end

def extra_columns
@extra_columns ||= alternatives.flat_map { |alternative| alternative.extra_info.keys }.uniq
end

def extra_totals
@extra_totals ||= extra_columns.to_h do |column|
values = alternatives.map { |alternative| alternative.extra_info[column] }.compact
[column, values.any? && values.all?(Numeric) ? values.sum : "N/A"]
end
end

def total_participants
alternatives.sum(&:participants)
end

def total_unfinished
alternatives.sum(&:unfinished)
end

def total_completed
alternatives.sum(&:completed)
end
end

class DashboardAlternative
extend Forwardable
def_delegators :@alternative, :name, :control?

attr_reader :participants, :unfinished, :completed, :extra_info

def initialize(alternative, experiment, goal = nil)
@alternative = alternative
@experiment = experiment
@goal = goal

@participants = alternative.participant_count
@unfinished = alternative.unfinished_count
@completed = alternative.completed_count(goal)
@extra_info = alternative.extra_info
end

def conversion_rate
@conversion_rate ||= @alternative.conversion_rate(@goal)
end

def conversion_delta
return if control?

control_rate = @experiment.control.conversion_rate(@goal)
return if control_rate <= 0 || conversion_rate == control_rate

conversion_rate / control_rate - 1
end

def z_score
@z_score ||= @alternative.z_score(@goal)
end

def p_winner
@p_winner ||= @alternative.p_winner(@goal)
end

def winner?
@experiment.winner.name == name
end
end
end
Loading
Loading