growthbook 0.3.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: growthbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GrowthBook
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-08 00:00:00.000000000 Z
11
+ date: 2023-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -25,19 +25,61 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: fnv
28
+ name: rspec-its
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.0
34
- type: :runtime
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.21'
48
+ type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 0.2.0
54
+ version: '0.21'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov-shields-badge
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.18'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.18'
41
83
  description: Official GrowthBook SDK for Ruby
42
84
  email: jeremy@growthbook.io
43
85
  executables: []
@@ -45,29 +87,23 @@ extensions: []
45
87
  extra_rdoc_files: []
46
88
  files:
47
89
  - lib/growthbook.rb
48
- - lib/growthbook/client.rb
49
90
  - lib/growthbook/conditions.rb
50
91
  - lib/growthbook/context.rb
51
- - lib/growthbook/experiment.rb
52
- - lib/growthbook/experiment_result.rb
92
+ - lib/growthbook/decryption_util.rb
53
93
  - lib/growthbook/feature.rb
94
+ - lib/growthbook/feature_repository.rb
54
95
  - lib/growthbook/feature_result.rb
55
96
  - lib/growthbook/feature_rule.rb
97
+ - lib/growthbook/fnv.rb
56
98
  - lib/growthbook/inline_experiment.rb
57
99
  - lib/growthbook/inline_experiment_result.rb
58
- - lib/growthbook/lookup_result.rb
59
- - lib/growthbook/user.rb
100
+ - lib/growthbook/tracking_callback.rb
60
101
  - lib/growthbook/util.rb
61
- - spec/cases.json
62
- - spec/client_spec.rb
63
- - spec/context_spec.rb
64
- - spec/json_spec.rb
65
- - spec/user_spec.rb
66
- - spec/util_spec.rb
67
102
  homepage: https://github.com/growthbook/growthbook-ruby
68
103
  licenses:
69
104
  - MIT
70
- metadata: {}
105
+ metadata:
106
+ rubygems_mfa_required: 'true'
71
107
  post_install_message:
72
108
  rdoc_options: []
73
109
  require_paths:
@@ -76,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
112
  requirements:
77
113
  - - ">="
78
114
  - !ruby/object:Gem::Version
79
- version: '0'
115
+ version: 2.5.0
80
116
  required_rubygems_version: !ruby/object:Gem::Requirement
81
117
  requirements:
82
118
  - - ">="
@@ -87,10 +123,4 @@ rubygems_version: 3.1.2
87
123
  signing_key:
88
124
  specification_version: 4
89
125
  summary: GrowthBook SDK for Ruby
90
- test_files:
91
- - spec/json_spec.rb
92
- - spec/user_spec.rb
93
- - spec/util_spec.rb
94
- - spec/context_spec.rb
95
- - spec/client_spec.rb
96
- - spec/cases.json
126
+ test_files: []
@@ -1,67 +0,0 @@
1
- module Growthbook
2
- class Client
3
- # @returns [Boolean]
4
- attr_accessor :enabled
5
-
6
- # @returns [Array<Growthbook::Experiment>]
7
- attr_accessor :experiments
8
-
9
- # @param config [Hash]
10
- # @option config [Boolean] :enabled (true) Set to false to disable all experiments
11
- # @option config [Array<Growthbook::Experiment>] :experiments ([]) Array of Growthbook::Experiment objects
12
- def initialize(config = {})
13
- @enabled = config.has_key?(:enabled) ? config[:enabled] : true
14
- @experiments = config[:experiments] || []
15
- @resultsToTrack = []
16
- end
17
-
18
- # Look up a pre-configured experiment by id
19
- #
20
- # @param id [String] The experiment id to look up
21
- # @return [Growthbook::Experiment, nil] the experiment object or nil if not found
22
- def getExperiment(id)
23
- match = nil;
24
- @experiments.each do |exp|
25
- if exp.id == id
26
- match = exp
27
- break
28
- end
29
- end
30
- return match
31
- end
32
-
33
- # Get a User object you can run experiments against
34
- #
35
- # @param params [Hash]
36
- # @option params [String, nil] :id The logged-in user id
37
- # @option params [String, nil] :anonId The anonymous id (session id, ip address, cookie, etc.)
38
- # @option params [Hash, nil] :attributes Any user attributes you want to use for experiment targeting
39
- # Values can be any type, even nested arrays and hashes
40
- # @return [Growthbook::User] the User object
41
- def user(params = {})
42
- Growthbook::User.new(
43
- params[:anonId] || nil,
44
- params[:id] || nil,
45
- params[:attributes] || nil,
46
- self
47
- )
48
- end
49
-
50
- def importExperimentsHash(experimentsHash = {})
51
- @experiments = []
52
- experimentsHash.each do |id, data|
53
- variations = data["variations"]
54
-
55
- options = {}
56
- options[:coverage] = data["coverage"] if data.has_key?("coverage")
57
- options[:weights] = data["weights"] if data.has_key?("weights")
58
- options[:force] = data["force"] if data.has_key?("force")
59
- options[:anon] = data["anon"] if data.has_key?("anon")
60
- options[:targeting] = data["targeting"] if data.has_key?("targeting")
61
- options[:data] = data["data"] if data.has_key?("data")
62
-
63
- @experiments << Growthbook::Experiment.new(id, variations, options)
64
- end
65
- end
66
- end
67
- end
@@ -1,72 +0,0 @@
1
- module Growthbook
2
- class Experiment
3
- # @returns [String]
4
- attr_accessor :id
5
-
6
- # @returns [Integer]
7
- attr_accessor :variations
8
-
9
- # @returns [Float]
10
- attr_accessor :coverage
11
-
12
- # @returns [Array<Float>]
13
- attr_accessor :weights
14
-
15
- # @returns [Boolean]
16
- attr_accessor :anon
17
-
18
- # @returns [Array<String>]
19
- attr_accessor :targeting
20
-
21
- # @returns [Integer, nil]
22
- attr_accessor :force
23
-
24
- # @returns [Hash]
25
- attr_accessor :data
26
-
27
- # Constructor for an Experiment
28
- #
29
- # @param id [String] The unique id for this experiment
30
- # @param variations [Integer] The number of variations in this experiment (including the Control)
31
- # @param options [Hash]
32
- # @option options [Float] :coverage (1.0) The percent of elegible traffic to include in the experiment
33
- # @option options [Array<Float>] :weights The relative weights of the variations.
34
- # Length must be the same as the number of variations. Total should add to 1.0.
35
- # Default is an even split between variations
36
- # @option options [Boolean] :anon (false) If false, the experiment uses the logged-in user id for bucketing
37
- # If true, the experiment uses the anonymous id for bucketing
38
- # @option options [Array<String>] :targeting Array of targeting rules in the format "key op value"
39
- # where op is one of: =, !=, <, >, ~, !~
40
- # @option options [Integer, nil] :force If an integer, force all users to get this variation
41
- # @option options [Hash] :data Data to attach to the variations
42
- def initialize(id, variations, options = {})
43
- @id = id
44
- @variations = variations
45
- @coverage = options[:coverage] || 1
46
- @weights = options[:weights] || getEqualWeights()
47
- @force = options.has_key?(:force) ? options[:force] : nil
48
- @anon = options.has_key?(:anon) ? options[:anon] : false
49
- @targeting = options[:targeting] || []
50
- @data = options[:data] || {}
51
- end
52
-
53
- def getScaledWeights
54
- scaled = @weights.map do |n|
55
- n*@coverage
56
- end
57
-
58
- return scaled
59
- end
60
-
61
- private
62
-
63
- def getEqualWeights
64
- weights = []
65
- n = @variations
66
- for i in 1..n
67
- weights << (1.0 / n)
68
- end
69
- return weights
70
- end
71
- end
72
- end
@@ -1,43 +0,0 @@
1
- module Growthbook
2
- class ExperimentResult
3
- # The experiment that was performed
4
- # @return [Growthbook::Experiment, nil] If nil, then the experiment with the required id could not be found
5
- attr_reader :experiment
6
-
7
- # The user that was experimented on
8
- # @return [Growthbook::User]
9
- attr_reader :user
10
-
11
- # The chosen variation. -1 for "not in experiment", 0 for control, 1 for 1st variation, etc.
12
- # @return [Integer]
13
- attr_reader :variation
14
-
15
- # The data tied to the chosen variation
16
- # @return [Hash]
17
- attr_reader :data
18
-
19
- @forced = false
20
-
21
- def forced?
22
- @forced
23
- end
24
-
25
- def shouldTrack?
26
- !@forced && @variation >= 0
27
- end
28
-
29
- def initialize(user = nil, experiment = nil, variation = -1, forced = false)
30
- @experiment = experiment
31
- @variation = variation
32
- @forced = forced
33
-
34
- @data = {}
35
- if experiment && experiment.data
36
- var = variation < 0 ? 0 : variation
37
- experiment.data.each do |k, v|
38
- @data[k] = v[var]
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,44 +0,0 @@
1
- module Growthbook
2
- class LookupResult
3
- # The first matching experiment
4
- # @return [Growthbook::Experiment]
5
- attr_reader :experiment
6
-
7
- # The chosen variation. -1 for "not in experiment", 0 for control, 1 for 1st variation, etc.
8
- # @return [Integer]
9
- attr_reader :variation
10
-
11
- # The data tied to the chosen variation
12
- # @return [Hash]
13
- attr_reader :data
14
-
15
- # The value of the data key that was used to lookup the experiment
16
- attr_reader :value
17
-
18
- @forced
19
-
20
- def forced?
21
- @forced
22
- end
23
-
24
- def shouldTrack?
25
- !@forced && @variation >= 0
26
- end
27
-
28
- def initialize(result, key)
29
- @experiment = result.experiment
30
- @variation = result.variation
31
- @forced = result.forced?
32
-
33
- @data = {}
34
- if @experiment && @experiment.data
35
- var = @variation <0 ? 0 : @variation
36
- @experiment.data.each do |k, v|
37
- @data[k] = v[var]
38
- end
39
- end
40
-
41
- @value = @data[key] || nil
42
- end
43
- end
44
- end
@@ -1,165 +0,0 @@
1
- require 'set'
2
-
3
- module Growthbook
4
- class User
5
- # @returns [String, nil]
6
- attr_accessor :id
7
-
8
- # @returns [String, nil]
9
- attr_accessor :anonId
10
-
11
- # @returns [Hash, nil]
12
- attr_reader :attributes
13
-
14
- # @returns [Array<Growthbook::ExperimentResult>]
15
- attr_reader :resultsToTrack
16
-
17
- @client
18
- @attributeMap = {}
19
- @experimentsTracked
20
-
21
- def initialize(anonId, id, attributes, client)
22
- @anonId = anonId
23
- @id = id
24
- @attributes = attributes
25
- @client = client
26
- updateAttributeMap
27
-
28
- @resultsToTrack = []
29
- @experimentsTracked = Set[]
30
- end
31
-
32
- # Set the user attributes
33
- #
34
- # @params attributes [Hash, nil] Any user attributes you want to use for experiment targeting
35
- # Values can be any type, even nested arrays and hashes
36
- def attributes=(attributes)
37
- @attributes = attributes
38
- updateAttributeMap
39
- end
40
-
41
- # Run an experiment on this user
42
- # @param experiment [Growthbook::Experiment, String] If string, will lookup the experiment by id in the client
43
- # @return [Growthbook::ExperimentResult]
44
- def experiment(experiment)
45
- # If experiments are disabled globally
46
- return getExperimentResult unless @client.enabled
47
-
48
- # Make sure experiment is always an object (or nil)
49
- id = ""
50
- if experiment.is_a? String
51
- id = experiment
52
- experiment = @client.getExperiment(id)
53
- else
54
- id = experiment.id
55
- override = @client.getExperiment(id)
56
- experiment = override if override
57
- end
58
-
59
- # No experiment found
60
- return getExperimentResult unless experiment
61
-
62
- # User missing required user id type
63
- userId = experiment.anon ? @anonId : @id
64
- if !userId
65
- return getExperimentResult(experiment)
66
- end
67
-
68
- # Experiment has targeting rules, check if user passes
69
- if experiment.targeting
70
- return getExperimentResult(experiment) unless isTargeted(experiment.targeting)
71
- end
72
-
73
- # Experiment has a specific variation forced
74
- if experiment.force != nil
75
- return getExperimentResult(experiment, experiment.force, true)
76
- end
77
-
78
- # Choose a variation for the user
79
- variation = Growthbook::Util.chooseVariation(userId, experiment)
80
- result = getExperimentResult(experiment, variation)
81
-
82
- # Add to the list of experiments that should be tracked in analytics
83
- if result.shouldTrack? && !@experimentsTracked.include?(experiment.id)
84
- @experimentsTracked << experiment.id
85
- @resultsToTrack << result
86
- end
87
-
88
- return result
89
- end
90
-
91
- # Run the first matching experiment that defines variation data for the requested key
92
- # @param key [String, Symbol] The key to look up
93
- # @return [Growthbook::LookupResult, nil] If nil, no matching experiments found
94
- def lookupByDataKey(key)
95
- @client.experiments.each do |exp|
96
- if exp.data && exp.data.key?(key)
97
- ret = experiment(exp)
98
- if ret.variation >= 0
99
- return Growthbook::LookupResult.new(ret, key)
100
- end
101
- end
102
- end
103
-
104
- return nil
105
- end
106
-
107
- private
108
-
109
- def getExperimentResult(experiment = nil, variation = -1, forced = false)
110
- Growthbook::ExperimentResult.new(self, experiment, variation, forced)
111
- end
112
-
113
- def flattenUserValues(prefix, val)
114
- if val.nil?
115
- return []
116
- end
117
-
118
- if val.is_a? Hash
119
- ret = []
120
- val.each do |k, v|
121
- ret.concat(flattenUserValues(prefix.length>0 ? prefix.to_s + "." + k.to_s : k.to_s, v))
122
- end
123
- return ret
124
- end
125
-
126
- if val.is_a? Array
127
- val = val.join ","
128
- elsif !!val == val
129
- val = val ? "true" : "false"
130
- end
131
-
132
- return [
133
- {
134
- "k" => prefix.to_s,
135
- "v" => val.to_s
136
- }
137
- ]
138
- end
139
-
140
- def updateAttributeMap
141
- @attributeMap = {}
142
- flat = flattenUserValues("", @attributes)
143
- flat.each do |item|
144
- @attributeMap[item["k"]] = item["v"]
145
- end
146
- end
147
-
148
- def isTargeted(rules)
149
- pass = true
150
- rules.each do |rule|
151
- parts = rule.split(" ", 3)
152
- if parts.length == 3
153
- key = parts[0].strip
154
- actual = @attributeMap[key] || ""
155
- if !Growthbook::Util.checkRule(actual, parts[1].strip, parts[2].strip)
156
- pass = false
157
- break
158
- end
159
- end
160
- end
161
-
162
- return pass
163
- end
164
- end
165
- end