amplitude-experiment 1.3.1 → 1.4.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 +4 -4
- data/lib/amplitude-experiment.rb +3 -0
- data/lib/experiment/error.rb +14 -0
- data/lib/experiment/local/assignment/assignment.rb +3 -1
- data/lib/experiment/local/assignment/assignment_service.rb +15 -14
- data/lib/experiment/local/client.rb +28 -24
- data/lib/experiment/local/evaluation/evaluation.rb +2 -2
- data/lib/experiment/local/evaluation/lib/linuxArm64/libevaluation_interop.so +0 -0
- data/lib/experiment/local/evaluation/lib/linuxArm64/libevaluation_interop_api.h +1 -1
- data/lib/experiment/local/evaluation/lib/linuxX64/libevaluation_interop.so +0 -0
- data/lib/experiment/local/evaluation/lib/linuxX64/libevaluation_interop_api.h +1 -1
- data/lib/experiment/local/evaluation/lib/macosArm64/libevaluation_interop.dylib +0 -0
- data/lib/experiment/local/evaluation/lib/macosArm64/libevaluation_interop_api.h +1 -1
- data/lib/experiment/local/evaluation/lib/macosX64/libevaluation_interop.dylib +0 -0
- data/lib/experiment/local/evaluation/lib/macosX64/libevaluation_interop_api.h +1 -1
- data/lib/experiment/local/fetcher.rb +15 -0
- data/lib/experiment/remote/client.rb +2 -34
- data/lib/experiment/user.rb +17 -3
- data/lib/experiment/util/topological_sort.rb +39 -0
- data/lib/experiment/util/user.rb +33 -0
- data/lib/experiment/util/variant.rb +32 -0
- data/lib/experiment/variant.rb +3 -1
- data/lib/experiment/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6df1a01ac720c72256be526321f9b30f93fcdde050ff6a0743c24bd53a64dd5b
|
4
|
+
data.tar.gz: d10ebba30ae8a59b7964536909315059c8d096dc1b3a9d0e735c7242db400153
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d623457be0fb4cc632f708365d53abbab2b7b647baf7ab37d18d6a05f21e321960e0632152ff2e60686d590e598cfa4333313cb6e31ad29868d2e4798be3e200
|
7
|
+
data.tar.gz: e9800dd0da6e0edf6ee8c01bf289700e7f61963fc2e379ea55ddd54dda7c52c8b85f0185c07fcd2a3d2aa8b92391a0bda9828c794f1960d16e39f1e10ed15210
|
data/lib/amplitude-experiment.rb
CHANGED
@@ -15,6 +15,9 @@ require 'experiment/local/assignment/assignment_service'
|
|
15
15
|
require 'experiment/local/assignment/assignment_config'
|
16
16
|
require 'experiment/util/lru_cache'
|
17
17
|
require 'experiment/util/hash'
|
18
|
+
require 'experiment/util/topological_sort'
|
19
|
+
require 'experiment/util/user'
|
20
|
+
require 'experiment/util/variant'
|
18
21
|
require 'experiment/error'
|
19
22
|
|
20
23
|
# Amplitude Experiment Module
|
data/lib/experiment/error.rb
CHANGED
@@ -8,4 +8,18 @@ module AmplitudeExperiment
|
|
8
8
|
@status_code = status_code
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
class CycleError < StandardError
|
13
|
+
# Raised when topological sorting encounters a cycle between flag dependencies.
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
def initialize(path)
|
17
|
+
super
|
18
|
+
@path = path
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Detected a cycle between flags #{@path}"
|
23
|
+
end
|
24
|
+
end
|
11
25
|
end
|
@@ -13,7 +13,9 @@ module AmplitudeExperiment
|
|
13
13
|
def canonicalize
|
14
14
|
sb = "#{@user&.user_id&.strip} #{@user&.device_id&.strip} "
|
15
15
|
results.sort.to_h.each do |key, value|
|
16
|
-
|
16
|
+
next unless value.key
|
17
|
+
|
18
|
+
sb += "#{key.strip} #{value.key&.strip} "
|
17
19
|
end
|
18
20
|
sb
|
19
21
|
end
|
@@ -8,10 +8,10 @@ module AmplitudeExperiment
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def track(assignment)
|
11
|
-
@amplitude.track(to_event(assignment)) if @assignment_filter.should_track(assignment)
|
11
|
+
@amplitude.track(AssignmentService.to_event(assignment)) if @assignment_filter.should_track(assignment)
|
12
12
|
end
|
13
13
|
|
14
|
-
def to_event(assignment)
|
14
|
+
def self.to_event(assignment)
|
15
15
|
event = AmplitudeAnalytics::BaseEvent.new(
|
16
16
|
'[Experiment] Assignment',
|
17
17
|
user_id: assignment.user.user_id,
|
@@ -20,28 +20,29 @@ module AmplitudeExperiment
|
|
20
20
|
user_properties: {}
|
21
21
|
)
|
22
22
|
|
23
|
-
assignment.results.each do |results_key, result|
|
24
|
-
event.event_properties["#{results_key}.variant"] = result['variant']['key']
|
25
|
-
end
|
26
|
-
|
27
23
|
set = {}
|
28
24
|
unset = {}
|
29
25
|
|
30
|
-
assignment.results.each do |
|
31
|
-
next
|
26
|
+
assignment.results.sort.to_h.each do |flag_key, variant|
|
27
|
+
next unless variant.key
|
28
|
+
|
29
|
+
version = variant.metadata['flagVersion'] if variant.metadata
|
30
|
+
segment_name = variant.metadata['segmentName'] if variant.metadata
|
31
|
+
flag_type = variant.metadata['flagType'] if variant.metadata
|
32
|
+
default = variant.metadata ? variant.metadata.fetch('default', false) : false
|
33
|
+
event.event_properties["#{flag_key}.variant"] = variant.key
|
34
|
+
event.event_properties["#{flag_key}.details"] = "v#{version} rule:#{segment_name}" if version && segment_name
|
35
|
+
next if flag_type == FLAG_TYPE_MUTUAL_EXCLUSION_GROUP
|
32
36
|
|
33
|
-
if
|
34
|
-
unset["[Experiment] #{
|
37
|
+
if default
|
38
|
+
unset["[Experiment] #{flag_key}"] = '-'
|
35
39
|
else
|
36
|
-
set["[Experiment] #{
|
40
|
+
set["[Experiment] #{flag_key}"] = variant.key
|
37
41
|
end
|
38
42
|
end
|
39
|
-
|
40
43
|
event.user_properties['$set'] = set
|
41
44
|
event.user_properties['$unset'] = unset
|
42
|
-
|
43
45
|
event.insert_id = "#{event.user_id} #{event.device_id} #{AmplitudeExperiment.hash_code(assignment.canonicalize)} #{assignment.timestamp / DAY_MILLIS}"
|
44
|
-
|
45
46
|
event
|
46
47
|
end
|
47
48
|
end
|
@@ -3,8 +3,7 @@ require 'logger'
|
|
3
3
|
require_relative '../../amplitude'
|
4
4
|
|
5
5
|
module AmplitudeExperiment
|
6
|
-
FLAG_TYPE_MUTUAL_EXCLUSION_GROUP = '
|
7
|
-
FLAG_TYPE_HOLDOUT_GROUP = 'holdout-group'.freeze
|
6
|
+
FLAG_TYPE_MUTUAL_EXCLUSION_GROUP = 'mutual-exclusion-group'.freeze
|
8
7
|
# Main client for fetching variant data.
|
9
8
|
class LocalEvaluationClient
|
10
9
|
# Creates a new Experiment Client instance.
|
@@ -37,18 +36,38 @@ module AmplitudeExperiment
|
|
37
36
|
# @param [String[]] flag_keys The flags to evaluate with the user. If empty, all flags from the flag cache are evaluated
|
38
37
|
#
|
39
38
|
# @return [Hash[String, Variant]] The evaluated variants
|
39
|
+
# @deprecated Please use {evaluate_v2} instead
|
40
40
|
def evaluate(user, flag_keys = [])
|
41
|
+
variants = evaluate_v2(user, flag_keys)
|
42
|
+
AmplitudeExperiment.filter_default_variants(variants)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Locally evaluates flag variants for a user.
|
46
|
+
# This function will only evaluate flags for the keys specified in the flag_keys argument. If flag_keys is
|
47
|
+
# missing or None, all flags are evaluated. This function differs from evaluate as it will return a default
|
48
|
+
# variant object if the flag was evaluated but the user was not assigned (i.e. off).
|
49
|
+
#
|
50
|
+
# @param [User] user The user to evaluate
|
51
|
+
# @param [String[]] flag_keys The flags to evaluate with the user, if empty all flags are evaluated
|
52
|
+
# @return [Hash[String, Variant]] The evaluated variants
|
53
|
+
def evaluate_v2(user, flag_keys = [])
|
41
54
|
flags = @flags_mutex.synchronize do
|
42
55
|
@flags
|
43
56
|
end
|
44
57
|
return {} if flags.nil?
|
45
58
|
|
46
|
-
|
59
|
+
sorted_flags = AmplitudeExperiment.topological_sort(flags, flag_keys.to_set)
|
60
|
+
flags_json = sorted_flags.to_json
|
61
|
+
|
62
|
+
enriched_user = AmplitudeExperiment.user_to_evaluation_context(user)
|
63
|
+
user_str = enriched_user.to_json
|
47
64
|
|
48
65
|
@logger.debug("[Experiment] Evaluate: User: #{user_str} - Rules: #{flags}") if @config.debug
|
49
|
-
result = evaluation(
|
66
|
+
result = evaluation(flags_json, user_str)
|
50
67
|
@logger.debug("[Experiment] evaluate - result: #{result}") if @config.debug
|
51
|
-
|
68
|
+
variants = AmplitudeExperiment.evaluation_variants_json_to_variants(result)
|
69
|
+
@assignment_service&.track(Assignment.new(user, variants))
|
70
|
+
variants
|
52
71
|
end
|
53
72
|
|
54
73
|
# Fetch initial flag configurations and start polling for updates.
|
@@ -69,29 +88,14 @@ module AmplitudeExperiment
|
|
69
88
|
|
70
89
|
private
|
71
90
|
|
72
|
-
def parse_results(result, flag_keys, user)
|
73
|
-
variants = {}
|
74
|
-
assignments = {}
|
75
|
-
result.each do |key, value|
|
76
|
-
included = flag_keys.empty? || flag_keys.include?(key)
|
77
|
-
if !value['isDefaultVariant'] && included
|
78
|
-
variant_key = value['variant']['key']
|
79
|
-
variant_payload = value['variant']['payload']
|
80
|
-
variants.store(key, Variant.new(variant_key, variant_payload))
|
81
|
-
end
|
82
|
-
|
83
|
-
assignments[key] = value if included || value['type'] == FLAG_TYPE_MUTUAL_EXCLUSION_GROUP || value['type'] == FLAG_TYPE_HOLDOUT_GROUP
|
84
|
-
end
|
85
|
-
@assignment_service&.track(Assignment.new(user, assignments))
|
86
|
-
variants
|
87
|
-
end
|
88
|
-
|
89
91
|
def run
|
90
92
|
@is_running = true
|
91
93
|
begin
|
92
|
-
flags = @fetcher.
|
94
|
+
flags = @fetcher.fetch_v2
|
95
|
+
flags_obj = JSON.parse(flags)
|
96
|
+
flags_map = flags_obj.each_with_object({}) { |flag, hash| hash[flag['key']] = flag }
|
93
97
|
@flags_mutex.synchronize do
|
94
|
-
@flags =
|
98
|
+
@flags = flags_map
|
95
99
|
end
|
96
100
|
rescue StandardError => e
|
97
101
|
@logger.error("[Experiment] Flag poller - error: #{e.message}")
|
@@ -57,11 +57,11 @@ module EvaluationInterop
|
|
57
57
|
attach_function :libevaluation_interop_symbols, [], Libevaluation_interop_ExportedSymbols.by_ref
|
58
58
|
end
|
59
59
|
|
60
|
-
def evaluation(rule_json,
|
60
|
+
def evaluation(rule_json, context_json)
|
61
61
|
lib = EvaluationInterop.libevaluation_interop_symbols()
|
62
62
|
evaluate = lib[:kotlin][:root][:evaluate]
|
63
63
|
dispose = lib[:DisposeString]
|
64
|
-
result_raw = evaluate.call(rule_json,
|
64
|
+
result_raw = evaluate.call(rule_json, context_json)
|
65
65
|
result_json = result_raw.read_string
|
66
66
|
result = JSON.parse(result_json)
|
67
67
|
dispose.call(result_raw)
|
Binary file
|
@@ -99,7 +99,7 @@ typedef struct {
|
|
99
99
|
/* User functions. */
|
100
100
|
struct {
|
101
101
|
struct {
|
102
|
-
const char* (*evaluate)(const char*
|
102
|
+
const char* (*evaluate)(const char* flags, const char* context);
|
103
103
|
} root;
|
104
104
|
} kotlin;
|
105
105
|
} libevaluation_interop_ExportedSymbols;
|
Binary file
|
@@ -99,7 +99,7 @@ typedef struct {
|
|
99
99
|
/* User functions. */
|
100
100
|
struct {
|
101
101
|
struct {
|
102
|
-
const char* (*evaluate)(const char*
|
102
|
+
const char* (*evaluate)(const char* flags, const char* context);
|
103
103
|
} root;
|
104
104
|
} kotlin;
|
105
105
|
} libevaluation_interop_ExportedSymbols;
|
Binary file
|
@@ -99,7 +99,7 @@ typedef struct {
|
|
99
99
|
/* User functions. */
|
100
100
|
struct {
|
101
101
|
struct {
|
102
|
-
const char* (*evaluate)(const char*
|
102
|
+
const char* (*evaluate)(const char* flags, const char* context);
|
103
103
|
} root;
|
104
104
|
} kotlin;
|
105
105
|
} libevaluation_interop_ExportedSymbols;
|
Binary file
|
@@ -99,7 +99,7 @@ typedef struct {
|
|
99
99
|
/* User functions. */
|
100
100
|
struct {
|
101
101
|
struct {
|
102
|
-
const char* (*evaluate)(const char*
|
102
|
+
const char* (*evaluate)(const char* flags, const char* context);
|
103
103
|
} root;
|
104
104
|
} kotlin;
|
105
105
|
} libevaluation_interop_ExportedSymbols;
|
@@ -31,6 +31,21 @@ module AmplitudeExperiment
|
|
31
31
|
response.body
|
32
32
|
end
|
33
33
|
|
34
|
+
def fetch_v2
|
35
|
+
# fetch flag_configs
|
36
|
+
headers = {
|
37
|
+
'Authorization' => "Api-Key #{@api_key}",
|
38
|
+
'Content-Type' => 'application/json;charset=utf-8',
|
39
|
+
'X-Amp-Exp-Library' => "experiment-ruby-server/#{VERSION}"
|
40
|
+
}
|
41
|
+
request = Net::HTTP::Get.new("#{@server_url}/sdk/v2/flags?v=0", headers)
|
42
|
+
response = @http.request(request)
|
43
|
+
raise "flagConfigs - received error response: #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPOK)
|
44
|
+
|
45
|
+
@logger.debug("[Experiment] Fetch flag configs: #{response.body}")
|
46
|
+
response.body
|
47
|
+
end
|
48
|
+
|
34
49
|
# Fetch local evaluation mode flag configs from the Experiment API server.
|
35
50
|
# These flag configs can be used to perform local evaluation.
|
36
51
|
#
|
@@ -30,7 +30,7 @@ module AmplitudeExperiment
|
|
30
30
|
# @param [User] user
|
31
31
|
# @return [Hash] Variants Hash
|
32
32
|
def fetch(user)
|
33
|
-
filter_default_variants(fetch_internal(user))
|
33
|
+
AmplitudeExperiment.filter_default_variants(fetch_internal(user))
|
34
34
|
rescue StandardError => e
|
35
35
|
@logger.error("[Experiment] Failed to fetch variants: #{e.message}")
|
36
36
|
{}
|
@@ -144,30 +144,11 @@ module AmplitudeExperiment
|
|
144
144
|
raise FetchError.new(response.code.to_i, "Fetch error response: status=#{response.code} #{response.message}") if response.code != '200'
|
145
145
|
|
146
146
|
json = JSON.parse(response.body)
|
147
|
-
variants =
|
147
|
+
variants = AmplitudeExperiment.evaluation_variants_json_to_variants(json)
|
148
148
|
@logger.debug("[Experiment] Fetched variants: #{variants}")
|
149
149
|
variants
|
150
150
|
end
|
151
151
|
|
152
|
-
# Parse JSON response hash
|
153
|
-
#
|
154
|
-
# @param [Hash] json
|
155
|
-
# @return [Hash] Hash with String => Variant
|
156
|
-
def parse_json_variants(json)
|
157
|
-
variants = {}
|
158
|
-
json.each do |key, value|
|
159
|
-
variant_value = ''
|
160
|
-
if value.key?('value')
|
161
|
-
variant_value = value.fetch('value')
|
162
|
-
elsif value.key?('key')
|
163
|
-
# value was previously under the "key" field
|
164
|
-
variant_value = value.fetch('key')
|
165
|
-
end
|
166
|
-
variants.store(key, Variant.new(variant_value, value.fetch('payload', nil), value.fetch('key', nil), value.fetch('metadata', nil)))
|
167
|
-
end
|
168
|
-
variants
|
169
|
-
end
|
170
|
-
|
171
152
|
# @param [User] user
|
172
153
|
# @return [User, Hash] user with library context
|
173
154
|
def add_context(user)
|
@@ -181,18 +162,5 @@ module AmplitudeExperiment
|
|
181
162
|
|
182
163
|
true
|
183
164
|
end
|
184
|
-
|
185
|
-
def filter_default_variants(variants)
|
186
|
-
variants.each do |key, value|
|
187
|
-
default = value&.metadata&.fetch('default', nil)
|
188
|
-
deployed = value&.metadata&.fetch('deployed', nil)
|
189
|
-
default = false if default.nil?
|
190
|
-
deployed = true if deployed.nil?
|
191
|
-
variants.delete(key) if default || !deployed
|
192
|
-
end
|
193
|
-
variants
|
194
|
-
end
|
195
|
-
|
196
|
-
private :filter_default_variants
|
197
165
|
end
|
198
166
|
end
|
data/lib/experiment/user.rb
CHANGED
@@ -72,6 +72,14 @@ module AmplitudeExperiment
|
|
72
72
|
# @return [Hash, nil] the value of user properties
|
73
73
|
attr_accessor :user_properties
|
74
74
|
|
75
|
+
# Predefined field, must be manually provided
|
76
|
+
# @return [Hash, nil] the value of groups
|
77
|
+
attr_accessor :groups
|
78
|
+
|
79
|
+
# Predefined field, must be manually provided
|
80
|
+
# @return [Hash, nil] the value of group properties
|
81
|
+
attr_accessor :group_properties
|
82
|
+
|
75
83
|
# @param [String, nil] device_id Device ID for associating with an identity in Amplitude
|
76
84
|
# @param [String, nil] user_id User ID for associating with an identity in Amplitude
|
77
85
|
# @param [String, nil] country Predefined field, must be manually provided
|
@@ -89,9 +97,11 @@ module AmplitudeExperiment
|
|
89
97
|
# @param [String, nil] carrier Predefined field, must be manually provided
|
90
98
|
# @param [String, nil] library Predefined field, auto populated, can be manually overridden
|
91
99
|
# @param [Hash, nil] user_properties Custom user properties
|
100
|
+
# @param [Hash, nil] groups List of groups the user belongs to
|
101
|
+
# @param [Hash, nil] group_properties Custom properties for groups
|
92
102
|
def initialize(device_id: nil, user_id: nil, country: nil, city: nil, region: nil, dma: nil, ip_address: nil, language: nil,
|
93
103
|
platform: nil, version: nil, os: nil, device_manufacturer: nil, device_brand: nil,
|
94
|
-
device_model: nil, carrier: nil, library: nil, user_properties: nil)
|
104
|
+
device_model: nil, carrier: nil, library: nil, user_properties: nil, groups: nil, group_properties: nil)
|
95
105
|
@device_id = device_id
|
96
106
|
@user_id = user_id
|
97
107
|
@country = country
|
@@ -109,6 +119,8 @@ module AmplitudeExperiment
|
|
109
119
|
@carrier = carrier
|
110
120
|
@library = library
|
111
121
|
@user_properties = user_properties
|
122
|
+
@groups = groups
|
123
|
+
@group_properties = group_properties
|
112
124
|
end
|
113
125
|
|
114
126
|
# Return User as Hash.
|
@@ -131,8 +143,10 @@ module AmplitudeExperiment
|
|
131
143
|
device_model: @device_model,
|
132
144
|
carrier: @carrier,
|
133
145
|
library: @library,
|
134
|
-
user_properties: @user_properties
|
135
|
-
|
146
|
+
user_properties: @user_properties,
|
147
|
+
groups: @groups,
|
148
|
+
group_properties: @group_properties
|
149
|
+
}.compact
|
136
150
|
end
|
137
151
|
|
138
152
|
# Return user information as JSON string.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AmplitudeExperiment
|
2
|
+
def self.topological_sort(flags, keys = nil, ordered: false)
|
3
|
+
available = flags.dup
|
4
|
+
result = []
|
5
|
+
starting_keys = keys.nil? || keys.empty? ? flags.keys : keys
|
6
|
+
# Used for testing to ensure consistency.
|
7
|
+
starting_keys.sort! if ordered && (keys.nil? || keys.empty?)
|
8
|
+
|
9
|
+
starting_keys.each do |flag_key|
|
10
|
+
traversal = parent_traversal(flag_key, available, Set.new)
|
11
|
+
result.concat(traversal) unless traversal.nil?
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parent_traversal(flag_key, available, path)
|
17
|
+
flag = available[flag_key]
|
18
|
+
return nil if flag.nil?
|
19
|
+
|
20
|
+
dependencies = flag['dependencies']
|
21
|
+
if dependencies.nil? || dependencies.empty?
|
22
|
+
available.delete(flag_key)
|
23
|
+
return [flag]
|
24
|
+
end
|
25
|
+
|
26
|
+
path.add(flag_key)
|
27
|
+
result = []
|
28
|
+
dependencies.each do |parent_key|
|
29
|
+
raise CycleError, path if path.include?(parent_key)
|
30
|
+
|
31
|
+
traversal = parent_traversal(parent_key, available, path)
|
32
|
+
result.concat(traversal) unless traversal.nil?
|
33
|
+
end
|
34
|
+
result << flag
|
35
|
+
path.delete(flag_key)
|
36
|
+
available.delete(flag_key)
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AmplitudeExperiment
|
2
|
+
def self.user_to_evaluation_context(user)
|
3
|
+
user_groups = user.groups
|
4
|
+
user_group_properties = user.group_properties
|
5
|
+
user_hash = user.as_json.compact
|
6
|
+
user_hash.delete(:groups)
|
7
|
+
user_hash.delete(:group_properties)
|
8
|
+
|
9
|
+
context = user_hash.empty? ? {} : { user: user_hash }
|
10
|
+
|
11
|
+
return context if user_groups.nil?
|
12
|
+
|
13
|
+
groups = {}
|
14
|
+
user_groups.each do |group_type, group_name|
|
15
|
+
group_name = group_name[0] if group_name.is_a?(Array) && !group_name.empty?
|
16
|
+
|
17
|
+
groups[group_type.to_sym] = { group_name: group_name }
|
18
|
+
|
19
|
+
next if user_group_properties.nil?
|
20
|
+
|
21
|
+
group_properties_type = user_group_properties[group_type.to_sym]
|
22
|
+
next if group_properties_type.nil? || !group_properties_type.is_a?(Hash)
|
23
|
+
|
24
|
+
group_properties_name = group_properties_type[group_name.to_sym]
|
25
|
+
next if group_properties_name.nil? || !group_properties_name.is_a?(Hash)
|
26
|
+
|
27
|
+
groups[group_type.to_sym][:group_properties] = group_properties_name
|
28
|
+
end
|
29
|
+
|
30
|
+
context[:groups] = groups unless groups.empty?
|
31
|
+
context
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
module AmplitudeExperiment
|
3
|
+
def self.evaluation_variants_json_to_variants(variants_json)
|
4
|
+
variants = {}
|
5
|
+
variants_json.each do |key, value|
|
6
|
+
variants[key] = AmplitudeExperiment.evaluation_variant_json_to_variant(value)
|
7
|
+
end
|
8
|
+
variants
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.evaluation_variant_json_to_variant(variant_json)
|
12
|
+
value = variant_json['value']
|
13
|
+
value = value.to_json if value && !value.is_a?(String)
|
14
|
+
Variant.new(
|
15
|
+
value: value,
|
16
|
+
key: variant_json['key'],
|
17
|
+
payload: variant_json['payload'],
|
18
|
+
metadata: variant_json['metadata']
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.filter_default_variants(variants)
|
23
|
+
variants.each do |key, value|
|
24
|
+
default = value&.metadata&.fetch('default', nil)
|
25
|
+
deployed = value&.metadata&.fetch('deployed', nil)
|
26
|
+
default = false if default.nil?
|
27
|
+
deployed = true if deployed.nil?
|
28
|
+
variants.delete(key) if default || !deployed
|
29
|
+
end
|
30
|
+
variants
|
31
|
+
end
|
32
|
+
end
|
data/lib/experiment/variant.rb
CHANGED
@@ -17,7 +17,9 @@ module AmplitudeExperiment
|
|
17
17
|
|
18
18
|
# @param [String] value The value of the variant determined by the flag configuration.
|
19
19
|
# @param [Object, nil] payload The attached payload, if any.
|
20
|
-
|
20
|
+
# @param [String] key The key of the variant determined by the flag configuration.
|
21
|
+
# @param [Object, nil] metadata The attached metadata, if any.
|
22
|
+
def initialize(value: nil, payload: nil, key: nil, metadata: nil)
|
21
23
|
@key = key
|
22
24
|
@value = value
|
23
25
|
@payload = payload
|
data/lib/experiment/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amplitude-experiment
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amplitude
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -200,6 +200,9 @@ files:
|
|
200
200
|
- lib/experiment/user.rb
|
201
201
|
- lib/experiment/util/hash.rb
|
202
202
|
- lib/experiment/util/lru_cache.rb
|
203
|
+
- lib/experiment/util/topological_sort.rb
|
204
|
+
- lib/experiment/util/user.rb
|
205
|
+
- lib/experiment/util/variant.rb
|
203
206
|
- lib/experiment/variant.rb
|
204
207
|
- lib/experiment/version.rb
|
205
208
|
homepage: https://github.com/amplitude/experiment-ruby-server
|