kameleoon-client-ruby 3.1.1 → 3.3.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/configuration/custom_data_info.rb +43 -0
  3. data/lib/kameleoon/configuration/data_file.rb +31 -2
  4. data/lib/kameleoon/configuration/rule.rb +4 -1
  5. data/lib/kameleoon/configuration/variation_exposition.rb +1 -1
  6. data/lib/kameleoon/data/browser.rb +19 -0
  7. data/lib/kameleoon/data/cookie.rb +14 -0
  8. data/lib/kameleoon/data/custom_data.rb +2 -0
  9. data/lib/kameleoon/data/data.rb +2 -0
  10. data/lib/kameleoon/data/geolocation.rb +45 -0
  11. data/lib/kameleoon/data/kcs_heat.rb +12 -0
  12. data/lib/kameleoon/data/manager/assigned_variation.rb +2 -1
  13. data/lib/kameleoon/data/manager/page_view_visit.rb +16 -2
  14. data/lib/kameleoon/data/manager/visitor.rb +71 -14
  15. data/lib/kameleoon/data/manager/visitor_manager.rb +23 -1
  16. data/lib/kameleoon/data/operating_system.rb +72 -0
  17. data/lib/kameleoon/data/page_view.rb +2 -2
  18. data/lib/kameleoon/data/visitor_visits.rb +20 -0
  19. data/lib/kameleoon/kameleoon_client.rb +122 -94
  20. data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +55 -0
  21. data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +188 -0
  22. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +3 -1
  23. data/lib/kameleoon/network/access_token_source.rb +22 -5
  24. data/lib/kameleoon/network/network_manager.rb +10 -10
  25. data/lib/kameleoon/network/response.rb +1 -1
  26. data/lib/kameleoon/network/url_provider.rb +16 -12
  27. data/lib/kameleoon/targeting/condition.rb +24 -2
  28. data/lib/kameleoon/targeting/condition_factory.rb +39 -6
  29. data/lib/kameleoon/targeting/conditions/cookie_condition.rb +55 -0
  30. data/lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb +27 -0
  31. data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +33 -0
  32. data/lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb +37 -0
  33. data/lib/kameleoon/targeting/conditions/number_condition.rb +38 -0
  34. data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +27 -0
  35. data/lib/kameleoon/targeting/conditions/page_view_number_condition.rb +28 -0
  36. data/lib/kameleoon/targeting/conditions/previous_page_condition.rb +35 -0
  37. data/lib/kameleoon/targeting/conditions/segment_condition.rb +42 -0
  38. data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +59 -0
  39. data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +29 -0
  40. data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +28 -0
  41. data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +23 -0
  42. data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +34 -0
  43. data/lib/kameleoon/targeting/targeting_manager.rb +68 -0
  44. data/lib/kameleoon/types/remote_visitor_data_filter.rb +29 -0
  45. data/lib/kameleoon/types/variable.rb +17 -0
  46. data/lib/kameleoon/types/variation.rb +18 -0
  47. data/lib/kameleoon/utils.rb +1 -0
  48. data/lib/kameleoon/version.rb +1 -1
  49. metadata +28 -4
  50. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +0 -18
  51. data/lib/kameleoon/targeting/conditions/target_experiment.rb +0 -45
@@ -20,12 +20,12 @@ module Kameleoon
20
20
  @log_func = log_func
21
21
  end
22
22
 
23
- def get_token
23
+ def get_token(timeout = nil)
24
24
  now = Time.new.to_i
25
25
  token = @cached_token
26
- return fetch_token if token.nil? || token.expired?(now)
26
+ return call_fetch_token(timeout) if token.nil? || token.expired?(now)
27
27
 
28
- Thread.new { fetch_token } if !@fetching && token.obsolete?(now)
28
+ run_fetch_token if !@fetching && token.obsolete?(now)
29
29
  token.value
30
30
  end
31
31
 
@@ -35,9 +35,26 @@ module Kameleoon
35
35
 
36
36
  private
37
37
 
38
- def fetch_token
38
+ def call_fetch_token(timeout)
39
39
  @fetching = true
40
- response_content = @network_manager.fetch_access_jwtoken(@client_id, @client_secret)
40
+ fetch_token(timeout)
41
+ rescue StandardError => e
42
+ @fetching = false
43
+ @log_func&.call("Failed to call access token fetching: #{e}")
44
+ nil
45
+ end
46
+
47
+ def run_fetch_token
48
+ # setting `@fetching` here to reduce the number of requests until new thread started
49
+ @fetching = true
50
+ Thread.new { fetch_token }
51
+ rescue StandardError => e
52
+ @fetching = false
53
+ @log_func&.call("Failed to run access token fetching: #{e}")
54
+ end
55
+
56
+ def fetch_token(timeout = nil)
57
+ response_content = @network_manager.fetch_access_jwtoken(@client_id, @client_secret, timeout)
41
58
  unless response_content
42
59
  @log_func&.call('Failed to fetch access JWT')
43
60
  return nil
@@ -45,17 +45,17 @@ module Kameleoon
45
45
  make_call(request, true)
46
46
  end
47
47
 
48
- def get_remote_visitor_data(visitor_code, timeout = nil)
49
- url = @url_provider.make_visitor_data_get_url(visitor_code)
48
+ def get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout = nil)
49
+ url = @url_provider.make_visitor_data_get_url(visitor_code, filter, is_unique_identifier)
50
50
  timeout = ensure_timeout(timeout)
51
51
  request = Request.new(Method::GET, url, ContentType::JSON, timeout)
52
52
  make_call(request, true)
53
53
  end
54
54
 
55
- def send_tracking_data(visitor_code, lines, user_agent, timeout = nil)
55
+ def send_tracking_data(visitor_code, lines, user_agent, is_unique_identifier, timeout = nil)
56
56
  return if lines.nil? || lines.empty?
57
57
 
58
- url = @url_provider.make_tracking_url(visitor_code)
58
+ url = @url_provider.make_tracking_url(visitor_code, is_unique_identifier)
59
59
  timeout = ensure_timeout(timeout)
60
60
  data = (lines.map(&:obtain_full_post_text_line).join("\n") || '').encode('UTF-8')
61
61
  request = Request.new(Method::POST, url, ContentType::TEXT, timeout, user_agent: user_agent, data: data)
@@ -87,11 +87,13 @@ module Kameleoon
87
87
  delay(retry_delay) if attempt.positive? && retry_delay.positive?
88
88
  try_authorize(request) if try_access_token_auth
89
89
  response = @sync_net_provider.make_request(request)
90
- if !response.error.nil?
90
+ if response.success?
91
+ success = true
92
+ elsif !response.error.nil?
91
93
  log_failure(request, "Error occurred during request: #{response.error}")
92
- elsif response.code / 100 != 2
94
+ else
93
95
  log_failure(request, "Received unexpected status code '#{response.code}'")
94
- if ((response.code == 401) || (response.code == 403)) && request.access_token
96
+ if response.code == 401 && request.access_token
95
97
  @log_func&.call("Unexpected rejection of access token '#{request.access_token}'")
96
98
  @access_token_source.discard_token(request.access_token)
97
99
  if attempt == retry_limit
@@ -101,8 +103,6 @@ module Kameleoon
101
103
  attempt -= 1
102
104
  end
103
105
  end
104
- else
105
- success = true
106
106
  end
107
107
  attempt += 1
108
108
  end
@@ -126,7 +126,7 @@ module Kameleoon
126
126
  end
127
127
 
128
128
  def try_authorize(request)
129
- token = @access_token_source.get_token
129
+ token = @access_token_source.get_token(request.timeout)
130
130
  request.authorize(token)
131
131
  end
132
132
  end
@@ -15,7 +15,7 @@ module Kameleoon
15
15
  end
16
16
 
17
17
  def success?
18
- @error.nil? && (@code / 100 == 2)
18
+ @error.nil? && ((@code / 100 == 2) || (@code == 403))
19
19
  end
20
20
  end
21
21
  end
@@ -37,25 +37,29 @@ module Kameleoon
37
37
  @data_api_domain = domain if domain.is_a?(String)
38
38
  end
39
39
 
40
- def make_tracking_url(visitor_code)
40
+ def make_tracking_url(visitor_code, is_unique_identifier = false)
41
41
  params = {
42
42
  sdkName: SDK_NAME,
43
43
  sdkVersion: SDK_VERSION,
44
- siteCode: @site_code,
45
- visitorCode: visitor_code
44
+ siteCode: @site_code
46
45
  }
46
+ params[is_unique_identifier ? :mappingValue : :visitorCode] = visitor_code
47
47
  "https://#{@data_api_domain}#{TRACKING_PATH}?#{UriHelper.encode_query(params)}"
48
48
  end
49
49
 
50
- def make_visitor_data_get_url(visitor_code)
51
- params = {
52
- siteCode: @site_code,
53
- visitorCode: visitor_code,
54
- currentVisit: true,
55
- maxNumberPreviousVisits: 1,
56
- customData: true,
57
- version: 0
58
- }
50
+ def make_visitor_data_get_url(visitor_code, filter, is_unique_identifier = false)
51
+ params = { siteCode: @site_code }
52
+ params[is_unique_identifier ? :mappingValue : :visitorCode] = visitor_code
53
+ params[:maxNumberPreviousVisits] = filter.previous_visit_amount
54
+ params[:version] = 0
55
+ params[:kcs] = true if filter.kcs
56
+ params[:currentVisit] = true if filter.current_visit
57
+ params[:customData] = true if filter.custom_data
58
+ params[:conversion] = true if filter.conversions
59
+ params[:geolocation] = true if filter.geolocation
60
+ params[:experiment] = true if filter.experiments
61
+ params[:page] = true if filter.page_views
62
+ params[:staticData] = true if filter.device || filter.browser || filter.operating_system
59
63
  "https://#{@data_api_domain}#{VISITOR_DATA_PATH}?#{UriHelper.encode_query(params)}"
60
64
  end
61
65
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'kameleoon/exceptions'
3
4
 
4
5
  module Kameleoon
@@ -6,15 +7,27 @@ module Kameleoon
6
7
  module Targeting
7
8
  module ConditionType
8
9
  CUSTOM_DATUM = 'CUSTOM_DATUM'
9
- TARGET_EXPERIMENT = 'TARGET_EXPERIMENT'
10
- EXCLUSIVE_EXPERIMENT = 'EXCLUSIVE_EXPERIMENT'
10
+ TARGET_FEATURE_FLAG = 'TARGET_FEATURE_FLAG'
11
+ EXCLUSIVE_FEATURE_FLAG = 'EXCLUSIVE_FEATURE_FLAG'
11
12
  PAGE_URL = 'PAGE_URL'
13
+ PAGE_VIEWS = 'PAGE_VIEWS'
14
+ PREVIOUS_PAGE = 'PREVIOUS_PAGE'
12
15
  PAGE_TITLE = 'PAGE_TITLE'
13
16
  VISITOR_CODE = 'VISITOR_CODE'
14
17
  DEVICE_TYPE = 'DEVICE_TYPE'
15
18
  BROWSER = 'BROWSER'
16
19
  SDK_LANGUAGE = 'SDK_LANGUAGE'
17
20
  CONVERSIONS = 'CONVERSIONS'
21
+ COOKIE = 'COOKIE'
22
+ OPERATING_SYSTEM = 'OPERATING_SYSTEM'
23
+ GEOLOCATION = 'GEOLOCATION'
24
+ SEGMENT = 'SEGMENT'
25
+ FIRST_VISIT = 'FIRST_VISIT'
26
+ LAST_VISIT = 'LAST_VISIT'
27
+ VISITS = 'VISITS'
28
+ SAME_DAY_VISITS = 'SAME_DAY_VISITS'
29
+ NEW_VISITORS = 'NEW_VISITORS'
30
+ HEAT_SLICE = 'HEAT_SLICE'
18
31
  end
19
32
 
20
33
  module Operator
@@ -52,6 +65,15 @@ module Kameleoon
52
65
  def get_last_targeting_data(list_data, data_type)
53
66
  list_data.select { |data| data.instance == data_type }.last
54
67
  end
68
+
69
+ def is_regex_match(value, pattern)
70
+ begin
71
+ return pattern.match? value
72
+ rescue StandardError => e
73
+ log "Failed to match with regex (targeting): #{e}"
74
+ end
75
+ false
76
+ end
55
77
  end
56
78
  end
57
79
  end
@@ -1,13 +1,24 @@
1
1
  require_relative 'conditions/custom_datum'
2
- require_relative 'conditions/target_experiment'
3
- require_relative 'conditions/exclusive_experiment'
2
+ require_relative 'conditions/target_feature_flag_condition'
3
+ require_relative 'conditions/exclusive_feature_flag_condition'
4
4
  require_relative 'conditions/page_title_condition'
5
5
  require_relative 'conditions/page_url_condition'
6
+ require_relative 'conditions/page_view_number_condition'
7
+ require_relative 'conditions/previous_page_condition'
6
8
  require_relative 'conditions/visitor_code_condition'
7
9
  require_relative 'conditions/device_condition'
8
10
  require_relative 'conditions/conversion_condition'
9
11
  require_relative 'conditions/browser_condition'
10
12
  require_relative 'conditions/sdk_language_condition'
13
+ require_relative 'conditions/geolocation_condition'
14
+ require_relative 'conditions/kcs_heat_range_condition'
15
+ require_relative 'conditions/operating_system_condition'
16
+ require_relative 'conditions/cookie_condition'
17
+ require_relative 'conditions/segment_condition'
18
+ require_relative 'conditions/visit_number_total_condition'
19
+ require_relative 'conditions/visit_number_today_condition'
20
+ require_relative 'conditions/visitor_new_return_condition'
21
+ require_relative 'conditions/time_elapsed_since_visit_condition'
11
22
  require_relative 'conditions/unknown_condition'
12
23
 
13
24
  module Kameleoon
@@ -19,12 +30,16 @@ module Kameleoon
19
30
  case condition_json['targetingType']
20
31
  when ConditionType::CUSTOM_DATUM
21
32
  CustomDatum.new(condition_json)
22
- when ConditionType::TARGET_EXPERIMENT
23
- TargetExperiment.new(condition_json)
24
- when ConditionType::EXCLUSIVE_EXPERIMENT
25
- ExclusiveExperiment.new(condition_json)
33
+ when ConditionType::TARGET_FEATURE_FLAG
34
+ TargetFeatureFlagCondition.new(condition_json)
35
+ when ConditionType::EXCLUSIVE_FEATURE_FLAG
36
+ ExclusiveFeatureFlagCondition.new(condition_json)
26
37
  when ConditionType::PAGE_URL
27
38
  PageUrlCondition.new(condition_json)
39
+ when ConditionType::PAGE_VIEWS
40
+ PageViewNumberCondition.new(condition_json)
41
+ when ConditionType::PREVIOUS_PAGE
42
+ PreviousPageCondition.new(condition_json)
28
43
  when ConditionType::PAGE_TITLE
29
44
  PageTitleCondition.new(condition_json)
30
45
  when ConditionType::VISITOR_CODE
@@ -37,6 +52,24 @@ module Kameleoon
37
52
  BrowserCondition.new(condition_json)
38
53
  when ConditionType::SDK_LANGUAGE
39
54
  SdkLanguageCondition.new(condition_json)
55
+ when ConditionType::GEOLOCATION
56
+ GeolocationCondition.new(condition_json)
57
+ when ConditionType::OPERATING_SYSTEM
58
+ OperatingSystemCondition.new(condition_json)
59
+ when ConditionType::COOKIE
60
+ CookieCondition.new(condition_json)
61
+ when ConditionType::SEGMENT
62
+ SegmentCondition.new(condition_json)
63
+ when ConditionType::VISITS
64
+ VisitNumberTotalCondition.new(condition_json)
65
+ when ConditionType::SAME_DAY_VISITS
66
+ VisitNumberTodayCondition.new(condition_json)
67
+ when ConditionType::NEW_VISITORS
68
+ VisitorNewReturnCondition.new(condition_json)
69
+ when ConditionType::FIRST_VISIT, ConditionType::LAST_VISIT
70
+ TimeElapsedSinceVisitCondition.new(condition_json)
71
+ when ConditionType::HEAT_SLICE
72
+ KcsHeatRangeCondition.new(condition_json)
40
73
  else
41
74
  UnknownCondition.new(condition_json)
42
75
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/cookie'
4
+ require 'kameleoon/version'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class CookieCondition < Condition
11
+ def initialize(json_condition)
12
+ super(json_condition)
13
+ @condition_name = json_condition['name']
14
+ @name_match_type = json_condition['nameMatchType']
15
+ @condition_value = json_condition['value']
16
+ @value_match_type = json_condition['valueMatchType']
17
+ end
18
+
19
+ def check(cookie)
20
+ cookie.is_a?(Kameleoon::Cookie) && check_values(select_values(cookie))
21
+ end
22
+
23
+ private
24
+
25
+ def select_values(cookie)
26
+ case @name_match_type
27
+ when Kameleoon::Targeting::Operator::EXACT
28
+ value = cookie.cookies[@condition_name]
29
+ return value != nil ? [value] : []
30
+ when Kameleoon::Targeting::Operator::CONTAINS
31
+ return cookie.cookies.select{|key| key.include? @condition_name}.values()
32
+ when Kameleoon::Targeting::Operator::REGULAR_EXPRESSION
33
+ return cookie.cookies.select{|key| is_regex_match(@condition_name, key)}.values()
34
+ else
35
+ log "Unexpected comparing operation for Cookie condition (name): #{@name_match_type}"
36
+ end
37
+ []
38
+ end
39
+
40
+ def check_values(values)
41
+ case @value_match_type
42
+ when Kameleoon::Targeting::Operator::EXACT
43
+ return values.any? {|value| value == @condition_value}
44
+ when Kameleoon::Targeting::Operator::CONTAINS
45
+ return values.any? {|value| value.include? @condition_value}
46
+ when Kameleoon::Targeting::Operator::REGULAR_EXPRESSION
47
+ return values.any? {|value| is_regex_match(@condition_value, value)}
48
+ else
49
+ log "Unexpected comparing operation for Cookie condition (name): #{@name_match_type}"
50
+ return false
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/targeting/condition'
4
+
5
+ module Kameleoon
6
+ # @api private
7
+ module Targeting
8
+ # ExclusiveExperiment represents an instance of Exclusive Experiment condition in user account
9
+ class ExclusiveFeatureFlagCondition < Condition
10
+ def check(data)
11
+ return false unless data.is_a?(ExclusiveFeatureFlagInfo)
12
+
13
+ size = data.variations_storage&.size || 0
14
+ size.zero? || (size == 1 && !data.variations_storage.get(data.current_experiment_id).nil?)
15
+ end
16
+ end
17
+
18
+ class ExclusiveFeatureFlagInfo
19
+ attr_reader :current_experiment_id, :variations_storage
20
+
21
+ def initialize(current_experiment_id, variations_storage)
22
+ @current_experiment_id = current_experiment_id
23
+ @variations_storage = variations_storage
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/geolocation'
4
+ require 'kameleoon/version'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class GeolocationCondition < Condition
11
+ def initialize(json_condition)
12
+ super(json_condition)
13
+ @country = json_condition['country']
14
+ @region = json_condition['region']
15
+ @city = json_condition['city']
16
+ end
17
+
18
+ def check(geolocation)
19
+ geolocation.is_a?(Kameleoon::Geolocation) && check_targeting(geolocation)
20
+ end
21
+
22
+ private
23
+
24
+ def check_targeting(geolocation)
25
+ return false if !@country.is_a?(String) || !@country.casecmp?(geolocation.country)
26
+ return false if @region.is_a?(String) && !@region.casecmp?(geolocation.region)
27
+ return false if @city.is_a?(String) && !@city.casecmp?(geolocation.city)
28
+
29
+ true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/device'
4
+
5
+ module Kameleoon
6
+ # @api private
7
+ module Targeting
8
+ # KcsHeatRangeCondition is a condition for checking KCS heat
9
+ class KcsHeatRangeCondition < Condition
10
+ def initialize(json_condition)
11
+ super(json_condition)
12
+ @goal_id = json_condition['goalId'] || -1
13
+ @key_moment_id = json_condition['keyMomentId'] || -1
14
+ @lower_bound = json_condition['lowerBound'] || Float::MAX
15
+ @upper_bound = json_condition['upperBound'] || Float::MIN
16
+ end
17
+
18
+ def check(kcs_heat)
19
+ kcs_heat.is_a?(Kameleoon::KcsHeat) && check_targeting(kcs_heat)
20
+ end
21
+
22
+ private
23
+
24
+ def check_targeting(kcs_heat)
25
+ return false unless kcs_heat.values.is_a?(Hash)
26
+
27
+ goal_scores = kcs_heat.values[@key_moment_id]
28
+ return false unless goal_scores.is_a?(Hash)
29
+
30
+ score = goal_scores[@goal_id]
31
+ return false unless score.is_a?(Float) || score.is_a?(Integer)
32
+
33
+ (score >= @lower_bound) && (score <= @upper_bound)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/cookie'
4
+ require 'kameleoon/version'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class NumberCondition < Condition
11
+ def initialize(json_condition, value)
12
+ super(json_condition)
13
+ @match_type = json_condition['matchType']
14
+ @condition_value = value
15
+ end
16
+
17
+ def check(value)
18
+ check_targeting(value)
19
+ end
20
+
21
+ protected
22
+
23
+ def check_targeting(value)
24
+ case @match_type
25
+ when Kameleoon::Targeting::Operator::EQUAL
26
+ return value == @condition_value
27
+ when Kameleoon::Targeting::Operator::GREATER
28
+ return @condition_value != nil && value > @condition_value
29
+ when Kameleoon::Targeting::Operator::LOWER
30
+ return @condition_value != nil && value < @condition_value
31
+ else
32
+ log "Unexpected comparing operation for #{type} condition: #{@match_type}"
33
+ end
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/operating_system'
4
+ require 'kameleoon/version'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class OperatingSystemCondition < Condition
11
+ def initialize(json_condition)
12
+ super(json_condition)
13
+ @os_type = Kameleoon::OperatingSystemType.from_name(json_condition['os'])
14
+ end
15
+
16
+ def check(operating_system)
17
+ operating_system.is_a?(Kameleoon::OperatingSystem) && check_targeting(operating_system)
18
+ end
19
+
20
+ private
21
+
22
+ def check_targeting(operating_system)
23
+ @os_type != nil && operating_system.os_type == @os_type
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/targeting/conditions/number_condition'
4
+ require 'kameleoon/data/manager/page_view_visit'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class PageViewNumberCondition < NumberCondition
11
+ def initialize(json_condition)
12
+ page_count = json_condition['pageCount']
13
+ super(json_condition, page_count)
14
+ end
15
+
16
+ def check(page_view_visit_storage)
17
+ return false unless page_view_visit_storage.is_a?(Kameleoon::DataManager::DataMapStorage)
18
+
19
+ count = 0
20
+ page_view_visit_storage.enumerate do |visit|
21
+ return false unless visit.is_a?(Kameleoon::DataManager::PageViewVisit)
22
+ count += visit.count
23
+ end
24
+ check_targeting(count)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/targeting/conditions/string_value_condition'
4
+ require 'kameleoon/data/manager/page_view_visit'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class PreviousPageCondition < StringValueCondition
11
+ def initialize(json_condition)
12
+ super(json_condition, json_condition['url'])
13
+ end
14
+
15
+ def check(page_view_visit_storage)
16
+ is_targeted = false
17
+ if page_view_visit_storage.is_a?(Kameleoon::DataManager::DataMapStorage)
18
+ most_recent_visit = nil
19
+ second_most_recent_visit = nil
20
+ page_view_visit_storage.enumerate do |visit|
21
+ return false unless visit.is_a?(Kameleoon::DataManager::PageViewVisit)
22
+ if most_recent_visit == nil || visit.last_timestamp > most_recent_visit.last_timestamp
23
+ second_most_recent_visit = most_recent_visit
24
+ most_recent_visit = visit
25
+ elsif second_most_recent_visit == nil || visit.last_timestamp > second_most_recent_visit.last_timestamp
26
+ second_most_recent_visit = visit
27
+ end
28
+ end
29
+ is_targeted = second_most_recent_visit != nil && check_targeting(second_most_recent_visit.page_view.url)
30
+ end
31
+ is_targeted
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/geolocation'
4
+ require 'kameleoon/version'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+
10
+ class SegmentCondition < Condition
11
+ def initialize(json_condition)
12
+ super(json_condition)
13
+ @segment_id = json_condition["segmentId"]
14
+ end
15
+
16
+ def check(data)
17
+ data.is_a?(SegmentInfo) && check_targeting(data)
18
+ end
19
+
20
+ private
21
+
22
+ def check_targeting(segment_info)
23
+ rule = segment_info.data_file.rule_by_segment_id[@segment_id]
24
+ return false unless rule.is_a?(Kameleoon::Configuration::Rule)
25
+ rule.targeting_segment.check_tree(->(type) {segment_info.condition_data(type)})
26
+ end
27
+ end
28
+
29
+ class SegmentInfo
30
+ attr_reader :data_file
31
+
32
+ def initialize(data_file, condition_data)
33
+ @data_file = data_file
34
+ @condition_data = condition_data
35
+ end
36
+
37
+ def condition_data(type)
38
+ @condition_data.call(type)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/targeting/condition'
4
+ require 'kameleoon/exceptions'
5
+
6
+ module Kameleoon
7
+ # @api private
8
+ module Targeting
9
+ # TargetExperiment represents an instance of Experiment condition in user account
10
+ class TargetFeatureFlagCondition < Condition
11
+ include Kameleoon::Exception
12
+
13
+ def initialize(json_condition)
14
+ super(json_condition)
15
+
16
+ @feature_flag_id = json_condition['featureFlagId']
17
+ @condition_variation_key = json_condition['variationKey']
18
+ @condition_rule_id = json_condition['ruleId']
19
+ end
20
+
21
+ def check(data)
22
+ return false unless data.is_a?(Kameleoon::Targeting::TargetFeatureFlagInfo) && data.variations_storage.size != 0
23
+
24
+ get_rules(data).any? { |rule| check_rule(data, rule) }
25
+ end
26
+
27
+ private def check_rule(data, rule)
28
+ return false if !rule.is_a?(Kameleoon::Configuration::Rule) || rule.experiment_id.nil?
29
+
30
+ return false if !@condition_rule_id.nil? && (@condition_rule_id != rule.id)
31
+
32
+ variation = data.variations_storage.get(rule.experiment_id)
33
+ return false if variation.nil?
34
+
35
+ return true if @condition_variation_key.nil?
36
+
37
+ variation = data.data_file.variation_by_id[variation.variation_id]
38
+
39
+ return false unless variation.is_a?(Kameleoon::Configuration::VariationByExposition)
40
+
41
+ variation.variation_key == @condition_variation_key
42
+ end
43
+
44
+ def get_rules(data)
45
+ feature_flag = data.data_file.feature_flag_by_id[@feature_flag_id]
46
+ feature_flag&.rules || []
47
+ end
48
+ end
49
+
50
+ class TargetFeatureFlagInfo
51
+ attr_reader :data_file, :variations_storage
52
+
53
+ def initialize(data_file, variations_storage)
54
+ @data_file = data_file
55
+ @variations_storage = variations_storage
56
+ end
57
+ end
58
+ end
59
+ end