growthbook 0.0.1 → 0.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.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Growthbook
4
+ class InlineExperimentResult
5
+ # Whether or not the user is in the experiment
6
+ # @return [Bool]
7
+ attr_reader :in_experiment
8
+
9
+ # The array index of the assigned variation
10
+ # @return [Integer]
11
+ attr_reader :variation_id
12
+
13
+ # The assigned variation value
14
+ # @return [Any]
15
+ attr_reader :value
16
+
17
+ # If the variation was randomly assigned based on user attribute hashes
18
+ # @return [Bool]
19
+ attr_reader :hash_used
20
+
21
+ # The attribute used to split traffic
22
+ # @return [String]
23
+ attr_reader :hash_attribute
24
+
25
+ # The value of the hashAttribute
26
+ # @return [String]
27
+ attr_reader :hash_value
28
+
29
+ attr_reader :feature_id
30
+
31
+ def initialize(
32
+ hash_used,
33
+ in_experiment,
34
+ variation_id,
35
+ value,
36
+ hash_attribute,
37
+ hash_value,
38
+ feature_id
39
+ )
40
+
41
+ @hash_used = hash_used
42
+ @in_experiment = in_experiment
43
+ @variation_id = variation_id
44
+ @value = value
45
+ @hash_attribute = hash_attribute
46
+ @hash_value = hash_value
47
+ @feature_id = feature_id
48
+ end
49
+
50
+ def to_json(*_args)
51
+ res = {}
52
+ res['inExperiment'] = @in_experiment
53
+ res['hashUsed'] = @hash_used
54
+ res['variationId'] = @variation_id
55
+ res['value'] = @value
56
+ res['hashAttribute'] = @hash_attribute
57
+ res['hashValue'] = @hash_value
58
+ res['featureId'] = @feature_id.to_s
59
+ res
60
+ end
61
+ end
62
+ end
@@ -1,25 +1,39 @@
1
- require "fnv"
1
+ # frozen_string_literal: true
2
+
3
+ require 'fnv'
2
4
 
3
5
  module Growthbook
4
6
  class Util
5
7
  def self.checkRule(actual, op, desired)
6
8
  # Check if both strings are numeric so we can do natural ordering
7
9
  # for greater than / less than operators
8
- numeric = (Float(actual) != nil && Float(desired) != nil) rescue false
10
+ numeric = begin
11
+ (!Float(actual).nil? && !Float(desired).nil?)
12
+ rescue StandardError
13
+ false
14
+ end
9
15
 
10
16
  case op
11
- when "="
17
+ when '='
12
18
  numeric ? Float(actual) == Float(desired) : actual == desired
13
- when "!="
19
+ when '!='
14
20
  numeric ? Float(actual) != Float(desired) : actual != desired
15
- when ">"
21
+ when '>'
16
22
  numeric ? Float(actual) > Float(desired) : actual > desired
17
- when "<"
23
+ when '<'
18
24
  numeric ? Float(actual) < Float(desired) : actual < desired
19
- when "~"
20
- !!(actual =~ Regexp.new(desired)) rescue false
21
- when "!~"
22
- !(actual =~ Regexp.new(desired)) rescue false
25
+ when '~'
26
+ begin
27
+ !!(actual =~ Regexp.new(desired))
28
+ rescue StandardError
29
+ false
30
+ end
31
+ when '!~'
32
+ begin
33
+ actual !~ Regexp.new(desired)
34
+ rescue StandardError
35
+ false
36
+ end
23
37
  else
24
38
  true
25
39
  end
@@ -27,10 +41,10 @@ module Growthbook
27
41
 
28
42
  def self.chooseVariation(userId, experiment)
29
43
  testId = experiment.id
30
- weights = experiment.getScaledWeights()
44
+ weights = experiment.getScaledWeights
31
45
 
32
46
  # Hash the user id and testName to a number from 0 to 1
33
- n = (FNV.new.fnv1a_32(userId + testId)%1000)/1000.0
47
+ n = (FNV.new.fnv1a_32(userId + testId) % 1000) / 1000.0
34
48
 
35
49
  cumulativeWeight = 0
36
50
 
@@ -42,10 +56,97 @@ module Growthbook
42
56
  match = i
43
57
  break
44
58
  end
45
- i+=1
59
+ i += 1
60
+ end
61
+
62
+ match
63
+ end
64
+
65
+ def self.hash(str)
66
+ (FNV.new.fnv1a_32(str) % 1000) / 1000.0
67
+ end
68
+
69
+ def self.in_namespace(userId, namespace)
70
+ n = hash("#{userId}__#{namespace[0]}")
71
+ n >= namespace[1] && n < namespace[2]
72
+ end
73
+
74
+ def self.get_equal_weights(numVariations)
75
+ return [] if numVariations < 1
76
+
77
+ weights = []
78
+ (1..numVariations).each do |_i|
79
+ weights << (1.0 / numVariations)
80
+ end
81
+ weights
82
+ end
83
+
84
+ # Determine bucket ranges for experiment variations
85
+ def self.get_bucket_ranges(numVariations, coverage = 1, weights = [])
86
+ # Make sure coverage is within bounds
87
+ coverage = 1 if coverage.nil?
88
+ coverage = 0 if coverage.negative?
89
+ coverage = 1 if coverage > 1
90
+
91
+ # Default to equal weights
92
+ weights = get_equal_weights(numVariations) if !weights || weights.length != numVariations
93
+
94
+ # If weights don't add up to 1 (or close to it), default to equal weights
95
+ total = weights.sum
96
+ weights = get_equal_weights(numVariations) if total < 0.99 || total > 1.01
97
+
98
+ # Convert weights to ranges
99
+ cumulative = 0
100
+ ranges = []
101
+ weights.each do |w|
102
+ start = cumulative
103
+ cumulative += w
104
+ ranges << [start, start + coverage * w]
105
+ end
106
+
107
+ ranges
108
+ end
109
+
110
+ # Chose a variation based on a hash and range
111
+ def self.choose_variation(n, ranges)
112
+ ranges.each_with_index do |range, i|
113
+ return i if n >= range[0] && n < range[1]
114
+ end
115
+ -1
116
+ end
117
+
118
+ # Get an override variation from a url querystring
119
+ # e.g. http://localhost?my-test=1 will return `1` for id `my-test`
120
+ def self.get_query_string_override(id, url, numVariations)
121
+ # Skip if url is empty
122
+ return nil if url == ''
123
+
124
+ # Parse out the query string
125
+ parsed = URI(url)
126
+ return nil unless parsed.query
127
+
128
+ qs = URI.decode_www_form(parsed.query)
129
+
130
+ # Look for `id` in the querystring and get the value
131
+ vals = qs.assoc(id)
132
+ return nil unless vals
133
+
134
+ val = vals.last
135
+ return nill unless val
136
+
137
+ # Parse the value as an integer
138
+ n = begin
139
+ Integer(val)
140
+ rescue StandardError
141
+ nil
46
142
  end
47
143
 
48
- return match
144
+ # Make sure the integer is within range
145
+ return nil if n.nil?
146
+ return nil if n.negative?
147
+ return nil if n >= numVariations
148
+
149
+ n
49
150
  end
50
151
  end
51
- end
152
+ end
data/lib/growthbook.rb CHANGED
@@ -1,9 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Growthbook
2
4
  end
3
5
 
4
6
  require 'growthbook/client'
7
+ require 'growthbook/conditions'
8
+ require 'growthbook/context'
9
+ require 'growthbook/experiment'
5
10
  require 'growthbook/experiment_result'
11
+ require 'growthbook/feature'
12
+ require 'growthbook/feature_result'
13
+ require 'growthbook/feature_rule'
14
+ require 'growthbook/inline_experiment'
15
+ require 'growthbook/inline_experiment_result'
6
16
  require 'growthbook/lookup_result'
7
- require 'growthbook/experiment'
17
+ require 'growthbook/user'
8
18
  require 'growthbook/util'
9
- require 'growthbook/user'