split 4.0.1 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +8 -3
  3. data/.rubocop.yml +2 -5
  4. data/CHANGELOG.md +38 -0
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +1 -1
  7. data/README.md +11 -3
  8. data/Rakefile +4 -5
  9. data/gemfiles/5.2.gemfile +1 -3
  10. data/gemfiles/6.0.gemfile +1 -3
  11. data/gemfiles/6.1.gemfile +1 -3
  12. data/gemfiles/7.0.gemfile +1 -3
  13. data/lib/split/algorithms/block_randomization.rb +5 -6
  14. data/lib/split/algorithms/whiplash.rb +16 -18
  15. data/lib/split/algorithms.rb +14 -0
  16. data/lib/split/alternative.rb +21 -22
  17. data/lib/split/cache.rb +0 -1
  18. data/lib/split/combined_experiments_helper.rb +4 -4
  19. data/lib/split/configuration.rb +83 -84
  20. data/lib/split/dashboard/helpers.rb +6 -7
  21. data/lib/split/dashboard/pagination_helpers.rb +53 -54
  22. data/lib/split/dashboard/public/style.css +5 -2
  23. data/lib/split/dashboard/views/_experiment.erb +2 -1
  24. data/lib/split/dashboard/views/index.erb +19 -4
  25. data/lib/split/dashboard.rb +29 -23
  26. data/lib/split/encapsulated_helper.rb +4 -6
  27. data/lib/split/experiment.rb +93 -88
  28. data/lib/split/experiment_catalog.rb +6 -5
  29. data/lib/split/extensions/string.rb +1 -1
  30. data/lib/split/goals_collection.rb +8 -10
  31. data/lib/split/helper.rb +20 -20
  32. data/lib/split/metric.rb +4 -5
  33. data/lib/split/persistence/cookie_adapter.rb +44 -47
  34. data/lib/split/persistence/dual_adapter.rb +7 -8
  35. data/lib/split/persistence/redis_adapter.rb +3 -4
  36. data/lib/split/persistence/session_adapter.rb +0 -2
  37. data/lib/split/persistence.rb +4 -4
  38. data/lib/split/redis_interface.rb +7 -1
  39. data/lib/split/trial.rb +23 -24
  40. data/lib/split/user.rb +12 -13
  41. data/lib/split/version.rb +1 -1
  42. data/lib/split/zscore.rb +1 -3
  43. data/lib/split.rb +26 -25
  44. data/spec/algorithms/block_randomization_spec.rb +6 -5
  45. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  46. data/spec/algorithms/whiplash_spec.rb +4 -5
  47. data/spec/alternative_spec.rb +35 -36
  48. data/spec/cache_spec.rb +15 -19
  49. data/spec/combined_experiments_helper_spec.rb +18 -17
  50. data/spec/configuration_spec.rb +32 -38
  51. data/spec/dashboard/pagination_helpers_spec.rb +69 -67
  52. data/spec/dashboard/paginator_spec.rb +10 -9
  53. data/spec/dashboard_helpers_spec.rb +19 -18
  54. data/spec/dashboard_spec.rb +79 -35
  55. data/spec/encapsulated_helper_spec.rb +12 -14
  56. data/spec/experiment_catalog_spec.rb +14 -13
  57. data/spec/experiment_spec.rb +132 -123
  58. data/spec/goals_collection_spec.rb +17 -15
  59. data/spec/helper_spec.rb +415 -382
  60. data/spec/metric_spec.rb +14 -14
  61. data/spec/persistence/cookie_adapter_spec.rb +23 -8
  62. data/spec/persistence/dual_adapter_spec.rb +71 -71
  63. data/spec/persistence/redis_adapter_spec.rb +28 -29
  64. data/spec/persistence/session_adapter_spec.rb +2 -3
  65. data/spec/persistence_spec.rb +1 -2
  66. data/spec/redis_interface_spec.rb +26 -14
  67. data/spec/spec_helper.rb +16 -13
  68. data/spec/split_spec.rb +11 -11
  69. data/spec/support/cookies_mock.rb +1 -2
  70. data/spec/trial_spec.rb +61 -60
  71. data/spec/user_spec.rb +36 -36
  72. data/split.gemspec +21 -20
  73. metadata +25 -14
  74. data/.rubocop_todo.yml +0 -226
  75. data/Appraisals +0 -19
  76. data/gemfiles/5.0.gemfile +0 -9
  77. data/gemfiles/5.1.gemfile +0 -9
@@ -39,83 +39,83 @@ module Split
39
39
  def bots
40
40
  @bots ||= {
41
41
  # Indexers
42
- 'AdsBot-Google' => 'Google Adwords',
43
- 'Baidu' => 'Chinese search engine',
44
- 'Baiduspider' => 'Chinese search engine',
45
- 'bingbot' => 'Microsoft bing bot',
46
- 'Butterfly' => 'Topsy Labs',
47
- 'Gigabot' => 'Gigabot spider',
48
- 'Googlebot' => 'Google spider',
49
- 'MJ12bot' => 'Majestic-12 spider',
50
- 'msnbot' => 'Microsoft bot',
51
- 'rogerbot' => 'SeoMoz spider',
52
- 'PaperLiBot' => 'PaperLi is another content curation service',
53
- 'Slurp' => 'Yahoo spider',
54
- 'Sogou' => 'Chinese search engine',
55
- 'spider' => 'generic web spider',
56
- 'UnwindFetchor' => 'Gnip crawler',
57
- 'WordPress' => 'WordPress spider',
58
- 'YandexAccessibilityBot' => 'Yandex accessibility spider',
59
- 'YandexBot' => 'Yandex spider',
60
- 'YandexMobileBot' => 'Yandex mobile spider',
61
- 'ZIBB' => 'ZIBB spider',
42
+ "AdsBot-Google" => "Google Adwords",
43
+ "Baidu" => "Chinese search engine",
44
+ "Baiduspider" => "Chinese search engine",
45
+ "bingbot" => "Microsoft bing bot",
46
+ "Butterfly" => "Topsy Labs",
47
+ "Gigabot" => "Gigabot spider",
48
+ "Googlebot" => "Google spider",
49
+ "MJ12bot" => "Majestic-12 spider",
50
+ "msnbot" => "Microsoft bot",
51
+ "rogerbot" => "SeoMoz spider",
52
+ "PaperLiBot" => "PaperLi is another content curation service",
53
+ "Slurp" => "Yahoo spider",
54
+ "Sogou" => "Chinese search engine",
55
+ "spider" => "generic web spider",
56
+ "UnwindFetchor" => "Gnip crawler",
57
+ "WordPress" => "WordPress spider",
58
+ "YandexAccessibilityBot" => "Yandex accessibility spider",
59
+ "YandexBot" => "Yandex spider",
60
+ "YandexMobileBot" => "Yandex mobile spider",
61
+ "ZIBB" => "ZIBB spider",
62
62
 
63
63
  # HTTP libraries
64
- 'Apache-HttpClient' => 'Java http library',
65
- 'AppEngine-Google' => 'Google App Engine',
66
- 'curl' => 'curl unix CLI http client',
67
- 'ColdFusion' => 'ColdFusion http library',
68
- 'EventMachine HttpClient' => 'Ruby http library',
69
- 'Go http package' => 'Go http library',
70
- 'Go-http-client' => 'Go http library',
71
- 'Java' => 'Generic Java http library',
72
- 'libwww-perl' => 'Perl client-server library loved by script kids',
73
- 'lwp-trivial' => 'Another Perl library loved by script kids',
74
- 'Python-urllib' => 'Python http library',
75
- 'PycURL' => 'Python http library',
76
- 'Test Certificate Info' => 'C http library?',
77
- 'Typhoeus' => 'Ruby http library',
78
- 'Wget' => 'wget unix CLI http client',
64
+ "Apache-HttpClient" => "Java http library",
65
+ "AppEngine-Google" => "Google App Engine",
66
+ "curl" => "curl unix CLI http client",
67
+ "ColdFusion" => "ColdFusion http library",
68
+ "EventMachine HttpClient" => "Ruby http library",
69
+ "Go http package" => "Go http library",
70
+ "Go-http-client" => "Go http library",
71
+ "Java" => "Generic Java http library",
72
+ "libwww-perl" => "Perl client-server library loved by script kids",
73
+ "lwp-trivial" => "Another Perl library loved by script kids",
74
+ "Python-urllib" => "Python http library",
75
+ "PycURL" => "Python http library",
76
+ "Test Certificate Info" => "C http library?",
77
+ "Typhoeus" => "Ruby http library",
78
+ "Wget" => "wget unix CLI http client",
79
79
 
80
80
  # URL expanders / previewers
81
- 'awe.sm' => 'Awe.sm URL expander',
82
- 'bitlybot' => 'bit.ly bot',
83
- 'bot@linkfluence.net' => 'Linkfluence bot',
84
- 'facebookexternalhit' => 'facebook bot',
85
- 'Facebot' => 'Facebook crawler',
86
- 'Feedfetcher-Google' => 'Google Feedfetcher',
87
- 'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
88
- 'LinkedInBot' => 'LinkedIn bot',
89
- 'LongURL' => 'URL expander service',
90
- 'NING' => 'NING - Yet Another Twitter Swarmer',
91
- 'Pinterestbot' => 'Pinterest Bot',
92
- 'redditbot' => 'Reddit Bot',
93
- 'ShortLinkTranslate' => 'Link shortener',
94
- 'Slackbot' => 'Slackbot link expander',
95
- 'TweetmemeBot' => 'TweetMeMe Crawler',
96
- 'Twitterbot' => 'Twitter URL expander',
97
- 'UnwindFetch' => 'Gnip URL expander',
98
- 'vkShare' => 'VKontake Sharer',
81
+ "awe.sm" => "Awe.sm URL expander",
82
+ "bitlybot" => "bit.ly bot",
83
+ "bot@linkfluence.net" => "Linkfluence bot",
84
+ "facebookexternalhit" => "facebook bot",
85
+ "Facebot" => "Facebook crawler",
86
+ "Feedfetcher-Google" => "Google Feedfetcher",
87
+ "https://developers.google.com/+/web/snippet" => "Google+ Snippet Fetcher",
88
+ "LinkedInBot" => "LinkedIn bot",
89
+ "LongURL" => "URL expander service",
90
+ "NING" => "NING - Yet Another Twitter Swarmer",
91
+ "Pinterestbot" => "Pinterest Bot",
92
+ "redditbot" => "Reddit Bot",
93
+ "ShortLinkTranslate" => "Link shortener",
94
+ "Slackbot" => "Slackbot link expander",
95
+ "TweetmemeBot" => "TweetMeMe Crawler",
96
+ "Twitterbot" => "Twitter URL expander",
97
+ "UnwindFetch" => "Gnip URL expander",
98
+ "vkShare" => "VKontake Sharer",
99
99
 
100
100
  # Uptime monitoring
101
- 'check_http' => 'Nagios monitor',
102
- 'GoogleStackdriverMonitoring' => 'Google Cloud monitor',
103
- 'NewRelicPinger' => 'NewRelic monitor',
104
- 'Panopta' => 'Monitoring service',
105
- 'Pingdom' => 'Pingdom monitoring',
106
- 'SiteUptime' => 'Site monitoring services',
107
- 'UptimeRobot' => 'Monitoring service',
101
+ "check_http" => "Nagios monitor",
102
+ "GoogleStackdriverMonitoring" => "Google Cloud monitor",
103
+ "NewRelicPinger" => "NewRelic monitor",
104
+ "Panopta" => "Monitoring service",
105
+ "Pingdom" => "Pingdom monitoring",
106
+ "SiteUptime" => "Site monitoring services",
107
+ "UptimeRobot" => "Monitoring service",
108
108
 
109
109
  # ???
110
- 'DigitalPersona Fingerprint Software' => 'HP Fingerprint scanner',
111
- 'ShowyouBot' => 'Showyou iOS app spider',
112
- 'ZyBorg' => 'Zyborg? Hmmm....',
113
- 'ELB-HealthChecker' => 'ELB Health Check'
110
+ "DigitalPersona Fingerprint Software" => "HP Fingerprint scanner",
111
+ "ShowyouBot" => "Showyou iOS app spider",
112
+ "ZyBorg" => "Zyborg? Hmmm....",
113
+ "ELB-HealthChecker" => "ELB Health Check"
114
114
  }
115
115
  end
116
116
 
117
- def experiments= experiments
118
- raise InvalidExperimentsFormatError.new('Experiments must be a Hash') unless experiments.respond_to?(:keys)
117
+ def experiments=(experiments)
118
+ raise InvalidExperimentsFormatError.new("Experiments must be a Hash") unless experiments.respond_to?(:keys)
119
119
  @experiments = experiments
120
120
  end
121
121
 
@@ -157,8 +157,8 @@ module Split
157
157
 
158
158
  @experiments.each do |experiment_name, settings|
159
159
  alternatives = if (alts = value_for(settings, :alternatives))
160
- normalize_alternatives(alts)
161
- end
160
+ normalize_alternatives(alts)
161
+ end
162
162
 
163
163
  experiment_data = {
164
164
  alternatives: alternatives,
@@ -213,14 +213,14 @@ module Split
213
213
 
214
214
  def initialize
215
215
  @ignore_ip_addresses = []
216
- @ignore_filter = proc{ |request| is_robot? || is_ignored_ip_address? }
216
+ @ignore_filter = proc { |request| is_robot? || is_ignored_ip_address? }
217
217
  @db_failover = false
218
- @db_failover_on_db_error = proc{|error|} # e.g. use Rails logger here
219
- @on_experiment_reset = proc{|experiment|}
220
- @on_experiment_delete = proc{|experiment|}
221
- @on_before_experiment_reset = proc{|experiment|}
222
- @on_before_experiment_delete = proc{|experiment|}
223
- @on_experiment_winner_choose = proc{|experiment|}
218
+ @db_failover_on_db_error = proc { |error| } # e.g. use Rails logger here
219
+ @on_experiment_reset = proc { |experiment| }
220
+ @on_experiment_delete = proc { |experiment| }
221
+ @on_before_experiment_reset = proc { |experiment| }
222
+ @on_before_experiment_delete = proc { |experiment| }
223
+ @on_experiment_winner_choose = proc { |experiment| }
224
224
  @db_failover_allow_parameter_override = false
225
225
  @allow_multiple_experiments = false
226
226
  @enabled = true
@@ -232,20 +232,19 @@ module Split
232
232
  @include_rails_helper = true
233
233
  @beta_probability_simulations = 10000
234
234
  @winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day
235
- @redis = ENV.fetch(ENV.fetch('REDIS_PROVIDER', 'REDIS_URL'), 'redis://localhost:6379')
235
+ @redis = ENV.fetch(ENV.fetch("REDIS_PROVIDER", "REDIS_URL"), "redis://localhost:6379")
236
236
  @dashboard_pagination_default_per_page = 10
237
237
  end
238
238
 
239
239
  private
240
-
241
- def value_for(hash, key)
242
- if hash.kind_of?(Hash)
243
- hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym]
240
+ def value_for(hash, key)
241
+ if hash.kind_of?(Hash)
242
+ hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym]
243
+ end
244
244
  end
245
- end
246
245
 
247
- def escaped_bots
248
- bots.map { |key, _| Regexp.escape(key) }
249
- end
246
+ def escaped_bots
247
+ bots.map { |key, _| Regexp.escape(key) }
248
+ end
250
249
  end
251
250
  end
@@ -7,11 +7,11 @@ module Split
7
7
  end
8
8
 
9
9
  def url(*path_parts)
10
- [ path_prefix, path_parts ].join("/").squeeze('/')
10
+ [ path_prefix, path_parts ].join("/").squeeze("/")
11
11
  end
12
12
 
13
13
  def path_prefix
14
- request.env['SCRIPT_NAME']
14
+ request.env["SCRIPT_NAME"]
15
15
  end
16
16
 
17
17
  def number_to_percentage(number, precision = 2)
@@ -32,15 +32,14 @@ module Split
32
32
  z = round(z_score.to_s.to_f, 3).abs
33
33
 
34
34
  if z >= 2.58
35
- '99% confidence'
35
+ "99% confidence"
36
36
  elsif z >= 1.96
37
- '95% confidence'
37
+ "95% confidence"
38
38
  elsif z >= 1.65
39
- '90% confidence'
39
+ "90% confidence"
40
40
  else
41
- 'Insufficient confidence'
41
+ "Insufficient confidence"
42
42
  end
43
-
44
43
  end
45
44
  end
46
45
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'split/dashboard/paginator'
3
+ require "split/dashboard/paginator"
4
4
 
5
5
  module Split
6
6
  module DashboardPaginationHelpers
@@ -30,58 +30,57 @@ module Split
30
30
  end
31
31
 
32
32
  private
33
-
34
- def show_first_page_tag?
35
- page_number > 2
36
- end
37
-
38
- def first_page_tag
39
- %Q(<a href="#{url.chop}?page=1&per=#{pagination_per}">1</a>)
40
- end
41
-
42
- def show_first_ellipsis_tag?
43
- page_number >= 4
44
- end
45
-
46
- def ellipsis_tag
47
- '<span>...</span>'
48
- end
49
-
50
- def show_prev_page_tag?
51
- page_number > 1
52
- end
53
-
54
- def prev_page_tag
55
- %Q(<a href="#{url.chop}?page=#{page_number - 1}&per=#{pagination_per}">#{page_number - 1}</a>)
56
- end
57
-
58
- def current_page_tag
59
- "<span><b>#{page_number}</b></span>"
60
- end
61
-
62
- def show_next_page_tag?(collection)
63
- (page_number * pagination_per) < collection.count
64
- end
65
-
66
- def next_page_tag
67
- %Q(<a href="#{url.chop}?page=#{page_number + 1}&per=#{pagination_per}">#{page_number + 1}</a>)
68
- end
69
-
70
- def show_last_ellipsis_tag?(collection)
71
- (total_pages(collection) - page_number) >= 3
72
- end
73
-
74
- def total_pages(collection)
75
- collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1)
76
- end
77
-
78
- def show_last_page_tag?(collection)
79
- page_number < (total_pages(collection) - 1)
80
- end
81
-
82
- def last_page_tag(collection)
83
- total = total_pages(collection)
84
- %Q(<a href="#{url.chop}?page=#{total}&per=#{pagination_per}">#{total}</a>)
85
- end
33
+ def show_first_page_tag?
34
+ page_number > 2
35
+ end
36
+
37
+ def first_page_tag
38
+ %Q(<a href="#{url.chop}?page=1&per=#{pagination_per}">1</a>)
39
+ end
40
+
41
+ def show_first_ellipsis_tag?
42
+ page_number >= 4
43
+ end
44
+
45
+ def ellipsis_tag
46
+ "<span>...</span>"
47
+ end
48
+
49
+ def show_prev_page_tag?
50
+ page_number > 1
51
+ end
52
+
53
+ def prev_page_tag
54
+ %Q(<a href="#{url.chop}?page=#{page_number - 1}&per=#{pagination_per}">#{page_number - 1}</a>)
55
+ end
56
+
57
+ def current_page_tag
58
+ "<span><b>#{page_number}</b></span>"
59
+ end
60
+
61
+ def show_next_page_tag?(collection)
62
+ (page_number * pagination_per) < collection.count
63
+ end
64
+
65
+ def next_page_tag
66
+ %Q(<a href="#{url.chop}?page=#{page_number + 1}&per=#{pagination_per}">#{page_number + 1}</a>)
67
+ end
68
+
69
+ def show_last_ellipsis_tag?(collection)
70
+ (total_pages(collection) - page_number) >= 3
71
+ end
72
+
73
+ def total_pages(collection)
74
+ collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1)
75
+ end
76
+
77
+ def show_last_page_tag?(collection)
78
+ page_number < (total_pages(collection) - 1)
79
+ end
80
+
81
+ def last_page_tag(collection)
82
+ total = total_pages(collection)
83
+ %Q(<a href="#{url.chop}?page=#{total}&per=#{pagination_per}">#{total}</a>)
84
+ end
86
85
  end
87
86
  end
@@ -258,7 +258,7 @@ body {
258
258
  color: #408C48;
259
259
  }
260
260
 
261
- a.button, button, input[type="submit"] {
261
+ .experiment a.button, .experiment button, .experiment input[type="submit"] {
262
262
  padding: 4px 10px;
263
263
  overflow: hidden;
264
264
  background: #d8dae0;
@@ -312,10 +312,13 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
312
312
  background:#768E7A;
313
313
  }
314
314
 
315
- #filter, #clear-filter {
315
+ .dashboard-controls input, .dashboard-controls select {
316
316
  padding: 10px;
317
317
  }
318
318
 
319
+ .dashboard-controls-bottom {
320
+ margin-top: 10px;
321
+ }
319
322
 
320
323
  .pagination {
321
324
  text-align: center;
@@ -16,7 +16,8 @@
16
16
  summary_texts = {}
17
17
  extra_columns.each do |column|
18
18
  extra_infos = experiment.alternatives.map(&:extra_info).select{|extra_info| extra_info && extra_info[column] }
19
- if extra_infos[0][column].kind_of?(Numeric)
19
+
20
+ if extra_infos.length > 0 && extra_infos.all? { |extra_info| extra_info[column].kind_of?(Numeric) }
20
21
  summary_texts[column] = extra_infos.inject(0){|sum, extra_info| sum += extra_info[column]}
21
22
  else
22
23
  summary_texts[column] = "N/A"
@@ -1,10 +1,12 @@
1
1
  <% if @experiments.any? %>
2
2
  <p class="intro">The list below contains all the registered experiments along with the number of test participants, completed and conversion rate currently in the system.</p>
3
3
 
4
- <input type="text" placeholder="Begin typing to filter" id="filter" />
5
- <input type="button" id="toggle-completed" value="Hide completed" />
6
- <input type="button" id="toggle-active" value="Hide active" />
7
- <input type="button" id="clear-filter" value="Clear filters" />
4
+ <div class="dashboard-controls">
5
+ <input type="text" placeholder="Begin typing to filter" id="filter" />
6
+ <input type="button" id="toggle-completed" value="Hide completed" />
7
+ <input type="button" id="toggle-active" value="Hide active" />
8
+ <input type="button" id="clear-filter" value="Clear filters" />
9
+ </div>
8
10
 
9
11
  <% paginated(@experiments).each do |experiment| %>
10
12
  <% if experiment.goals.empty? %>
@@ -24,3 +26,16 @@
24
26
  <p class="intro">No experiments have started yet, you need to define them in your code and introduce them to your users.</p>
25
27
  <p class="intro">Check out the <a href='https://github.com/splitrb/split#readme'>Readme</a> for more help getting started.</p>
26
28
  <% end %>
29
+
30
+ <div class="dashboard-controls dashboard-controls-bottom">
31
+ <form action="<%= url "/initialize_experiment" %>" method='post'>
32
+ <label>Add unregistered experiment: </label>
33
+ <select name="experiment" id="experiment-select">
34
+ <option selected disabled>experiment</option>
35
+ <% @unintialized_experiments.sort.each do |experiment_name| %>
36
+ <option value="<%= experiment_name %>"><%= experiment_name %></option>
37
+ <% end %>
38
+ </select>
39
+ <input type="submit" id="register-experiment-btn" value="register experiment"/>
40
+ </form>
41
+ <div>
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sinatra/base'
4
- require 'split'
5
- require 'bigdecimal'
6
- require 'split/dashboard/helpers'
7
- require 'split/dashboard/pagination_helpers'
3
+ require "sinatra/base"
4
+ require "split"
5
+ require "bigdecimal"
6
+ require "split/dashboard/helpers"
7
+ require "split/dashboard/pagination_helpers"
8
8
 
9
9
  module Split
10
10
  class Dashboard < Sinatra::Base
@@ -18,14 +18,15 @@ module Split
18
18
  helpers Split::DashboardHelpers
19
19
  helpers Split::DashboardPaginationHelpers
20
20
 
21
- get '/' do
21
+ get "/" do
22
22
  # Display experiments without a winner at the top of the dashboard
23
23
  @experiments = Split::ExperimentCatalog.all_active_first
24
+ @unintialized_experiments = Split.configuration.experiments.keys - @experiments.map(&:name)
24
25
 
25
26
  @metrics = Split::Metric.all
26
27
 
27
28
  # Display Rails Environment mode (or Rack version if not using Rails)
28
- if Object.const_defined?('Rails')
29
+ if Object.const_defined?("Rails") && Rails.respond_to?(:env)
29
30
  @current_env = Rails.env.titlecase
30
31
  else
31
32
  @current_env = "Rack: #{Rack.version}"
@@ -33,43 +34,48 @@ module Split
33
34
  erb :index
34
35
  end
35
36
 
36
- post '/force_alternative' do
37
+ post "/initialize_experiment" do
38
+ Split::ExperimentCatalog.find_or_create(params[:experiment]) unless params[:experiment].nil? || params[:experiment].empty?
39
+ redirect url("/")
40
+ end
41
+
42
+ post "/force_alternative" do
37
43
  experiment = Split::ExperimentCatalog.find(params[:experiment])
38
44
  alternative = Split::Alternative.new(params[:alternative], experiment.name)
39
45
 
40
- cookies = JSON.parse(request.cookies['split_override']) rescue {}
46
+ cookies = JSON.parse(request.cookies["split_override"]) rescue {}
41
47
  cookies[experiment.name] = alternative.name
42
- response.set_cookie('split_override', { value: cookies.to_json, path: '/' })
48
+ response.set_cookie("split_override", { value: cookies.to_json, path: "/" })
43
49
 
44
- redirect url('/')
50
+ redirect url("/")
45
51
  end
46
52
 
47
- post '/experiment' do
53
+ post "/experiment" do
48
54
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
49
55
  @alternative = Split::Alternative.new(params[:alternative], params[:experiment])
50
56
  @experiment.winner = @alternative.name
51
- redirect url('/')
57
+ redirect url("/")
52
58
  end
53
59
 
54
- post '/start' do
60
+ post "/start" do
55
61
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
56
62
  @experiment.start
57
- redirect url('/')
63
+ redirect url("/")
58
64
  end
59
65
 
60
- post '/reset' do
66
+ post "/reset" do
61
67
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
62
68
  @experiment.reset
63
- redirect url('/')
69
+ redirect url("/")
64
70
  end
65
71
 
66
- post '/reopen' do
72
+ post "/reopen" do
67
73
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
68
74
  @experiment.reset_winner
69
- redirect url('/')
75
+ redirect url("/")
70
76
  end
71
77
 
72
- post '/update_cohorting' do
78
+ post "/update_cohorting" do
73
79
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
74
80
  case params[:cohorting_action].downcase
75
81
  when "enable"
@@ -77,13 +83,13 @@ module Split
77
83
  when "disable"
78
84
  @experiment.disable_cohorting
79
85
  end
80
- redirect url('/')
86
+ redirect url("/")
81
87
  end
82
88
 
83
- delete '/experiment' do
89
+ delete "/experiment" do
84
90
  @experiment = Split::ExperimentCatalog.find(params[:experiment])
85
91
  @experiment.delete
86
- redirect url('/')
92
+ redirect url("/")
87
93
  end
88
94
  end
89
95
  end
@@ -15,7 +15,6 @@ require "split/helper"
15
15
  #
16
16
  module Split
17
17
  module EncapsulatedHelper
18
-
19
18
  class ContextShim
20
19
  include Split::Helper
21
20
  public :ab_test, :ab_finished
@@ -34,10 +33,9 @@ module Split
34
33
  end
35
34
 
36
35
  private
37
-
38
- # instantiate and memoize a context shim in case of multiple ab_test* calls
39
- def split_context_shim
40
- @split_context_shim ||= ContextShim.new(self)
41
- end
36
+ # instantiate and memoize a context shim in case of multiple ab_test* calls
37
+ def split_context_shim
38
+ @split_context_shim ||= ContextShim.new(self)
39
+ end
42
40
  end
43
41
  end