split 3.0.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +2 -2
- data/.travis.yml +39 -2
- data/Appraisals +8 -1
- data/CHANGELOG.md +41 -0
- data/CONTRIBUTING.md +54 -5
- data/LICENSE +1 -1
- data/README.md +184 -110
- data/gemfiles/4.2.gemfile +1 -1
- data/gemfiles/5.0.gemfile +1 -2
- data/gemfiles/5.1.gemfile +9 -0
- data/gemfiles/5.2.gemfile +9 -0
- data/lib/split/alternative.rb +3 -0
- data/lib/split/combined_experiments_helper.rb +37 -0
- data/lib/split/configuration.rb +8 -0
- data/lib/split/dashboard/pagination_helpers.rb +87 -0
- data/lib/split/dashboard/paginator.rb +16 -0
- data/lib/split/dashboard/public/style.css +9 -0
- data/lib/split/dashboard/views/index.erb +5 -1
- data/lib/split/dashboard.rb +2 -0
- data/lib/split/engine.rb +2 -0
- data/lib/split/experiment.rb +5 -4
- data/lib/split/helper.rb +14 -1
- data/lib/split/persistence/cookie_adapter.rb +53 -15
- data/lib/split/redis_interface.rb +1 -3
- data/lib/split/version.rb +1 -1
- data/lib/split.rb +1 -0
- data/spec/alternative_spec.rb +12 -0
- data/spec/combined_experiments_helper_spec.rb +57 -0
- data/spec/dashboard/pagination_helpers_spec.rb +198 -0
- data/spec/dashboard/paginator_spec.rb +37 -0
- data/spec/experiment_spec.rb +1 -1
- data/spec/helper_spec.rb +20 -0
- data/spec/persistence/cookie_adapter_spec.rb +90 -23
- data/spec/persistence/dual_adapter_spec.rb +2 -2
- data/spec/split_spec.rb +7 -7
- data/split.gemspec +16 -6
- metadata +33 -16
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'split/dashboard/paginator'
|
|
3
|
+
|
|
4
|
+
module Split
|
|
5
|
+
module DashboardPaginationHelpers
|
|
6
|
+
DEFAULT_PER = 10
|
|
7
|
+
|
|
8
|
+
def pagination_per
|
|
9
|
+
@pagination_per ||= (params[:per] || DEFAULT_PER).to_i
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def page_number
|
|
13
|
+
@page_number ||= (params[:page] || 1).to_i
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def paginated(collection)
|
|
17
|
+
Split::DashboardPaginator.new(collection, page_number, pagination_per).paginate
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pagination(collection)
|
|
21
|
+
html = []
|
|
22
|
+
html << first_page_tag if show_first_page_tag?
|
|
23
|
+
html << ellipsis_tag if show_first_ellipsis_tag?
|
|
24
|
+
html << prev_page_tag if show_prev_page_tag?
|
|
25
|
+
html << current_page_tag
|
|
26
|
+
html << next_page_tag if show_next_page_tag?(collection)
|
|
27
|
+
html << ellipsis_tag if show_last_ellipsis_tag?(collection)
|
|
28
|
+
html << last_page_tagcollection if show_last_page_tag?(collection)
|
|
29
|
+
html.join
|
|
30
|
+
end
|
|
31
|
+
|
|
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
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Split
|
|
3
|
+
class DashboardPaginator
|
|
4
|
+
def initialize(collection, page_number, per)
|
|
5
|
+
@collection = collection
|
|
6
|
+
@page_number = page_number
|
|
7
|
+
@per = per
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def paginate
|
|
11
|
+
to = @page_number * @per
|
|
12
|
+
from = to - @per
|
|
13
|
+
@collection[from...to]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -317,3 +317,12 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
|
|
320
|
+
.pagination {
|
|
321
|
+
text-align: center;
|
|
322
|
+
font-size: 15px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.pagination a, .paginaton span {
|
|
326
|
+
display: inline-block;
|
|
327
|
+
padding: 5px;
|
|
328
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<input type="button" id="toggle-active" value="Hide active" />
|
|
7
7
|
<input type="button" id="clear-filter" value="Clear filters" />
|
|
8
8
|
|
|
9
|
-
<% @experiments.each do |experiment| %>
|
|
9
|
+
<% paginated(@experiments).each do |experiment| %>
|
|
10
10
|
<% if experiment.goals.empty? %>
|
|
11
11
|
<%= erb :_experiment, :locals => {:goal => nil, :experiment => experiment} %>
|
|
12
12
|
<% else %>
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
<% end %>
|
|
17
17
|
<% end %>
|
|
18
18
|
<% end %>
|
|
19
|
+
|
|
20
|
+
<div class="pagination">
|
|
21
|
+
<%= pagination(@experiments) %>
|
|
22
|
+
</div>
|
|
19
23
|
<% else %>
|
|
20
24
|
<p class="intro">No experiments have started yet, you need to define them in your code and introduce them to your users.</p>
|
|
21
25
|
<p class="intro">Check out the <a href='https://github.com/splitrb/split#readme'>Readme</a> for more help getting started.</p>
|
data/lib/split/dashboard.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'sinatra/base'
|
|
|
3
3
|
require 'split'
|
|
4
4
|
require 'bigdecimal'
|
|
5
5
|
require 'split/dashboard/helpers'
|
|
6
|
+
require 'split/dashboard/pagination_helpers'
|
|
6
7
|
|
|
7
8
|
module Split
|
|
8
9
|
class Dashboard < Sinatra::Base
|
|
@@ -14,6 +15,7 @@ module Split
|
|
|
14
15
|
set :method_override, true
|
|
15
16
|
|
|
16
17
|
helpers Split::DashboardHelpers
|
|
18
|
+
helpers Split::DashboardPaginationHelpers
|
|
17
19
|
|
|
18
20
|
get '/' do
|
|
19
21
|
# Display experiments without a winner at the top of the dashboard
|
data/lib/split/engine.rb
CHANGED
|
@@ -5,6 +5,8 @@ module Split
|
|
|
5
5
|
if Split.configuration.include_rails_helper
|
|
6
6
|
ActionController::Base.send :include, Split::Helper
|
|
7
7
|
ActionController::Base.helper Split::Helper
|
|
8
|
+
ActionController::Base.send :include, Split::CombinedExperimentsHelper
|
|
9
|
+
ActionController::Base.helper Split::CombinedExperimentsHelper
|
|
8
10
|
end
|
|
9
11
|
end
|
|
10
12
|
end
|
data/lib/split/experiment.rb
CHANGED
|
@@ -262,10 +262,11 @@ module Split
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
def calc_winning_alternatives
|
|
265
|
-
#
|
|
266
|
-
|
|
265
|
+
# Cache the winning alternatives so we recalculate them once per the specified interval.
|
|
266
|
+
intervals_since_epoch =
|
|
267
|
+
Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval
|
|
267
268
|
|
|
268
|
-
if self.calc_time !=
|
|
269
|
+
if self.calc_time != intervals_since_epoch
|
|
269
270
|
if goals.empty?
|
|
270
271
|
self.estimate_winning_alternative
|
|
271
272
|
else
|
|
@@ -274,7 +275,7 @@ module Split
|
|
|
274
275
|
end
|
|
275
276
|
end
|
|
276
277
|
|
|
277
|
-
self.calc_time =
|
|
278
|
+
self.calc_time = intervals_since_epoch
|
|
278
279
|
|
|
279
280
|
self.save
|
|
280
281
|
end
|
data/lib/split/helper.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Split
|
|
|
10
10
|
experiment = ExperimentCatalog.find_or_initialize(metric_descriptor, control, *alternatives)
|
|
11
11
|
alternative = if Split.configuration.enabled
|
|
12
12
|
experiment.save
|
|
13
|
+
raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
|
|
13
14
|
trial = Trial.new(:user => ab_user, :experiment => experiment,
|
|
14
15
|
:override => override_alternative(experiment.name), :exclude => exclude_visitor?,
|
|
15
16
|
:disabled => split_generically_disabled?)
|
|
@@ -96,6 +97,14 @@ module Split
|
|
|
96
97
|
Split.configuration.db_failover_on_db_error.call(e)
|
|
97
98
|
end
|
|
98
99
|
|
|
100
|
+
def ab_active_experiments()
|
|
101
|
+
ab_user.active_experiments
|
|
102
|
+
rescue => e
|
|
103
|
+
raise unless Split.configuration.db_failover
|
|
104
|
+
Split.configuration.db_failover_on_db_error.call(e)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
99
108
|
def override_present?(experiment_name)
|
|
100
109
|
override_alternative(experiment_name)
|
|
101
110
|
end
|
|
@@ -113,13 +122,17 @@ module Split
|
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
def exclude_visitor?
|
|
116
|
-
|
|
125
|
+
instance_exec(request, &Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot? || is_preview?
|
|
117
126
|
end
|
|
118
127
|
|
|
119
128
|
def is_robot?
|
|
120
129
|
defined?(request) && request.user_agent =~ Split.configuration.robot_regex
|
|
121
130
|
end
|
|
122
131
|
|
|
132
|
+
def is_preview?
|
|
133
|
+
defined?(request) && defined?(request.headers) && request.headers['x-purpose'] == 'preview'
|
|
134
|
+
end
|
|
135
|
+
|
|
123
136
|
def is_ignored_ip_address?
|
|
124
137
|
return false if Split.configuration.ignore_ip_addresses.empty?
|
|
125
138
|
|
|
@@ -6,20 +6,22 @@ module Split
|
|
|
6
6
|
class CookieAdapter
|
|
7
7
|
|
|
8
8
|
def initialize(context)
|
|
9
|
-
@
|
|
9
|
+
@context = context
|
|
10
|
+
@request, @response = context.request, context.response
|
|
11
|
+
@cookies = @request.cookies
|
|
10
12
|
@expires = Time.now + cookie_length_config
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def [](key)
|
|
14
|
-
hash[key]
|
|
16
|
+
hash[key.to_s]
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def []=(key, value)
|
|
18
|
-
set_cookie(hash.merge(key => value))
|
|
20
|
+
set_cookie(hash.merge!(key.to_s => value))
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def delete(key)
|
|
22
|
-
set_cookie(hash.tap { |h| h.delete(key) })
|
|
24
|
+
set_cookie(hash.tap { |h| h.delete(key.to_s) })
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def keys
|
|
@@ -28,22 +30,55 @@ module Split
|
|
|
28
30
|
|
|
29
31
|
private
|
|
30
32
|
|
|
31
|
-
def set_cookie(value)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
def set_cookie(value = {})
|
|
34
|
+
cookie_key = :split.to_s
|
|
35
|
+
cookie_value = default_options.merge(value: JSON.generate(value))
|
|
36
|
+
if action_dispatch?
|
|
37
|
+
# The "send" is necessary when we call ab_test from the controller
|
|
38
|
+
# and thus @context is a rails controller, because then "cookies" is
|
|
39
|
+
# a private method.
|
|
40
|
+
@context.send(:cookies)[cookie_key] = cookie_value
|
|
41
|
+
else
|
|
42
|
+
set_cookie_via_rack(cookie_key, cookie_value)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def default_options
|
|
47
|
+
{ expires: @expires, path: '/' }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_cookie_via_rack(key, value)
|
|
51
|
+
delete_cookie_header!(@response.header, key, value)
|
|
52
|
+
Rack::Utils.set_cookie_header!(@response.header, key, value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0
|
|
56
|
+
def delete_cookie_header!(header, key, value)
|
|
57
|
+
cookie_header = header['Set-Cookie']
|
|
58
|
+
case cookie_header
|
|
59
|
+
when nil, ''
|
|
60
|
+
cookies = []
|
|
61
|
+
when String
|
|
62
|
+
cookies = cookie_header.split("\n")
|
|
63
|
+
when Array
|
|
64
|
+
cookies = cookie_header
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ }
|
|
68
|
+
header['Set-Cookie'] = cookies.join("\n")
|
|
36
69
|
end
|
|
37
70
|
|
|
38
71
|
def hash
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
72
|
+
@hash ||= begin
|
|
73
|
+
if cookies = @cookies[:split.to_s]
|
|
74
|
+
begin
|
|
75
|
+
JSON.parse(cookies)
|
|
76
|
+
rescue JSON::ParserError
|
|
77
|
+
{}
|
|
78
|
+
end
|
|
79
|
+
else
|
|
43
80
|
{}
|
|
44
81
|
end
|
|
45
|
-
else
|
|
46
|
-
{}
|
|
47
82
|
end
|
|
48
83
|
end
|
|
49
84
|
|
|
@@ -51,6 +86,9 @@ module Split
|
|
|
51
86
|
Split.configuration.persistence_cookie_length
|
|
52
87
|
end
|
|
53
88
|
|
|
89
|
+
def action_dispatch?
|
|
90
|
+
defined?(Rails) && @response.is_a?(ActionDispatch::Response)
|
|
91
|
+
end
|
|
54
92
|
end
|
|
55
93
|
end
|
|
56
94
|
end
|
data/lib/split/version.rb
CHANGED
data/lib/split.rb
CHANGED
|
@@ -13,6 +13,7 @@ require 'split/experiment_catalog'
|
|
|
13
13
|
require 'split/extensions/string'
|
|
14
14
|
require 'split/goals_collection'
|
|
15
15
|
require 'split/helper'
|
|
16
|
+
require 'split/combined_experiments_helper'
|
|
16
17
|
require 'split/metric'
|
|
17
18
|
require 'split/persistence'
|
|
18
19
|
require 'split/redis_interface'
|
data/spec/alternative_spec.rb
CHANGED
|
@@ -273,6 +273,18 @@ describe Split::Alternative do
|
|
|
273
273
|
expect(control.z_score(goal1)).to eq('N/A')
|
|
274
274
|
expect(control.z_score(goal2)).to eq('N/A')
|
|
275
275
|
end
|
|
276
|
+
|
|
277
|
+
it "should not blow up for Conversion Rates > 1" do
|
|
278
|
+
control = experiment.control
|
|
279
|
+
control.participant_count = 3474
|
|
280
|
+
control.set_completed_count(4244)
|
|
281
|
+
|
|
282
|
+
alternative2.participant_count = 3434
|
|
283
|
+
alternative2.set_completed_count(4358)
|
|
284
|
+
|
|
285
|
+
expect { control.z_score }.not_to raise_error
|
|
286
|
+
expect { alternative2.z_score }.not_to raise_error
|
|
287
|
+
end
|
|
276
288
|
end
|
|
277
289
|
|
|
278
290
|
describe "extra_info" do
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'split/combined_experiments_helper'
|
|
4
|
+
|
|
5
|
+
describe Split::CombinedExperimentsHelper do
|
|
6
|
+
include Split::CombinedExperimentsHelper
|
|
7
|
+
|
|
8
|
+
describe 'ab_combined_test' do
|
|
9
|
+
let!(:config_enabled) { true }
|
|
10
|
+
let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
|
|
11
|
+
let!(:allow_multiple_experiments) { true }
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
Split.configuration.experiments = {
|
|
15
|
+
:combined_exp_1 => {
|
|
16
|
+
:alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
|
|
17
|
+
:metric => :my_metric,
|
|
18
|
+
:combined_experiments => combined_experiments
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
Split.configuration.enabled = config_enabled
|
|
22
|
+
Split.configuration.allow_multiple_experiments = allow_multiple_experiments
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'without config enabled' do
|
|
26
|
+
let!(:config_enabled) { false }
|
|
27
|
+
|
|
28
|
+
it "raises an error" do
|
|
29
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'multiple experiments disabled' do
|
|
34
|
+
let!(:allow_multiple_experiments) { false }
|
|
35
|
+
|
|
36
|
+
it "raises an error if multiple experiments is disabled" do
|
|
37
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'without combined experiments' do
|
|
42
|
+
let!(:combined_experiments) { nil }
|
|
43
|
+
|
|
44
|
+
it "raises an error" do
|
|
45
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "uses same alternative for all sub experiments and returns the alternative" do
|
|
50
|
+
allow(self).to receive(:get_alternative) { "test-alt" }
|
|
51
|
+
expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
|
|
52
|
+
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
|
|
53
|
+
|
|
54
|
+
expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'split/dashboard/pagination_helpers'
|
|
3
|
+
|
|
4
|
+
describe Split::DashboardPaginationHelpers do
|
|
5
|
+
include Split::DashboardPaginationHelpers
|
|
6
|
+
|
|
7
|
+
let(:url) { '/split/' }
|
|
8
|
+
|
|
9
|
+
describe '#pagination_per' do
|
|
10
|
+
context 'when params empty' do
|
|
11
|
+
let(:params) { Hash[] }
|
|
12
|
+
|
|
13
|
+
it 'returns 10' do
|
|
14
|
+
expect(pagination_per).to eql 10
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context 'when params[:per] is 5' do
|
|
19
|
+
let(:params) { Hash[per: 5] }
|
|
20
|
+
|
|
21
|
+
it 'returns 5' do
|
|
22
|
+
expect(pagination_per).to eql 5
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#page_number' do
|
|
28
|
+
context 'when params empty' do
|
|
29
|
+
let(:params) { Hash[] }
|
|
30
|
+
|
|
31
|
+
it 'returns 1' do
|
|
32
|
+
expect(page_number).to eql 1
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'when params[:page] is "2"' do
|
|
37
|
+
let(:params) { Hash[page: '2'] }
|
|
38
|
+
|
|
39
|
+
it 'returns 2' do
|
|
40
|
+
expect(page_number).to eql 2
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe '#paginated' do
|
|
46
|
+
let(:collection) { (1..20).to_a }
|
|
47
|
+
let(:params) { Hash[per: '5', page: '3'] }
|
|
48
|
+
|
|
49
|
+
it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#show_first_page_tag?' do
|
|
53
|
+
context 'when page is 1' do
|
|
54
|
+
it { expect(show_first_page_tag?).to be false }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context 'when page is 3' do
|
|
58
|
+
let(:params) { Hash[page: '3'] }
|
|
59
|
+
it { expect(show_first_page_tag?).to be true }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#first_page_tag' do
|
|
64
|
+
it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '#show_first_ellipsis_tag?' do
|
|
68
|
+
context 'when page is 1' do
|
|
69
|
+
it { expect(show_first_ellipsis_tag?).to be false }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'when page is 4' do
|
|
73
|
+
let(:params) { Hash[page: '4'] }
|
|
74
|
+
it { expect(show_first_ellipsis_tag?).to be true }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe '#ellipsis_tag' do
|
|
79
|
+
it { expect(ellipsis_tag).to eql '<span>...</span>' }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#show_prev_page_tag?' do
|
|
83
|
+
context 'when page is 1' do
|
|
84
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context 'when page is 2' do
|
|
88
|
+
let(:params) { Hash[page: '2'] }
|
|
89
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#prev_page_tag' do
|
|
94
|
+
context 'when page is 2' do
|
|
95
|
+
let(:params) { Hash[page: '2'] }
|
|
96
|
+
|
|
97
|
+
it do
|
|
98
|
+
expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
context 'when page is 3' do
|
|
103
|
+
let(:params) { Hash[page: '3'] }
|
|
104
|
+
|
|
105
|
+
it do
|
|
106
|
+
expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe '#show_prev_page_tag?' do
|
|
112
|
+
context 'when page is 1' do
|
|
113
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context 'when page is 2' do
|
|
117
|
+
let(:params) { Hash[page: '2'] }
|
|
118
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe '#current_page_tag' do
|
|
123
|
+
context 'when page is 1' do
|
|
124
|
+
let(:params) { Hash[page: '1'] }
|
|
125
|
+
it { expect(current_page_tag).to eql '<span><b>1</b></span>' }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context 'when page is 2' do
|
|
129
|
+
let(:params) { Hash[page: '2'] }
|
|
130
|
+
it { expect(current_page_tag).to eql '<span><b>2</b></span>' }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#show_next_page_tag?' do
|
|
135
|
+
context 'when page is 2' do
|
|
136
|
+
let(:params) { Hash[page: '2'] }
|
|
137
|
+
|
|
138
|
+
context 'when collection length is 20' do
|
|
139
|
+
let(:collection) { (1..20).to_a }
|
|
140
|
+
it { expect(show_next_page_tag?(collection)).to be false }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context 'when collection length is 25' do
|
|
144
|
+
let(:collection) { (1..25).to_a }
|
|
145
|
+
it { expect(show_next_page_tag?(collection)).to be true }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe '#next_page_tag' do
|
|
151
|
+
context 'when page is 1' do
|
|
152
|
+
let(:params) { Hash[page: '1'] }
|
|
153
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'when page is 2' do
|
|
157
|
+
let(:params) { Hash[page: '2'] }
|
|
158
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe '#total_pages' do
|
|
163
|
+
context 'when collection length is 30' do
|
|
164
|
+
let(:collection) { (1..30).to_a }
|
|
165
|
+
it { expect(total_pages(collection)).to eql 3 }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context 'when collection length is 35' do
|
|
169
|
+
let(:collection) { (1..35).to_a }
|
|
170
|
+
it { expect(total_pages(collection)).to eql 4 }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe '#show_last_ellipsis_tag?' do
|
|
175
|
+
let(:collection) { (1..30).to_a }
|
|
176
|
+
let(:params) { Hash[per: '5', page: '2'] }
|
|
177
|
+
it { expect(show_last_ellipsis_tag?(collection)).to be true }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe '#show_last_page_tag?' do
|
|
181
|
+
let(:collection) { (1..30).to_a }
|
|
182
|
+
|
|
183
|
+
context 'when page is 5/6' do
|
|
184
|
+
let(:params) { Hash[per: '5', page: '5'] }
|
|
185
|
+
it { expect(show_last_page_tag?(collection)).to be false }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
context 'when page is 4/6' do
|
|
189
|
+
let(:params) { Hash[per: '5', page: '4'] }
|
|
190
|
+
it { expect(show_last_page_tag?(collection)).to be true }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe '#last_page_tag' do
|
|
195
|
+
let(:collection) { (1..30).to_a }
|
|
196
|
+
it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'split/dashboard/paginator'
|
|
4
|
+
|
|
5
|
+
describe Split::DashboardPaginator do
|
|
6
|
+
context 'when collection is 1..20' do
|
|
7
|
+
let(:collection) { (1..20).to_a }
|
|
8
|
+
|
|
9
|
+
context 'when per 5 for page' do
|
|
10
|
+
let(:per) { 5 }
|
|
11
|
+
|
|
12
|
+
it 'when page number is 1 result is [1, 2, 3, 4, 5]' do
|
|
13
|
+
result = Split::DashboardPaginator.new(collection, 1, per).paginate
|
|
14
|
+
expect(result).to eql [1, 2, 3, 4, 5]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'when page number is 2 result is [6, 7, 8, 9, 10]' do
|
|
18
|
+
result = Split::DashboardPaginator.new(collection, 2, per).paginate
|
|
19
|
+
expect(result).to eql [6, 7, 8, 9, 10]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'when per 10 for page' do
|
|
24
|
+
let(:per) { 10 }
|
|
25
|
+
|
|
26
|
+
it 'when page number is 1 result is [1..10]' do
|
|
27
|
+
result = Split::DashboardPaginator.new(collection, 1, per).paginate
|
|
28
|
+
expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'when page number is 2 result is [10..20]' do
|
|
32
|
+
result = Split::DashboardPaginator.new(collection, 2, per).paginate
|
|
33
|
+
expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/spec/experiment_spec.rb
CHANGED
|
@@ -458,7 +458,7 @@ describe Split::Experiment do
|
|
|
458
458
|
expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
|
|
459
459
|
end
|
|
460
460
|
|
|
461
|
-
it "should calculate the probability of being the winning alternative separately for each goal" do
|
|
461
|
+
it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do
|
|
462
462
|
experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
|
463
463
|
goal1 = experiment.goals[0]
|
|
464
464
|
goal2 = experiment.goals[1]
|