kameleoon-client-ruby 2.3.0 → 3.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_readiness.rb +40 -0
- data/lib/kameleoon/configuration/data_file.rb +41 -0
- data/lib/kameleoon/configuration/feature_flag.rb +3 -2
- data/lib/kameleoon/configuration/rule.rb +18 -3
- data/lib/kameleoon/configuration/settings.rb +5 -0
- data/lib/kameleoon/configuration/variable.rb +0 -2
- data/lib/kameleoon/configuration/variation_exposition.rb +0 -2
- data/lib/kameleoon/data/browser.rb +1 -1
- data/lib/kameleoon/data/conversion.rb +1 -1
- data/lib/kameleoon/data/custom_data.rb +10 -14
- data/lib/kameleoon/data/data.rb +22 -3
- data/lib/kameleoon/data/device.rb +2 -1
- data/lib/kameleoon/data/manager/assigned_variation.rb +38 -0
- data/lib/kameleoon/data/manager/data_array_storage.rb +43 -0
- data/lib/kameleoon/data/manager/data_map_storage.rb +43 -0
- data/lib/kameleoon/data/manager/page_view_visit.rb +19 -0
- data/lib/kameleoon/data/manager/visitor.rb +142 -0
- data/lib/kameleoon/data/manager/visitor_manager.rb +71 -0
- data/lib/kameleoon/data/page_view.rb +2 -1
- data/lib/kameleoon/exceptions.rb +30 -35
- data/lib/kameleoon/hybrid/manager.rb +13 -31
- data/lib/kameleoon/{client.rb → kameleoon_client.rb} +194 -334
- data/lib/kameleoon/kameleoon_client_config.rb +91 -0
- data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
- data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +33 -0
- data/lib/kameleoon/network/access_token_source.rb +109 -0
- data/lib/kameleoon/network/activity_event.rb +6 -3
- data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
- data/lib/kameleoon/network/net_provider.rb +25 -58
- data/lib/kameleoon/network/network_manager.rb +65 -43
- data/lib/kameleoon/network/request.rb +7 -2
- data/lib/kameleoon/network/response.rb +0 -8
- data/lib/kameleoon/network/url_provider.rb +30 -12
- data/lib/kameleoon/real_time/sse_client.rb +2 -0
- data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
- data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
- data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
- data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
- data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
- data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
- data/lib/kameleoon/utils.rb +41 -4
- data/lib/kameleoon/version.rb +1 -1
- data/lib/kameleoon.rb +4 -2
- metadata +16 -10
- data/lib/kameleoon/client_config.rb +0 -44
- data/lib/kameleoon/configuration/experiment.rb +0 -42
- data/lib/kameleoon/cookie.rb +0 -88
- data/lib/kameleoon/factory.rb +0 -43
- data/lib/kameleoon/network/experiment_event.rb +0 -35
- data/lib/kameleoon/storage/variation_storage.rb +0 -42
- data/lib/kameleoon/storage/visitor_variation.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f1490fd854e391fea3b908cad394e328a3540b6368942b72c1a9979c9002b87
|
4
|
+
data.tar.gz: 3abf894648cd3662a1a3a3453254014fa20c5405af28f6e01c8e336c810c6031
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76f8eb07daaf102c292c9ed04a6d788dfcef4a7cf757875603a68d2a1272b8e543dbfabd89226a6f775cd71d243998b8b897e6339c616d0a87b03624054720e3
|
7
|
+
data.tar.gz: d8f94272a6afa522a6ea5c48e3c0f9b91fec6bd1b2382e0ff2a9a94cfbdfb3c23d9f278a5bf143d4161a05cff385ad335d278a5634e8f3a23f4f63373c6da281
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
class ClientReadiness
|
7
|
+
attr_reader :is_initializing, :success
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@is_initializing = false
|
11
|
+
@success = false
|
12
|
+
@condition = Concurrent::ReadWriteLock.new
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@success = false
|
18
|
+
unless @is_initializing
|
19
|
+
@is_initializing = true
|
20
|
+
@condition.acquire_write_lock
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(success)
|
25
|
+
@success = success
|
26
|
+
if @is_initializing
|
27
|
+
@condition.release_write_lock
|
28
|
+
@is_initializing = false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait
|
33
|
+
if @is_initializing
|
34
|
+
@condition.acquire_read_lock
|
35
|
+
@condition.release_read_lock
|
36
|
+
end
|
37
|
+
@success
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/configuration/settings'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module Configuration
|
7
|
+
class DataFile
|
8
|
+
attr_reader :settings, :feature_flags, :has_any_targeted_delivery_rule
|
9
|
+
|
10
|
+
def initialize(environment)
|
11
|
+
@settings = Settings.new
|
12
|
+
@feature_flags = {}
|
13
|
+
@environment = environment
|
14
|
+
end
|
15
|
+
|
16
|
+
def init(configuration)
|
17
|
+
@settings.update(configuration['configuration'])
|
18
|
+
configuration['featureFlags'].each do |raw|
|
19
|
+
ff = FeatureFlag.new(raw)
|
20
|
+
@feature_flags[ff.feature_key] = ff
|
21
|
+
end
|
22
|
+
@has_any_targeted_delivery_rule = any_targeted_delivery_rule?
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_feature_flag(feature_key)
|
27
|
+
ff = @feature_flags[feature_key]
|
28
|
+
raise Exception::FeatureNotFound, feature_key if ff.nil?
|
29
|
+
raise Exception::FeatureEnvironmentDisabled.new(feature_key, @environment) unless ff.environment_enabled
|
30
|
+
|
31
|
+
ff
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def any_targeted_delivery_rule?
|
37
|
+
@feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -8,7 +8,7 @@ module Kameleoon
|
|
8
8
|
module Configuration
|
9
9
|
# Class for manage all feature flags with rules
|
10
10
|
class FeatureFlag
|
11
|
-
attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
|
11
|
+
attr_accessor :id, :feature_key, :variations, :default_variation_key, :environment_enabled, :rules
|
12
12
|
|
13
13
|
def self.create_from_array(array)
|
14
14
|
array&.map { |it| FeatureFlag.new(it) }
|
@@ -17,8 +17,9 @@ module Kameleoon
|
|
17
17
|
def initialize(hash)
|
18
18
|
@id = hash['id']
|
19
19
|
@feature_key = hash['featureKey']
|
20
|
-
@default_variation_key = hash['defaultVariationKey']
|
21
20
|
@variations = Variation.create_from_array(hash['variations'])
|
21
|
+
@default_variation_key = hash['defaultVariationKey']
|
22
|
+
@environment_enabled = hash['environmentEnabled']
|
22
23
|
@rules = Rule.create_from_array(hash['rules'])
|
23
24
|
end
|
24
25
|
|
@@ -8,8 +8,23 @@ module Kameleoon
|
|
8
8
|
module Configuration
|
9
9
|
# RuleType has a possible rule types
|
10
10
|
module RuleType
|
11
|
-
|
12
|
-
|
11
|
+
UNKNOWN = 0
|
12
|
+
EXPERIMENTATION = 1
|
13
|
+
TARGETED_DELIVERY = 2
|
14
|
+
|
15
|
+
EXPERIMENTATION_LITERAL = 'EXPERIMENTATION'
|
16
|
+
TARGETED_DELIVERY_LITERAL = 'TARGETED_DELIVERY'
|
17
|
+
|
18
|
+
def self.from_literal(literal)
|
19
|
+
case literal
|
20
|
+
when EXPERIMENTATION_LITERAL
|
21
|
+
EXPERIMENTATION
|
22
|
+
when TARGETED_DELIVERY_LITERAL
|
23
|
+
TARGETED_DELIVERY
|
24
|
+
else
|
25
|
+
UNKNOWN
|
26
|
+
end
|
27
|
+
end
|
13
28
|
end
|
14
29
|
|
15
30
|
# Rule is a class for new rules of feature flags
|
@@ -24,7 +39,7 @@ module Kameleoon
|
|
24
39
|
def initialize(hash)
|
25
40
|
@id = hash['id']
|
26
41
|
@order = hash['order']
|
27
|
-
@type = hash['type']
|
42
|
+
@type = RuleType.from_literal(hash['type'])
|
28
43
|
@exposition = hash['exposition']
|
29
44
|
@experiment_id = hash['experimentId']
|
30
45
|
@respool_time = hash['respoolTime']
|
@@ -7,13 +7,18 @@ module Kameleoon
|
|
7
7
|
# state of real time update for site code and etc
|
8
8
|
class Settings
|
9
9
|
attr_accessor :real_time_update
|
10
|
+
attr_reader :is_consent_required, :data_api_domain
|
10
11
|
|
11
12
|
def initialize
|
12
13
|
@real_time_update = false
|
14
|
+
@is_consent_required = false
|
15
|
+
@data_api_domain = nil
|
13
16
|
end
|
14
17
|
|
15
18
|
def update(configuration)
|
16
19
|
@real_time_update = configuration['realTimeUpdate'] || false
|
20
|
+
@is_consent_required = configuration['consentType'] == 'REQUIRED'
|
21
|
+
@data_api_domain = configuration['dataApiDomain']
|
17
22
|
end
|
18
23
|
end
|
19
24
|
end
|
@@ -15,7 +15,7 @@ module Kameleoon
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# Represents browser data for tracking calls
|
18
|
-
class Browser <
|
18
|
+
class Browser < DuplicationUnsafeData
|
19
19
|
attr_reader :type, :version
|
20
20
|
|
21
21
|
# @param [BrowserType] browser_type Browser type, can be: CHROME, INTERNET_EXPLORER, FIREFOX, SAFARI, OPERA, OTHER
|
@@ -6,7 +6,7 @@ require_relative 'data'
|
|
6
6
|
|
7
7
|
module Kameleoon
|
8
8
|
# Conversion class uses for tracking conversion
|
9
|
-
class Conversion <
|
9
|
+
class Conversion < DuplicationSafeData
|
10
10
|
attr_reader :goal_id, :revenue, :negative
|
11
11
|
|
12
12
|
# @param [Integer] goal_id Id of the goal associated to the conversion
|
@@ -6,7 +6,7 @@ require_relative 'data'
|
|
6
6
|
|
7
7
|
module Kameleoon
|
8
8
|
# Represents any custom data for targeting conditions
|
9
|
-
class CustomData <
|
9
|
+
class CustomData < DuplicationUnsafeData
|
10
10
|
attr_reader :id, :values
|
11
11
|
|
12
12
|
# @param [Integer] id Id of the custom data
|
@@ -23,23 +23,19 @@ module Kameleoon
|
|
23
23
|
id = hash['id']
|
24
24
|
raise Kameleoon::Exception::NotFound.new('id'), '"id" is mandatory' if id.nil?
|
25
25
|
|
26
|
-
@id = id
|
27
|
-
value = hash['value']
|
26
|
+
@id = id
|
28
27
|
values = hash['values']
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if values.nil?
|
34
|
-
@values = [value]
|
35
|
-
else
|
36
|
-
@values = values.is_a?(Array) ? values.dup : [values]
|
37
|
-
@values.append(value) unless value.nil?
|
38
|
-
end
|
28
|
+
raise Kameleoon::Exception::NotFound.new('values'), '"values" is mandatory' if values.nil?
|
29
|
+
|
30
|
+
@values = values.is_a?(Array) ? values.dup : [values]
|
39
31
|
else
|
40
|
-
@id = arg0
|
32
|
+
@id = arg0
|
41
33
|
@values = args
|
42
34
|
end
|
35
|
+
return if @id.is_a?(Integer)
|
36
|
+
|
37
|
+
warn "CustomData field 'id' must be of 'Integer' type"
|
38
|
+
@id = @id.is_a?(String) ? @id.to_i : -1
|
43
39
|
end
|
44
40
|
# rubocop:enable Metrics/MethodLength
|
45
41
|
|
data/lib/kameleoon/data/data.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'kameleoon/exceptions'
|
5
|
+
require 'kameleoon/utils'
|
5
6
|
|
6
7
|
module Kameleoon
|
7
8
|
NONCE_LENGTH = 16
|
@@ -12,12 +13,12 @@ module Kameleoon
|
|
12
13
|
CONVERSION = 'CONVERSION'
|
13
14
|
DEVICE = 'DEVICE'
|
14
15
|
PAGE_VIEW = 'PAGE_VIEW'
|
16
|
+
ASSIGNED_VARIATION = 'ASSIGNED_VARIATION'
|
15
17
|
end
|
16
18
|
|
17
19
|
# Represents base class for any Kameleoon data
|
18
20
|
class Data
|
19
|
-
attr_reader :instance
|
20
|
-
attr_accessor :sent
|
21
|
+
attr_reader :instance, :sent
|
21
22
|
|
22
23
|
def initialize(data_type)
|
23
24
|
@instance = data_type
|
@@ -28,10 +29,28 @@ module Kameleoon
|
|
28
29
|
raise KameleoonError.new('ToDo: implement this method.'), 'ToDo: implement this method.'
|
29
30
|
end
|
30
31
|
|
32
|
+
def mark_as_sent
|
33
|
+
@sent = true
|
34
|
+
@nonce = nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class DuplicationSafeData < Data
|
39
|
+
def initialize(data_type)
|
40
|
+
super(data_type)
|
41
|
+
@nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :nonce
|
47
|
+
end
|
48
|
+
|
49
|
+
class DuplicationUnsafeData < Data
|
31
50
|
private
|
32
51
|
|
33
52
|
def nonce
|
34
|
-
@nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if @nonce.nil?
|
53
|
+
@nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if !@sent && @nonce.nil?
|
35
54
|
@nonce
|
36
55
|
end
|
37
56
|
end
|
@@ -12,8 +12,9 @@ module Kameleoon
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Device uses for sending deviceType parameter for tracking calls
|
15
|
-
class Device <
|
15
|
+
class Device < DuplicationUnsafeData
|
16
16
|
attr_reader :device_type
|
17
|
+
|
17
18
|
def initialize(device_type)
|
18
19
|
super(DataType::DEVICE)
|
19
20
|
@device_type = device_type
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/data/data'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
module DataManager
|
8
|
+
##
|
9
|
+
# AssignedVariation represents a variation assigned to a visitor.
|
10
|
+
class AssignedVariation < DuplicationUnsafeData
|
11
|
+
EVENT_TYPE = 'experiment'
|
12
|
+
|
13
|
+
attr_reader :experiment_id, :variation_id, :rule_type, :assignment_time
|
14
|
+
|
15
|
+
def initialize(experiment_id, variation_id, rule_type, assignment_time: nil)
|
16
|
+
super(DataType::ASSIGNED_VARIATION)
|
17
|
+
@experiment_id = experiment_id
|
18
|
+
@variation_id = variation_id
|
19
|
+
@rule_type = rule_type
|
20
|
+
@assignment_time = assignment_time || Time.new.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def obtain_full_post_text_line
|
24
|
+
params = {
|
25
|
+
eventType: EVENT_TYPE,
|
26
|
+
id: @experiment_id,
|
27
|
+
variationId: @variation_id,
|
28
|
+
nonce: nonce
|
29
|
+
}
|
30
|
+
Kameleoon::Network::UriHelper.encode_query(params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?(respool_time)
|
34
|
+
respool_time.nil? || (@assignment_time >= respool_time)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module DataManager
|
5
|
+
##
|
6
|
+
# DataArrayStorage is a readonly accessor to a sequntial storage of specific visitor data.
|
7
|
+
# It is thread-safe
|
8
|
+
class DataArrayStorage
|
9
|
+
def initialize(mutex, array)
|
10
|
+
@mutex = mutex
|
11
|
+
@array = array
|
12
|
+
end
|
13
|
+
|
14
|
+
def enumerate(&blk)
|
15
|
+
return if @array.nil?
|
16
|
+
|
17
|
+
@mutex.with_read_lock do
|
18
|
+
@array.each { |v| blk.call(v) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def last
|
23
|
+
return nil if @array.nil?
|
24
|
+
|
25
|
+
value = nil
|
26
|
+
@mutex.with_read_lock do
|
27
|
+
value = @array.last
|
28
|
+
end
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
return 0 if @array.nil?
|
34
|
+
|
35
|
+
value = nil
|
36
|
+
@mutex.with_read_lock do
|
37
|
+
value = @array.size
|
38
|
+
end
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module DataManager
|
5
|
+
##
|
6
|
+
# DataMapStorage is a readonly accessor to a key-value based storage of specific visitor data.
|
7
|
+
# It is thread-safe
|
8
|
+
class DataMapStorage
|
9
|
+
def initialize(mutex, map)
|
10
|
+
@mutex = mutex
|
11
|
+
@map = map
|
12
|
+
end
|
13
|
+
|
14
|
+
def enumerate(&blk)
|
15
|
+
return if @map.nil?
|
16
|
+
|
17
|
+
@mutex.with_read_lock do
|
18
|
+
@map.each { |_k, v| blk.call(v) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(key)
|
23
|
+
return nil if @map.nil?
|
24
|
+
|
25
|
+
value = nil
|
26
|
+
@mutex.with_read_lock do
|
27
|
+
value = @map[key]
|
28
|
+
end
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
return 0 if @map.nil?
|
34
|
+
|
35
|
+
value = nil
|
36
|
+
@mutex.with_read_lock do
|
37
|
+
value = @map.size
|
38
|
+
end
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module DataManager
|
5
|
+
class PageViewVisit
|
6
|
+
attr_reader :page_view, :count
|
7
|
+
|
8
|
+
def initialize(page_view, count = 1)
|
9
|
+
@page_view = page_view
|
10
|
+
@count = count
|
11
|
+
end
|
12
|
+
|
13
|
+
def overwrite(page_view)
|
14
|
+
@page_view = page_view
|
15
|
+
@count += 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require 'kameleoon/data/browser'
|
5
|
+
require 'kameleoon/data/conversion'
|
6
|
+
require 'kameleoon/data/custom_data'
|
7
|
+
require 'kameleoon/data/device'
|
8
|
+
require 'kameleoon/data/page_view'
|
9
|
+
require 'kameleoon/data/user_agent'
|
10
|
+
require 'kameleoon/data/manager/assigned_variation'
|
11
|
+
require 'kameleoon/data/manager/page_view_visit'
|
12
|
+
require 'kameleoon/data/manager/data_map_storage'
|
13
|
+
require 'kameleoon/data/manager/data_array_storage'
|
14
|
+
|
15
|
+
module Kameleoon
|
16
|
+
module DataManager
|
17
|
+
##
|
18
|
+
# Visitor is a container of all data assigned to a visitor.
|
19
|
+
# It is thread-safe
|
20
|
+
class Visitor
|
21
|
+
attr_reader :last_activity_time, :user_agent, :device, :browser
|
22
|
+
attr_accessor :legal_consent
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@mutex = Concurrent::ReadWriteLock.new
|
26
|
+
@legal_consent = false
|
27
|
+
update_last_activity_time
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_last_activity_time
|
31
|
+
@last_activity_time = Time.new.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def enumerate_sendable_data(&blk)
|
35
|
+
blk.call(@device) unless @device.nil?
|
36
|
+
blk.call(@browser) unless @browser.nil?
|
37
|
+
@mutex.with_read_lock do
|
38
|
+
@custom_data_map&.each { |_, cd| blk.call(cd) }
|
39
|
+
@page_view_visits&.each { |_, pvv| blk.call(pvv.page_view) }
|
40
|
+
@conversions&.each { |c| blk.call(c) }
|
41
|
+
@variations&.each { |_, av| blk.call(av) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def count_sendable_data
|
46
|
+
count = 0
|
47
|
+
@mutex.with_read_lock do
|
48
|
+
count += 1 unless @device.nil?
|
49
|
+
count += 1 unless @browser.nil?
|
50
|
+
count += @custom_data_map.size unless @custom_data_map.nil?
|
51
|
+
count += @page_view_visits.size unless @page_view_visits.nil?
|
52
|
+
count += @conversions.size unless @conversions.nil?
|
53
|
+
count += @variations.size unless @variations.nil?
|
54
|
+
end
|
55
|
+
count
|
56
|
+
end
|
57
|
+
|
58
|
+
def custom_data
|
59
|
+
DataMapStorage.new(@mutex, @custom_data_map)
|
60
|
+
end
|
61
|
+
|
62
|
+
def page_view_visits
|
63
|
+
DataMapStorage.new(@mutex, @page_view_visits)
|
64
|
+
end
|
65
|
+
|
66
|
+
def conversions
|
67
|
+
DataArrayStorage.new(@mutex, @conversions)
|
68
|
+
end
|
69
|
+
|
70
|
+
def variations
|
71
|
+
DataMapStorage.new(@mutex, @variations)
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_data(log_func, *args)
|
75
|
+
@mutex.with_write_lock do
|
76
|
+
args.each do |data|
|
77
|
+
case data
|
78
|
+
when Kameleoon::UserAgent
|
79
|
+
add_user_agent(data)
|
80
|
+
when Kameleoon::Device
|
81
|
+
add_device(data)
|
82
|
+
when Kameleoon::Browser
|
83
|
+
add_browser(data)
|
84
|
+
when Kameleoon::CustomData
|
85
|
+
add_custom_data(data)
|
86
|
+
when Kameleoon::PageView
|
87
|
+
add_page_view(data)
|
88
|
+
when Kameleoon::Conversion
|
89
|
+
add_conversion(data)
|
90
|
+
else
|
91
|
+
log_func&.call("Data has unsupported type '#{data.class}'")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def assign_variation(variation)
|
98
|
+
@mutex.with_write_lock do
|
99
|
+
@variations = {} if @variations.nil?
|
100
|
+
@variations[variation.experiment_id] = variation
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def add_user_agent(user_agent)
|
107
|
+
@user_agent = user_agent.value
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_device(device)
|
111
|
+
@device = device
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_browser(browser)
|
115
|
+
@browser = browser
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_custom_data(custom_data)
|
119
|
+
@custom_data_map = {} if @custom_data_map.nil?
|
120
|
+
@custom_data_map[custom_data.id] = custom_data
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_page_view(page_view)
|
124
|
+
return if page_view.url.nil? || page_view.url.empty?
|
125
|
+
|
126
|
+
@page_view_visits = {} if @page_view_visits.nil?
|
127
|
+
visit = @page_view_visits[page_view.url]
|
128
|
+
if visit.nil?
|
129
|
+
visit = DataManager::PageViewVisit.new(page_view)
|
130
|
+
else
|
131
|
+
visit.overwrite(page_view)
|
132
|
+
end
|
133
|
+
@page_view_visits[page_view.url] = visit
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_conversion(conversion)
|
137
|
+
@conversions = [] if @conversions.nil?
|
138
|
+
@conversions.push(conversion)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require 'rufus/scheduler'
|
5
|
+
require 'kameleoon/data/manager/visitor'
|
6
|
+
|
7
|
+
module Kameleoon
|
8
|
+
module DataManager
|
9
|
+
##
|
10
|
+
# VisitorManager stores Visitor instances and manages visitor sessions.
|
11
|
+
# It is thread-safe except `stop` method
|
12
|
+
class VisitorManager
|
13
|
+
attr_reader :expiration_period
|
14
|
+
|
15
|
+
def initialize(expiration_period)
|
16
|
+
@expiration_period = expiration_period
|
17
|
+
@visitors = Concurrent::Map.new
|
18
|
+
start
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop
|
22
|
+
return if @purge_job.nil?
|
23
|
+
|
24
|
+
@purge_job.unschedule
|
25
|
+
@purge_job = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_visitor(visitor_code)
|
29
|
+
@visitors.compute_if_present(visitor_code) do |visitor|
|
30
|
+
visitor.update_last_activity_time
|
31
|
+
visitor
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_or_create_visitor(visitor_code)
|
36
|
+
@visitors.compute(visitor_code) do |former_v|
|
37
|
+
next DataManager::Visitor.new if former_v.nil?
|
38
|
+
|
39
|
+
former_v.update_last_activity_time
|
40
|
+
former_v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def enumerate(&blk)
|
45
|
+
@visitors.each_pair { |vc, v| blk.call(vc, v) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def size
|
49
|
+
@visitors.size
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def purge
|
55
|
+
expired_time = Time.new.to_i - @expiration_period
|
56
|
+
@visitors.each_pair do |vc, v|
|
57
|
+
next if v.last_activity_time >= expired_time
|
58
|
+
|
59
|
+
@visitors.compute_if_present(vc) { |visitor| v.last_activity_time < expired_time ? nil : visitor }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
stop unless @purge_job.nil?
|
65
|
+
@purge_job = Rufus::Scheduler.singleton.schedule_every @expiration_period do
|
66
|
+
purge
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -6,7 +6,7 @@ require_relative 'data'
|
|
6
6
|
|
7
7
|
module Kameleoon
|
8
8
|
# Represents page view data for tracking calls
|
9
|
-
class PageView <
|
9
|
+
class PageView < DuplicationSafeData
|
10
10
|
attr_reader :url, :title, :referrer
|
11
11
|
|
12
12
|
# @param [String] url Url of the page
|
@@ -15,6 +15,7 @@ module Kameleoon
|
|
15
15
|
def initialize(url, title, referrers = nil)
|
16
16
|
super(DataType::PAGE_VIEW)
|
17
17
|
@url = url || ''
|
18
|
+
print('Kameleoon SDK: Url for PageView is required parameter, the data will be ignored.') if url == ''
|
18
19
|
@title = title || ''
|
19
20
|
@referrers = referrers.instance_of?(Integer) ? [referrers] : referrers
|
20
21
|
end
|