kameleoon-client-ruby 1.1.2 → 2.1.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/kameleoon/client.rb +541 -404
- data/lib/kameleoon/configuration/experiment.rb +42 -0
- data/lib/kameleoon/configuration/feature_flag.rb +30 -0
- data/lib/kameleoon/configuration/rule.rb +57 -0
- data/lib/kameleoon/configuration/settings.rb +20 -0
- data/lib/kameleoon/configuration/variable.rb +23 -0
- data/lib/kameleoon/configuration/variation.rb +31 -0
- data/lib/kameleoon/configuration/variation_exposition.rb +23 -0
- data/lib/kameleoon/cookie.rb +13 -6
- data/lib/kameleoon/data.rb +60 -40
- data/lib/kameleoon/exceptions.rb +46 -23
- data/lib/kameleoon/factory.rb +21 -18
- data/lib/kameleoon/hybrid/manager.rb +60 -0
- data/lib/kameleoon/real_time/real_time_configuration_service.rb +98 -0
- data/lib/kameleoon/real_time/real_time_event.rb +22 -0
- data/lib/kameleoon/real_time/sse_client.rb +111 -0
- data/lib/kameleoon/real_time/sse_message.rb +23 -0
- data/lib/kameleoon/real_time/sse_request.rb +59 -0
- data/lib/kameleoon/request.rb +14 -13
- data/lib/kameleoon/storage/cache.rb +84 -0
- data/lib/kameleoon/storage/cache_factory.rb +23 -0
- data/lib/kameleoon/storage/variation_storage.rb +42 -0
- data/lib/kameleoon/storage/visitor_variation.rb +20 -0
- data/lib/kameleoon/targeting/condition.rb +17 -5
- data/lib/kameleoon/targeting/condition_factory.rb +9 -2
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +67 -48
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +29 -0
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +44 -0
- data/lib/kameleoon/targeting/models.rb +36 -36
- data/lib/kameleoon/utils.rb +4 -1
- data/lib/kameleoon/version.rb +4 -2
- metadata +35 -3
- data/lib/kameleoon/query_graphql.rb +0 -76
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/targeting/models'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
# Module which contains all internal data of SDK
|
7
|
+
module Configuration
|
8
|
+
# Class for manage all experiments and old feature flags
|
9
|
+
class Experiment
|
10
|
+
attr_accessor :id, :status, :site_enabled, :deviations, :respool_time, :variations, :targeting_segment
|
11
|
+
|
12
|
+
def self.create_from_array(array)
|
13
|
+
array&.map { |it| Experiment.new(it) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(experiment_hash)
|
17
|
+
@id = experiment_hash['id']
|
18
|
+
@status = experiment_hash['status']
|
19
|
+
@site_enabled = experiment_hash['siteEnabled']
|
20
|
+
unless experiment_hash['deviations'].nil?
|
21
|
+
@deviations =
|
22
|
+
Hash[*experiment_hash['deviations'].map do |it|
|
23
|
+
[it['variationId'] == 'origin' ? '0' : it['variationId'], it['value']]
|
24
|
+
end.flatten]
|
25
|
+
end
|
26
|
+
unless experiment_hash['respoolTime'].nil?
|
27
|
+
@respool_time =
|
28
|
+
Hash[*experiment_hash['respoolTime'].map do |it|
|
29
|
+
[it['variationId'] == 'origin' ? '0' : it['variationId'], it['value']]
|
30
|
+
end.flatten]
|
31
|
+
end
|
32
|
+
unless experiment_hash['variations'].nil?
|
33
|
+
@variations =
|
34
|
+
experiment_hash['variations'].map do |it|
|
35
|
+
{ 'id' => it['id'].to_i, 'customJson' => it['customJson'] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@targeting_segment = Kameleoon::Targeting::Segment.new((experiment_hash['segment'])) if experiment_hash['segment']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rule'
|
4
|
+
require_relative 'variation'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
# Module which contains all internal data of SDK
|
8
|
+
module Configuration
|
9
|
+
# Class for manage all feature flags with rules
|
10
|
+
class FeatureFlag
|
11
|
+
attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
|
12
|
+
|
13
|
+
def self.create_from_array(array)
|
14
|
+
array&.map { |it| FeatureFlag.new(it) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(hash)
|
18
|
+
@id = hash['id']
|
19
|
+
@feature_key = hash['featureKey']
|
20
|
+
@default_variation_key = hash['defaultVariationKey']
|
21
|
+
@variations = Variation.create_from_array(hash['variations'])
|
22
|
+
@rules = Rule.create_from_array(hash['rules'])
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_variation_key(key)
|
26
|
+
variations.select { |v| v.key == key }.first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'variation_exposition'
|
4
|
+
require 'kameleoon/targeting/models'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
# Module which contains all internal data of SDK
|
8
|
+
module Configuration
|
9
|
+
# RuleType has a possible rule types
|
10
|
+
module RuleType
|
11
|
+
EXPERIMENTATION = 'EXPERIMENTATION'
|
12
|
+
TARGETED_DELIVERY = 'TARGETED_DELIVERY'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Rule is a class for new rules of feature flags
|
16
|
+
class Rule
|
17
|
+
attr_reader :id, :order, :type, :exposition, :experiment_id, :variation_by_exposition, :respool_time
|
18
|
+
attr_accessor :targeting_segment
|
19
|
+
|
20
|
+
def self.create_from_array(array)
|
21
|
+
array&.map { |it| Rule.new(it) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(hash)
|
25
|
+
@id = hash['id']
|
26
|
+
@order = hash['order']
|
27
|
+
@type = hash['type']
|
28
|
+
@exposition = hash['exposition']
|
29
|
+
@experiment_id = hash['experimentId']
|
30
|
+
@respool_time = hash['respoolTime']
|
31
|
+
@variation_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
|
32
|
+
@targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_variation(hash_double)
|
36
|
+
total = 0.0
|
37
|
+
variation_by_exposition.each do |var_by_exp|
|
38
|
+
total += var_by_exp.exposition
|
39
|
+
return var_by_exp if total >= hash_double
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_variation_id_by_key(key)
|
45
|
+
variation_by_exposition.select { |v| v.variation_key == key }.first&.variation_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def experiment_type?
|
49
|
+
@type == RuleType::EXPERIMENTATION
|
50
|
+
end
|
51
|
+
|
52
|
+
def targeted_delivery_type?
|
53
|
+
@type == RuleType::TARGETED_DELIVERY
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
# Module which contains all internal data of SDK
|
5
|
+
module Configuration
|
6
|
+
# KameleoonConfigurationSettings is used for saving setting's parameters, e.g
|
7
|
+
# state of real time update for site code and etc
|
8
|
+
class Settings
|
9
|
+
attr_accessor :real_time_update
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@real_time_update = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(configuration)
|
16
|
+
@real_time_update = configuration['realTimeUpdate'] || false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'experiment'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
# Module which contains all internal data of SDK
|
7
|
+
module Configuration
|
8
|
+
# Variable class contains key / type / value of variable
|
9
|
+
class Variable
|
10
|
+
attr_accessor :key, :type, :value
|
11
|
+
|
12
|
+
def self.create_from_array(array)
|
13
|
+
array&.map { |it| Variable.new(it) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(hash)
|
17
|
+
@key = hash['key']
|
18
|
+
@type = hash['type']
|
19
|
+
@value = hash['value']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'variable'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
# Module which contains all internal data of SDK
|
7
|
+
module Configuration
|
8
|
+
# Constant values of types of variations
|
9
|
+
module VariationType
|
10
|
+
VARIATION_OFF = 'off'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Variation of feature flag
|
14
|
+
class Variation
|
15
|
+
attr_accessor :key, :variables
|
16
|
+
|
17
|
+
def self.create_from_array(array)
|
18
|
+
array&.map { |it| Variation.new(it) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(hash)
|
22
|
+
@key = hash['key']
|
23
|
+
@variables = Variable.create_from_array(hash['variables'])
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_variable_by_key(key)
|
27
|
+
variables.select { |var| var.key == key }.first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'experiment'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
# Module which contains all internal data of SDK
|
7
|
+
module Configuration
|
8
|
+
# VariationByExposition represents a variation with exposition rate for rule
|
9
|
+
class VariationByExposition
|
10
|
+
attr_accessor :variation_key, :variation_id, :exposition
|
11
|
+
|
12
|
+
def self.create_from_array(array)
|
13
|
+
array&.map { |it| VariationByExposition.new(it) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(hash)
|
17
|
+
@variation_key = hash['variationKey']
|
18
|
+
@variation_id = hash['variationId']
|
19
|
+
@exposition = hash['exposition']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/kameleoon/cookie.rb
CHANGED
@@ -23,15 +23,22 @@ module Kameleoon
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def obtain_hash_double(visitor_code, respool_times = {}, container_id = '')
|
26
|
+
obtain_hash_double_helper(visitor_code, respool_times, container_id, '')
|
27
|
+
end
|
28
|
+
|
29
|
+
def obtain_hash_double_rule(visitor_code, container_id = '', suffix = '')
|
30
|
+
obtain_hash_double_helper(visitor_code, {}, container_id, suffix)
|
31
|
+
end
|
32
|
+
|
33
|
+
def obtain_hash_double_helper(visitor_code, respool_times, container_id, suffix)
|
26
34
|
identifier = visitor_code.to_s
|
27
35
|
identifier += container_id.to_s
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
(Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal("2") ** BigDecimal("256"))).round(16)
|
36
|
+
identifier += suffix.to_s
|
37
|
+
identifier += respool_times.sort.to_h.values.join.to_s if !respool_times.nil? && !respool_times.empty?
|
38
|
+
(Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal('2')**BigDecimal('256'))).round(16)
|
32
39
|
end
|
33
|
-
|
34
|
-
def check_visitor_code(visitor_code)
|
40
|
+
|
41
|
+
def check_visitor_code(visitor_code)
|
35
42
|
if visitor_code.nil?
|
36
43
|
check_default_visitor_code('')
|
37
44
|
elsif
|
data/lib/kameleoon/data.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/exceptions'
|
4
|
+
|
1
5
|
module Kameleoon
|
2
6
|
NONCE_LENGTH = 16
|
3
7
|
|
4
8
|
module DataType
|
5
|
-
CUSTOM =
|
6
|
-
BROWSER =
|
7
|
-
CONVERSION =
|
8
|
-
|
9
|
-
PAGE_VIEW =
|
9
|
+
CUSTOM = 'CUSTOM'
|
10
|
+
BROWSER = 'BROWSER'
|
11
|
+
CONVERSION = 'CONVERSION'
|
12
|
+
DEVICE = 'DEVICE'
|
13
|
+
PAGE_VIEW = 'PAGE_VIEW'
|
10
14
|
end
|
11
15
|
|
12
16
|
module BrowserType
|
@@ -18,6 +22,12 @@ module Kameleoon
|
|
18
22
|
OTHER = 5
|
19
23
|
end
|
20
24
|
|
25
|
+
module DeviceType
|
26
|
+
PHONE = 'PHONE'
|
27
|
+
TABLET = 'TABLET'
|
28
|
+
DESKTOP = 'DESKTOP'
|
29
|
+
end
|
30
|
+
|
21
31
|
class Data
|
22
32
|
attr_accessor :instance, :sent
|
23
33
|
|
@@ -42,38 +52,42 @@ module Kameleoon
|
|
42
52
|
end
|
43
53
|
|
44
54
|
class CustomData < Data
|
45
|
-
|
55
|
+
attr_reader :id, :values
|
46
56
|
|
47
57
|
# @param [Integer] id Id of the custom data
|
48
58
|
# @param [String] value Value of the custom data
|
49
59
|
#
|
50
60
|
# @overload
|
51
61
|
# @param [Hash] hash Json value encoded in a hash.
|
52
|
-
def initialize(*args)
|
62
|
+
def initialize(arg0, *args)
|
53
63
|
@instance = DataType::CUSTOM
|
54
64
|
@sent = false
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@
|
68
|
-
@value = args[1]
|
65
|
+
if arg0.is_a?(Hash)
|
66
|
+
hash = arg0
|
67
|
+
id = hash['id']
|
68
|
+
raise Kameleoon::Exception::NotFound.new('id') if id.nil?
|
69
|
+
@id = id.to_s
|
70
|
+
value = hash['value']
|
71
|
+
values = hash['values']
|
72
|
+
raise Kameleoon::Exception::NotFound.new('values') if values.nil? && value.nil?
|
73
|
+
if values.nil?
|
74
|
+
@values = [value]
|
75
|
+
else
|
76
|
+
@values = values.dup
|
77
|
+
@values.append(value) unless value.nil?
|
69
78
|
end
|
79
|
+
else
|
80
|
+
@id = arg0.to_s
|
81
|
+
@values = args
|
70
82
|
end
|
71
83
|
end
|
72
84
|
|
73
85
|
def obtain_full_post_text_line
|
74
|
-
|
86
|
+
return '' if @values.empty?
|
87
|
+
|
88
|
+
str_values = "[[\"#{@values.join('",1],["')}\",1]]"
|
75
89
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
76
|
-
"eventType=customData&index
|
90
|
+
"eventType=customData&index=#{@id}&valueToCount=#{encode(str_values)}&overwrite=true&nonce=#{nonce}"
|
77
91
|
end
|
78
92
|
end
|
79
93
|
|
@@ -89,7 +103,7 @@ module Kameleoon
|
|
89
103
|
|
90
104
|
def obtain_full_post_text_line
|
91
105
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
92
|
-
"eventType=staticData&
|
106
|
+
"eventType=staticData&browserIndex=" + @browser.to_s + "&nonce=" + nonce
|
93
107
|
end
|
94
108
|
end
|
95
109
|
|
@@ -98,22 +112,22 @@ module Kameleoon
|
|
98
112
|
|
99
113
|
# @param [String] url Url of the page
|
100
114
|
# @param [String] title Title of the page
|
101
|
-
# @param [
|
102
|
-
def initialize(url, title,
|
115
|
+
# @param [Array] referrers Optional field - Referrer ids
|
116
|
+
def initialize(url, title, referrers = nil)
|
103
117
|
@instance = DataType::PAGE_VIEW
|
104
118
|
@sent = false
|
105
119
|
@url = url
|
106
120
|
@title = title
|
107
|
-
@
|
121
|
+
@referrers = referrers
|
108
122
|
end
|
109
123
|
|
110
124
|
def obtain_full_post_text_line
|
111
125
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
112
|
-
referrer_text =
|
113
|
-
unless @
|
114
|
-
referrer_text = "&
|
126
|
+
referrer_text = ''
|
127
|
+
unless @referrers.nil?
|
128
|
+
referrer_text = "&referrersIndices=" + @referrers.to_s
|
115
129
|
end
|
116
|
-
"eventType=page&href=" + encode(@url) + "&title=" + @title +
|
130
|
+
"eventType=page&href=" + encode(@url) + "&title=" + @title + referrer_text + "&nonce=" + nonce
|
117
131
|
end
|
118
132
|
end
|
119
133
|
|
@@ -137,19 +151,25 @@ module Kameleoon
|
|
137
151
|
end
|
138
152
|
end
|
139
153
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
def initialize(index)
|
145
|
-
@instance = DataType::INTEREST
|
154
|
+
# Device uses for sending deviceType parameter for tracking calls
|
155
|
+
class Device < Data
|
156
|
+
def initialize(device_type)
|
157
|
+
@instance = DataType::DEVICE
|
146
158
|
@sent = false
|
147
|
-
@
|
159
|
+
@device_type = device_type
|
148
160
|
end
|
149
161
|
|
150
162
|
def obtain_full_post_text_line
|
151
163
|
nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
152
|
-
"eventType=
|
164
|
+
"eventType=staticData&deviceType=#{@device_type}&nonce=#{nonce}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# UserAgent uses for changing User-Agent header for tracking calls
|
169
|
+
class UserAgent
|
170
|
+
attr_accessor :value
|
171
|
+
def initialize(value)
|
172
|
+
@value = value
|
153
173
|
end
|
154
174
|
end
|
155
|
-
end
|
175
|
+
end
|
data/lib/kameleoon/exceptions.rb
CHANGED
@@ -1,59 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Kameleoon
|
2
4
|
module Exception
|
5
|
+
# Base Error
|
3
6
|
class KameleoonError < ::StandardError
|
4
7
|
def initialize(message = nil)
|
5
|
-
super("Kameleoon error: "
|
8
|
+
super("Kameleoon error: #{message}")
|
6
9
|
end
|
7
10
|
end
|
11
|
+
|
12
|
+
# Base Not Found Error
|
8
13
|
class NotFound < KameleoonError
|
9
|
-
def initialize(value =
|
10
|
-
super(value
|
14
|
+
def initialize(value = '')
|
15
|
+
super("#{value} not found.")
|
11
16
|
end
|
12
17
|
end
|
18
|
+
|
19
|
+
# Variation Not Found
|
13
20
|
class VariationConfigurationNotFound < NotFound
|
14
|
-
def initialize(id =
|
15
|
-
super("Variation "
|
21
|
+
def initialize(id = '')
|
22
|
+
super("Variation #{id}")
|
16
23
|
end
|
17
24
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
|
26
|
+
# Experiment Configuration Not Found
|
27
|
+
class ExperimentConfigurationNotFound < NotFound
|
28
|
+
def initialize(id = '')
|
29
|
+
super("Experiment #{id}")
|
21
30
|
end
|
22
31
|
end
|
32
|
+
|
33
|
+
# Feature Flag Configuration Not Found
|
23
34
|
class FeatureConfigurationNotFound < NotFound
|
24
|
-
def initialize(id =
|
25
|
-
super("Feature flag "
|
35
|
+
def initialize(id = '')
|
36
|
+
super("Feature flag #{id}")
|
26
37
|
end
|
27
38
|
end
|
39
|
+
|
40
|
+
# Feature Variable Not Found
|
28
41
|
class FeatureVariableNotFound < NotFound
|
29
|
-
def initialize(key =
|
30
|
-
super("Feature variable "
|
42
|
+
def initialize(key = '')
|
43
|
+
super("Feature variable #{key}")
|
31
44
|
end
|
32
45
|
end
|
46
|
+
|
47
|
+
# Credentials Not Found
|
33
48
|
class CredentialsNotFound < NotFound
|
34
49
|
def initialize
|
35
|
-
super(
|
50
|
+
super('Credentials')
|
36
51
|
end
|
37
52
|
end
|
53
|
+
|
54
|
+
# Not Targeted (when visitor is not targeted for experiment or feature flag)
|
38
55
|
class NotTargeted < KameleoonError
|
39
|
-
def initialize(visitor_code =
|
40
|
-
super("Visitor
|
56
|
+
def initialize(visitor_code = '')
|
57
|
+
super("Visitor #{visitor_code} is not targeted.")
|
41
58
|
end
|
42
59
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
60
|
+
|
61
|
+
# Not Allocated (when visitor is not allocated for experiment)
|
62
|
+
class NotAllocated < KameleoonError
|
63
|
+
def initialize(visitor_code = '')
|
64
|
+
super("Visitor #{visitor_code} is not targeted.")
|
46
65
|
end
|
47
66
|
end
|
67
|
+
|
68
|
+
# Visitor Code Not Valod (empty or length > 255)
|
48
69
|
class VisitorCodeNotValid < KameleoonError
|
49
|
-
def initialize(message =
|
50
|
-
super("Visitor code not valid: "
|
70
|
+
def initialize(message = '')
|
71
|
+
super("Visitor code not valid: #{message}")
|
51
72
|
end
|
52
73
|
end
|
74
|
+
|
75
|
+
# SiteCode Disabled
|
53
76
|
class SiteCodeDisabled < KameleoonError
|
54
|
-
def initialize(message =
|
55
|
-
super("Site with siteCode '
|
77
|
+
def initialize(message = '')
|
78
|
+
super("Site with siteCode '#{message}' is disabled")
|
56
79
|
end
|
57
80
|
end
|
58
81
|
end
|
59
|
-
end
|
82
|
+
end
|
data/lib/kameleoon/factory.rb
CHANGED
@@ -1,26 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'kameleoon/client'
|
2
4
|
|
3
5
|
module Kameleoon
|
6
|
+
# A Factory class for creating kameleoon clients
|
4
7
|
module ClientFactory
|
5
8
|
CONFIGURATION_UPDATE_INTERVAL = '60m'
|
6
|
-
CONFIG_PATH =
|
7
|
-
DEFAULT_TIMEOUT = 2000 #milli-seconds
|
9
|
+
CONFIG_PATH = '/etc/kameleoon/client-ruby.yaml'
|
10
|
+
DEFAULT_TIMEOUT = 2000 # milli-seconds
|
11
|
+
|
12
|
+
@clients = {}
|
13
|
+
|
14
|
+
def self.create(site_code, config_path = CONFIG_PATH, client_id = nil, client_secret = nil)
|
15
|
+
if @clients[site_code].nil?
|
16
|
+
client = Client.new(site_code, config_path, CONFIGURATION_UPDATE_INTERVAL,
|
17
|
+
DEFAULT_TIMEOUT, client_id, client_secret)
|
18
|
+
client.send(:log, "Client created with site code: #{site_code}")
|
19
|
+
client.send(:fetch_configuration)
|
20
|
+
@clients.store(site_code, client)
|
21
|
+
end
|
22
|
+
@clients[site_code]
|
23
|
+
end
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
# The starting point for using the SDK is the initialization step. All interaction with the SDK is done through an object of the Kameleoon::Client class, therefore you need to create this object via Kameleoon::ClientFactory create static method.
|
12
|
-
#
|
13
|
-
# @param [String] site_code Site code
|
14
|
-
# @param [Boolean] blocking - optional, default is false
|
15
|
-
#
|
16
|
-
# @return [Kameleoon::Client]
|
17
|
-
#
|
18
|
-
def self.create(site_code, blocking = false, config_path = CONFIG_PATH, client_id = nil, client_secret = nil)
|
19
|
-
client = Client.new(site_code, config_path, blocking, CONFIGURATION_UPDATE_INTERVAL, DEFAULT_TIMEOUT, client_id, client_secret)
|
20
|
-
client.send(:log, "Warning: you are using the blocking mode") if blocking
|
21
|
-
client.send(:log, "Client created with site code: " + site_code.to_s)
|
22
|
-
client.send(:fetch_configuration)
|
23
|
-
client
|
25
|
+
def self.forget(site_code)
|
26
|
+
@clients.delete(site_code)
|
24
27
|
end
|
25
28
|
end
|
26
|
-
end
|
29
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Hybrid
|
5
|
+
TC_INIT = 'window.kameleoonQueue=window.kameleoonQueue||[];'
|
6
|
+
TC_ASSIGN_VARIATION_FORMAT = "window.kameleoonQueue.push(['Experiments.assignVariation',%d,%d]);"
|
7
|
+
TC_TRIGGER_FORMAT = "window.kameleoonQueue.push(['Experiments.trigger',%d,true]);"
|
8
|
+
TC_ASSIGN_VARIATION_TRIGGER_FORMAT = TC_ASSIGN_VARIATION_FORMAT + TC_TRIGGER_FORMAT
|
9
|
+
|
10
|
+
# Will be useful for Ruby 3.0
|
11
|
+
# Abstract Manager class (interface)
|
12
|
+
class Manager
|
13
|
+
def add_variation(_visitor, _experiment_id, _variation_id)
|
14
|
+
raise 'Abstract method `add` called'
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_engine_tracking_code(_visitor)
|
18
|
+
raise 'Abstract method `get_engine_tracking_code` called'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Implementation of Cache with auto cleaning feature
|
23
|
+
class ManagerImpl < Manager
|
24
|
+
def initialize(expiration_time, cleaning_interval, cache_factory, log_func)
|
25
|
+
super()
|
26
|
+
# synchronization is necessary for adding same visitor_code from different threads
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@expiration_time = expiration_time
|
29
|
+
@cache_factory = cache_factory
|
30
|
+
@log = log_func
|
31
|
+
# it's recommend to use cleaning_interval 3-4 times more than experiation_time for more performance
|
32
|
+
# in this case on every cleaning iteration storage will be cleaned 2/3 - 3/4 of volume
|
33
|
+
@cache = cache_factory.create(expiration_time, cleaning_interval)
|
34
|
+
@log.call('Hybrid Manager was successfully initialized')
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_variation(visitor_code, experiment_id, variation_id)
|
38
|
+
@mutex.synchronize do
|
39
|
+
visitor_cache = @cache.get(visitor_code)
|
40
|
+
visitor_cache = @cache_factory.create(@expiration_time, 0) if visitor_cache.nil?
|
41
|
+
visitor_cache.set(experiment_id, variation_id)
|
42
|
+
@cache.set(visitor_code, visitor_cache)
|
43
|
+
end
|
44
|
+
@log.call("Hybrid Manager successfully added variation for visitor_code: #{visitor_code}, " \
|
45
|
+
"experiment: #{experiment_id}, variation: #{variation_id}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_engine_tracking_code(visitor_code)
|
49
|
+
tracking_code = TC_INIT
|
50
|
+
visitor_cache = @cache.get(visitor_code)
|
51
|
+
return tracking_code if visitor_cache.nil?
|
52
|
+
|
53
|
+
visitor_cache.active_items.each_pair do |key, value|
|
54
|
+
tracking_code += format(TC_ASSIGN_VARIATION_TRIGGER_FORMAT, key, value, key)
|
55
|
+
end
|
56
|
+
tracking_code
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|