kameleoon-client-ruby 1.1.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,41 @@
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
+ # Class for manage all experiments and old feature flags
9
+ class FeatureFlag < Experiment
10
+ STATUS_ACTIVE = 'ACTIVE'
11
+ FEATURE_STATUS_DEACTIVATED = 'DEACTIVATED'
12
+ attr_accessor :identification_key, :feature_status, :schedules, :exposition_rate
13
+
14
+ def self.create_from_array(array)
15
+ array&.map { |it| FeatureFlag.new(it) }
16
+ end
17
+
18
+ def initialize(feature_flag_hash)
19
+ super(feature_flag_hash)
20
+ @feature_status = feature_flag_hash['featureStatus']
21
+ @identification_key = feature_flag_hash['identificationKey'] if feature_flag_hash['identificationKey']
22
+ @exposition_rate = feature_flag_hash['expositionRate']
23
+ @schedules = feature_flag_hash['schedules'] unless feature_flag_hash['schedules'].nil?
24
+ end
25
+
26
+ def is_scheduled_active(date)
27
+ current_status = @status == STATUS_ACTIVE
28
+ if @feature_status == FEATURE_STATUS_DEACTIVATED || @schedules.empty?
29
+ return current_status
30
+ end
31
+ @schedules.each do |schedule|
32
+ if (schedule['dateStart'].nil? || Time.parse(schedule['dateStart']).to_i < date) &&
33
+ (schedule['dateEnd'].nil? || Time.parse(schedule['dateEnd']).to_i > date)
34
+ return true
35
+ end
36
+ end
37
+ false
38
+ end
39
+ end
40
+ end
41
+ 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 v2 with rules
10
+ class FeatureFlagV2
11
+ attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
12
+
13
+ def self.create_from_array(array)
14
+ array&.map { |it| FeatureFlagV2.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,45 @@
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_accessor :type, :exposition, :experiment_id, :variation_by_exposition, :targeting_segment
18
+
19
+ def self.create_from_array(array)
20
+ array&.map { |it| Rule.new(it) }
21
+ end
22
+
23
+ def initialize(hash)
24
+ @type = hash['type']
25
+ @exposition = hash['exposition']
26
+ @experiment_id = hash['experimentId']
27
+ @variation_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
28
+ @targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
29
+ end
30
+
31
+ def get_variation_key(hash_double)
32
+ total = 0.0
33
+ variation_by_exposition.each do |element|
34
+ total += element.exposition
35
+ return element.variation_key if total >= hash_double
36
+ end
37
+ nil
38
+ end
39
+
40
+ def get_variation_id_by_key(key)
41
+ variation_by_exposition.select { |v| v.variation_key == key }.first&.variation_id
42
+ end
43
+ end
44
+ end
45
+ 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
@@ -23,12 +23,19 @@ 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_v2(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
- if !respool_times.nil? && !respool_times.empty?
29
- identifier += respool_times.sort.to_h.values.join.to_s
30
- end
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
40
 
34
41
  def check_visitor_code(visitor_code)
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kameleoon
2
4
  NONCE_LENGTH = 16
3
5
 
4
6
  module DataType
5
- CUSTOM = "CUSTOM"
6
- BROWSER = "BROWSER"
7
- CONVERSION = "CONVERSION"
8
- INTEREST = "INTEREST"
9
- PAGE_VIEW = "PAGE_VIEW"
7
+ CUSTOM = 'CUSTOM'
8
+ BROWSER = 'BROWSER'
9
+ CONVERSION = 'CONVERSION'
10
+ DEVICE = 'DEVICE'
11
+ PAGE_VIEW = 'PAGE_VIEW'
10
12
  end
11
13
 
12
14
  module BrowserType
@@ -18,6 +20,12 @@ module Kameleoon
18
20
  OTHER = 5
19
21
  end
20
22
 
23
+ module DeviceType
24
+ PHONE = 'PHONE'
25
+ TABLET = 'TABLET'
26
+ DESKTOP = 'DESKTOP'
27
+ end
28
+
21
29
  class Data
22
30
  attr_accessor :instance, :sent
23
31
 
@@ -89,7 +97,7 @@ module Kameleoon
89
97
 
90
98
  def obtain_full_post_text_line
91
99
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
92
- "eventType=staticData&browser=" + @browser.to_s + "&nonce=" + nonce
100
+ "eventType=staticData&browserIndex=" + @browser.to_s + "&nonce=" + nonce
93
101
  end
94
102
  end
95
103
 
@@ -98,22 +106,22 @@ module Kameleoon
98
106
 
99
107
  # @param [String] url Url of the page
100
108
  # @param [String] title Title of the page
101
- # @param [Integer] referrer Optional field - Referrer id
102
- def initialize(url, title, referrer = nil)
109
+ # @param [Array] referrers Optional field - Referrer ids
110
+ def initialize(url, title, referrers = nil)
103
111
  @instance = DataType::PAGE_VIEW
104
112
  @sent = false
105
113
  @url = url
106
114
  @title = title
107
- @referrer = referrer
115
+ @referrers = referrers
108
116
  end
109
117
 
110
118
  def obtain_full_post_text_line
111
119
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
112
- referrer_text = ""
113
- unless @referrer.nil?
114
- referrer_text = "&referrers=[" + @referrer.to_s + "]"
120
+ referrer_text = ''
121
+ unless @referrers.nil?
122
+ referrer_text = "&referrersIndices=" + @referrers.to_s
115
123
  end
116
- "eventType=page&href=" + encode(@url) + "&title=" + @title + "&keyPages=[]" + referrer_text + "&nonce=" + nonce
124
+ "eventType=page&href=" + encode(@url) + "&title=" + @title + referrer_text + "&nonce=" + nonce
117
125
  end
118
126
  end
119
127
 
@@ -137,19 +145,25 @@ module Kameleoon
137
145
  end
138
146
  end
139
147
 
140
- class Interest < Data
141
- attr_accessor :index
142
-
143
- # @param [Integer] index Index of the interest
144
- def initialize(index)
145
- @instance = DataType::INTEREST
148
+ # Device uses for sending deviceType parameter for tracking calls
149
+ class Device < Data
150
+ def initialize(device_type)
151
+ @instance = DataType::DEVICE
146
152
  @sent = false
147
- @index = index
153
+ @device_type = device_type
148
154
  end
149
155
 
150
156
  def obtain_full_post_text_line
151
157
  nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
152
- "eventType=interests&indexes=[" + @index.to_s + "]&fresh=true&nonce=" + nonce
158
+ "eventType=staticData&deviceType=#{@device_type}&nonce=#{nonce}"
159
+ end
160
+ end
161
+
162
+ # UserAgent uses for changing User-Agent header for tracking calls
163
+ class UserAgent
164
+ attr_accessor :value
165
+ def initialize(value)
166
+ @value = value
153
167
  end
154
168
  end
155
- end
169
+ end
@@ -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: " + message)
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.to_s + " not found.")
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 " + id.to_s)
21
+ def initialize(id = '')
22
+ super("Variation #{id}")
16
23
  end
17
24
  end
18
- class ExperimentConfigurationNotFound < NotFound
19
- def initialize(id = "")
20
- super("Experiment " + id.to_s)
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 " + id.to_s)
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 " + key.to_s)
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("Credentials")
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 " + visitor_code + " is not targeted.")
56
+ def initialize(visitor_code = '')
57
+ super("Visitor #{visitor_code} is not targeted.")
41
58
  end
42
59
  end
43
- class NotActivated < KameleoonError
44
- def initialize(visitor_code = "")
45
- super("Visitor " + visitor_code + " is not activated.")
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: " + message)
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 '" + message + "' is disabled")
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
@@ -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 = "/etc/kameleoon/client-ruby.yaml"
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
- # Create a kameleoon client object, each call create a new client.
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
@@ -1,16 +1,18 @@
1
- require "em-synchrony/em-http"
2
- require "kameleoon/version"
1
+ require 'em-synchrony/em-http'
2
+ require 'kameleoon/version'
3
3
  require 'net/http'
4
4
 
5
5
  module Kameleoon
6
6
  # @api private
7
7
  module Request
8
8
  protected
9
- API_URL = "https://api.kameleoon.com"
9
+
10
+ API_URL = 'https://api.kameleoon.com'.freeze
11
+ CLIENT_CONFIG_URL = 'https://client-config.kameleoon.com'.freeze
10
12
 
11
13
  module Method
12
- GET = "get"
13
- POST = "post"
14
+ GET = 'get'.freeze
15
+ POST = 'post'.freeze
14
16
  end
15
17
 
16
18
  def get(request_options, url = API_URL, connexion_options = {})
@@ -32,7 +34,7 @@ module Kameleoon
32
34
  private
33
35
 
34
36
  def request(method, request_options, url, connexion_options)
35
- connexion_options[:tls] = {verify_peer: false}
37
+ connexion_options[:tls] = { verify_peer: false }
36
38
  add_user_agent(request_options)
37
39
  case method
38
40
  when Method::POST then
@@ -40,7 +42,7 @@ module Kameleoon
40
42
  when Method::GET then
41
43
  return EventMachine::HttpRequest.new(url, connexion_options).get request_options
42
44
  else
43
- print "Unknown request type"
45
+ print 'Unknown request type'
44
46
  return false
45
47
  end
46
48
  end
@@ -68,22 +70,21 @@ module Kameleoon
68
70
  end
69
71
  end
70
72
 
71
- def is_successful(request)
73
+ def successful?(request)
72
74
  !request.nil? && request != false && /20\d/.match(request.response_header.status.to_s)
73
75
  end
74
76
 
75
- def is_successful_sync(response)
77
+ def successful_sync?(response)
76
78
  !response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
77
79
  end
78
80
 
79
81
  def add_user_agent(request_options)
82
+ sdk_version = "sdk/ruby/#{Kameleoon::VERSION}"
80
83
  if request_options[:head].nil?
81
- request_options[:head] = {'Kameleoon-Client' => 'sdk/ruby/' + Kameleoon::VERSION}
84
+ request_options[:head] = { 'Kameleoon-Client' => sdk_version }
82
85
  else
83
- request_options[:head].store('Kameleoon-Client', 'sdk/ruby/' + Kameleoon::VERSION)
86
+ request_options[:head].store('Kameleoon-Client', sdk_version)
84
87
  end
85
88
  end
86
89
  end
87
90
  end
88
-
89
-
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/storage/visitor_variation'
4
+
5
+ module Kameleoon
6
+ module Storage
7
+ # VariationStorage is a container for saved variations associated with a visitor
8
+ class VariationStorage
9
+ def initialize
10
+ @storage = {}
11
+ end
12
+
13
+ def get_variation_id(visitor_code, experiment_id)
14
+ variation_valid?(visitor_code, experiment_id, nil)
15
+ end
16
+
17
+ def variation_valid?(visitor_code, experiment_id, respool_time)
18
+ return nil if @storage[visitor_code].nil? || @storage[visitor_code][experiment_id].nil?
19
+
20
+ variation = @storage[visitor_code][experiment_id]
21
+ return nil unless variation.valid?(respool_time)
22
+
23
+ variation.variation_id
24
+ end
25
+
26
+ def update_variation(visitor_code, experiment_id, variation_id)
27
+ @storage[visitor_code] = {} if @storage[visitor_code].nil?
28
+ @storage[visitor_code][experiment_id] = Kameleoon::Storage::VisitorVariation.new(variation_id)
29
+ end
30
+
31
+ def get_hash_saved_variation_id(visitor_code)
32
+ return nil if @storage[visitor_code].nil?
33
+
34
+ map_variations = {}
35
+ @storage[visitor_code].each do |key, value|
36
+ map_variations[key] = value.variation_id
37
+ end
38
+ map_variations
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module Storage
5
+ # VisitorVariation contains a saved variation id associated with a visitor
6
+ # and time when it was associated.
7
+ class VisitorVariation
8
+ attr_accessor :variation_id
9
+
10
+ def initialize(variation_id)
11
+ @variation_id = variation_id
12
+ @assignment_date = Time.now.to_i
13
+ end
14
+
15
+ def valid?(respool_time)
16
+ respool_time.nil? || @assignment_date > respool_time
17
+ end
18
+ end
19
+ end
20
+ end