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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/client.rb +541 -404
  3. data/lib/kameleoon/configuration/experiment.rb +42 -0
  4. data/lib/kameleoon/configuration/feature_flag.rb +30 -0
  5. data/lib/kameleoon/configuration/rule.rb +57 -0
  6. data/lib/kameleoon/configuration/settings.rb +20 -0
  7. data/lib/kameleoon/configuration/variable.rb +23 -0
  8. data/lib/kameleoon/configuration/variation.rb +31 -0
  9. data/lib/kameleoon/configuration/variation_exposition.rb +23 -0
  10. data/lib/kameleoon/cookie.rb +13 -6
  11. data/lib/kameleoon/data.rb +60 -40
  12. data/lib/kameleoon/exceptions.rb +46 -23
  13. data/lib/kameleoon/factory.rb +21 -18
  14. data/lib/kameleoon/hybrid/manager.rb +60 -0
  15. data/lib/kameleoon/real_time/real_time_configuration_service.rb +98 -0
  16. data/lib/kameleoon/real_time/real_time_event.rb +22 -0
  17. data/lib/kameleoon/real_time/sse_client.rb +111 -0
  18. data/lib/kameleoon/real_time/sse_message.rb +23 -0
  19. data/lib/kameleoon/real_time/sse_request.rb +59 -0
  20. data/lib/kameleoon/request.rb +14 -13
  21. data/lib/kameleoon/storage/cache.rb +84 -0
  22. data/lib/kameleoon/storage/cache_factory.rb +23 -0
  23. data/lib/kameleoon/storage/variation_storage.rb +42 -0
  24. data/lib/kameleoon/storage/visitor_variation.rb +20 -0
  25. data/lib/kameleoon/targeting/condition.rb +17 -5
  26. data/lib/kameleoon/targeting/condition_factory.rb +9 -2
  27. data/lib/kameleoon/targeting/conditions/custom_datum.rb +67 -48
  28. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +29 -0
  29. data/lib/kameleoon/targeting/conditions/target_experiment.rb +44 -0
  30. data/lib/kameleoon/targeting/models.rb +36 -36
  31. data/lib/kameleoon/utils.rb +4 -1
  32. data/lib/kameleoon/version.rb +4 -2
  33. metadata +35 -3
  34. data/lib/kameleoon/query_graphql.rb +0 -76
@@ -1,35 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'json'
1
5
  require 'kameleoon/targeting/condition'
2
6
  require 'kameleoon/exceptions'
3
7
 
4
8
  module Kameleoon
5
- #@api private
6
9
  module Targeting
10
+ # CustomDatum represents an instance of Custom Data condition from back office
7
11
  class CustomDatum < Condition
8
12
  include Kameleoon::Exception
9
13
 
10
- attr_accessor :index, :operator, :value
11
14
  def initialize(json_condition)
12
- if json_condition['customDataIndex'].nil?
13
- raise Exception::NotFoundError.new('customDataIndex')
14
- end
15
+ super(json_condition)
15
16
  @index = json_condition['customDataIndex']
16
17
 
17
18
  if json_condition['valueMatchType'].nil?
18
- raise Exception::NotFoundError.new('valueMatchType')
19
+ raise Exception::NotFound.new('valueMatchType'), 'valueMatchType missed'
19
20
  end
21
+
20
22
  @operator = json_condition['valueMatchType']
21
23
 
22
- # if json_condition['value'].nil?
23
- # raise Exception::NotFoundError.new('value')
24
- # end
25
24
  @value = json_condition['value']
26
25
 
27
26
  @type = ConditionType::CUSTOM_DATUM
28
27
 
29
28
  if json_condition['include'].nil? && json_condition['isInclude'].nil?
30
- raise Exception::NotFoundError.new('include / isInclude missed')
29
+ raise Exception::NotFound.new('include / isInclude missed'), 'include / isInclude missed'
31
30
  end
31
+
32
32
  @include = json_condition['include'] || json_condition['isInclude']
33
+
34
+ @op = {
35
+ Operator::MATCH.to_s => method(:op_match),
36
+ Operator::CONTAINS.to_s => method(:op_contains),
37
+ Operator::EXACT.to_s => method(:op_exact),
38
+ Operator::EQUAL.to_s => method(:op_equal),
39
+ Operator::GREATER.to_s => method(:op_greater),
40
+ Operator::LOWER.to_s => method(:op_lower),
41
+ Operator::IS_TRUE.to_s => method(:op_is_true),
42
+ Operator::IS_FALSE.to_s => method(:op_is_false),
43
+ Operator::AMONG_VALUES.to_s => method(:op_among_values),
44
+ }[@operator]
33
45
  end
34
46
 
35
47
  def check(datas)
@@ -37,46 +49,53 @@ module Kameleoon
37
49
  custom_data = datas.select { |data| data.instance == DataType::CUSTOM && data.id == @index }.last
38
50
  if custom_data.nil?
39
51
  is_targeted = (@operator == Operator::UNDEFINED.to_s)
40
- else
41
- case @operator
42
- when Operator::MATCH.to_s
43
- if Regexp.new(@value.to_s).match(custom_data.value.to_s)
44
- is_targeted = true
45
- end
46
- when Operator::CONTAINS.to_s
47
- if custom_data.value.to_s.include? @value
48
- is_targeted = true
49
- end
50
- when Operator::EXACT.to_s
51
- if custom_data.value.to_s == @value.to_s
52
- is_targeted = true
53
- end
54
- when Operator::EQUAL.to_s
55
- if custom_data.value.to_f == @value.to_f
56
- is_targeted = true
57
- end
58
- when Operator::GREATER.to_s
59
- if custom_data.value.to_f > @value.to_f
60
- is_targeted = true
61
- end
62
- when Operator::LOWER.to_s
63
- if custom_data.value.to_f < @value.to_f
64
- is_targeted = true
65
- end
66
- when Operator::IS_TRUE.to_s
67
- if custom_data.value == 'true'
68
- is_targeted = true
69
- end
70
- when Operator::IS_FALSE.to_s
71
- if custom_data.value == 'false'
72
- is_targeted = true
73
- end
74
- else
75
- raise KameleoonError.new("Undefined operator " + @operator.to_s)
76
- end
52
+ elsif @operator != Operator::UNDEFINED.to_s
53
+ raise KameleoonError.new("Undefined operator #{@operator}"), "Undefined operator #{@operator}" if @op.nil?
54
+
55
+ is_targeted = @op.call(custom_data.values)
77
56
  end
78
57
  is_targeted
79
58
  end
59
+
60
+ private
61
+
62
+ def op_match(values)
63
+ re = Regexp.new(@value.to_s)
64
+ values.any? { |v| re.match(v) }
65
+ end
66
+
67
+ def op_contains(values)
68
+ values.any? { |v| v.to_s.include? @value.to_s }
69
+ end
70
+
71
+ def op_exact(values)
72
+ values.include? @value.to_s
73
+ end
74
+
75
+ def op_equal(values)
76
+ values.any? { |v| v.to_f == @value.to_f }
77
+ end
78
+
79
+ def op_greater(values)
80
+ values.any? { |v| v.to_f > @value.to_f }
81
+ end
82
+
83
+ def op_lower(values)
84
+ values.any? { |v| v.to_f < @value.to_f }
85
+ end
86
+
87
+ def op_is_true(values)
88
+ values.any? { |v| v.to_s == 'true' }
89
+ end
90
+
91
+ def op_is_false(values)
92
+ values.any? { |v| v.to_s == 'false' }
93
+ end
94
+
95
+ def op_among_values(values)
96
+ all_matches = JSON.parse(@value.to_s).map(&:to_s).to_set
97
+ values.any? { |v| all_matches.include?(v) }
98
+ end
80
99
  end
81
100
  end
82
- end
101
+ end
@@ -0,0 +1,29 @@
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
+ # ExclusiveExperiment represents an instance of Exclusive Experiment condition in user account
10
+ class ExclusiveExperiment < Condition
11
+ include Kameleoon::Exception
12
+
13
+ def initialize(json_condition)
14
+ if json_condition['targetingType'].nil?
15
+ raise Exception::NotFound.new('targetingType'), 'targetingType missed'
16
+ end
17
+
18
+ @type = json_condition['targetingType']
19
+ @include = true
20
+ end
21
+
22
+ def check(data)
23
+ experiment_id = data.experiment_id
24
+ storage = data.storage
25
+ storage.nil? || storage.empty? || (storage.length == 1 && !storage[experiment_id].nil?)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
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 TargetExperiment < Condition
11
+ include Kameleoon::Exception
12
+
13
+ def initialize(json_condition)
14
+ super(json_condition)
15
+
16
+ if json_condition['experiment'].nil?
17
+ raise Exception::NotFound.new('experiment'), 'experiment missed'
18
+ end
19
+
20
+ @experiment = json_condition['experiment']
21
+
22
+ if json_condition['variationMatchType'].nil?
23
+ raise Exception::NotFound.new('variationMatchType'), 'variationMatchType missed'
24
+ end
25
+
26
+ @operator = json_condition['variationMatchType']
27
+ @variation = json_condition['variation']
28
+ end
29
+
30
+ def check(variation_storage)
31
+ is_targeted = false
32
+ variation_storage_exist = !variation_storage.nil? && !variation_storage.empty?
33
+ saved_variation = variation_storage[@experiment] unless variation_storage.nil?
34
+ case @operator
35
+ when Operator::EXACT.to_s
36
+ is_targeted = variation_storage_exist && saved_variation == @variation
37
+ when Operator::ANY.to_s
38
+ is_targeted = variation_storage_exist
39
+ end
40
+ is_targeted
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,15 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'kameleoon/targeting/tree_builder'
2
4
  require 'kameleoon/exceptions'
3
5
 
4
6
  module Kameleoon
5
- #@api private
7
+ # @api private
6
8
  module Targeting
7
9
  class Segment
8
10
  include TreeBuilder
9
11
  attr_accessor :id, :tree
10
12
  def to_s
11
- print("\nSegment id: " + @id.to_s)
12
- print("\n")
13
+ print("\nSegment id: #{@id}\n")
13
14
  @tree.to_s
14
15
  end
15
16
 
@@ -18,16 +19,16 @@ module Kameleoon
18
19
  if args.length == 1
19
20
  hash = args.first
20
21
  if hash.nil?
21
- raise Kameleoon::Exception::NotFound.new("arguments for segment")
22
+ raise Kameleoon::Exception::NotFound.new('arguments for segment'), 'arguments for segment'
22
23
  end
23
- if hash["id"].nil?
24
- raise Kameleoon::Exception::NotFound.new("id")
24
+ if hash['id'].nil?
25
+ raise Kameleoon::Exception::NotFound.new('id'), 'id'
25
26
  end
26
- @id = hash["id"].to_i
27
- if hash["conditionsData"].nil?
28
- raise Kameleoon::Exception::NotFound.new(hash["conditionsData"])
27
+ @id = hash['id'].to_i
28
+ if hash['conditionsData'].nil?
29
+ raise Kameleoon::Exception::NotFound.new(hash['conditionsData']), 'hash[\'conditionsData\']'
29
30
  end
30
- @tree = create_tree(hash["conditionsData"])
31
+ @tree = create_tree(hash['conditionsData'])
31
32
  elsif args.length == 2
32
33
  @id = args[0]
33
34
  @tree = args[1]
@@ -49,17 +50,14 @@ module Kameleoon
49
50
  attr_accessor :or_operator, :left_child, :right_child, :condition
50
51
 
51
52
  def to_s
52
- print("or_operator: " + @or_operator.to_s)
53
- print("\n")
54
- print("condition: " + @condition.to_s)
53
+ print("or_operator: #{@or_operator}\n")
54
+ print("condition: #{@condition}")
55
55
  unless @left_child.nil?
56
- print("\n")
57
- print("Left child:\n ")
56
+ print('\nLeft child:\n ')
58
57
  @left_child.to_s
59
58
  end
60
59
  unless @right_child.nil?
61
- print("\n")
62
- print("right child:\n ")
60
+ print('\nright child:\n ')
63
61
  @right_child.to_s
64
62
  end
65
63
  end
@@ -125,37 +123,39 @@ module Kameleoon
125
123
  if condition.nil?
126
124
  is_targeted = true
127
125
  else
128
- is_targeted = condition.check(datas)
126
+ is_targeted = condition.check(datas.call(condition.type))
129
127
  unless condition.include
130
- if is_targeted.nil?
131
- is_targeted = true
132
- else
133
- is_targeted = !is_targeted
134
- end
128
+ return true if is_targeted.nil?
129
+
130
+ is_targeted = !is_targeted
135
131
  end
136
132
  end
137
- Marshal.load(Marshal.dump(is_targeted)) #Deep copy
133
+ Marshal.load(Marshal.dump(is_targeted)) # Deep copy
138
134
  end
139
135
  end
140
136
 
141
137
  module DataType
142
- CUSTOM = "CUSTOM"
138
+ CUSTOM = 'CUSTOM'
143
139
  end
144
140
 
145
141
  module ConditionType
146
- CUSTOM_DATUM = "CUSTOM_DATUM"
142
+ CUSTOM_DATUM = 'CUSTOM_DATUM'
143
+ TARGET_EXPERIMENT = 'TARGET_EXPERIMENT'
144
+ EXCLUSIVE_EXPERIMENT = 'EXCLUSIVE_EXPERIMENT'
147
145
  end
148
146
 
149
147
  module Operator
150
- UNDEFINED = "UNDEFINED"
151
- CONTAINS = "CONTAINS"
152
- EXACT = "EXACT"
153
- MATCH = "REGULAR_EXPRESSION"
154
- LOWER = "LOWER"
155
- EQUAL = "EQUAL"
156
- GREATER = "GREATER"
157
- IS_TRUE = "TRUE"
158
- IS_FALSE = "FALSE"
148
+ UNDEFINED = 'UNDEFINED'
149
+ CONTAINS = 'CONTAINS'
150
+ EXACT = 'EXACT'
151
+ MATCH = 'REGULAR_EXPRESSION'
152
+ LOWER = 'LOWER'
153
+ EQUAL = 'EQUAL'
154
+ GREATER = 'GREATER'
155
+ IS_TRUE = 'TRUE'
156
+ IS_FALSE = 'FALSE'
157
+ AMONG_VALUES = 'AMONG_VALUES'
158
+ ANY = 'ANY'
159
159
  end
160
160
  end
161
- end
161
+ end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kameleoon
4
+ # Utils is a helper module for project
2
5
  module Utils
3
6
  ALPHA_NUMERIC_CHARS = 'abcdef0123456789'
4
7
 
@@ -10,4 +13,4 @@ module Kameleoon
10
13
  (1..length).map { ALPHA_NUMERIC_CHARS[rand(ALPHA_NUMERIC_CHARS.length)] }.join
11
14
  end
12
15
  end
13
- end
16
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kameleoon
2
- VERSION = '1.1.2'
3
- end
4
+ VERSION = '2.1.0'
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kameleoon-client-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kameleoon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-08 00:00:00.000000000 Z
11
+ date: 2023-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rest-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
55
69
  description: Kameleoon Client Ruby Software Development Kit
56
70
  email:
57
71
  - sdk@kameleoon.com
@@ -62,15 +76,33 @@ files:
62
76
  - README.md
63
77
  - lib/kameleoon.rb
64
78
  - lib/kameleoon/client.rb
79
+ - lib/kameleoon/configuration/experiment.rb
80
+ - lib/kameleoon/configuration/feature_flag.rb
81
+ - lib/kameleoon/configuration/rule.rb
82
+ - lib/kameleoon/configuration/settings.rb
83
+ - lib/kameleoon/configuration/variable.rb
84
+ - lib/kameleoon/configuration/variation.rb
85
+ - lib/kameleoon/configuration/variation_exposition.rb
65
86
  - lib/kameleoon/cookie.rb
66
87
  - lib/kameleoon/data.rb
67
88
  - lib/kameleoon/exceptions.rb
68
89
  - lib/kameleoon/factory.rb
69
- - lib/kameleoon/query_graphql.rb
90
+ - lib/kameleoon/hybrid/manager.rb
91
+ - lib/kameleoon/real_time/real_time_configuration_service.rb
92
+ - lib/kameleoon/real_time/real_time_event.rb
93
+ - lib/kameleoon/real_time/sse_client.rb
94
+ - lib/kameleoon/real_time/sse_message.rb
95
+ - lib/kameleoon/real_time/sse_request.rb
70
96
  - lib/kameleoon/request.rb
97
+ - lib/kameleoon/storage/cache.rb
98
+ - lib/kameleoon/storage/cache_factory.rb
99
+ - lib/kameleoon/storage/variation_storage.rb
100
+ - lib/kameleoon/storage/visitor_variation.rb
71
101
  - lib/kameleoon/targeting/condition.rb
72
102
  - lib/kameleoon/targeting/condition_factory.rb
73
103
  - lib/kameleoon/targeting/conditions/custom_datum.rb
104
+ - lib/kameleoon/targeting/conditions/exclusive_experiment.rb
105
+ - lib/kameleoon/targeting/conditions/target_experiment.rb
74
106
  - lib/kameleoon/targeting/models.rb
75
107
  - lib/kameleoon/targeting/tree_builder.rb
76
108
  - lib/kameleoon/utils.rb
@@ -1,76 +0,0 @@
1
- module Kameleoon
2
- module Query
3
-
4
- def self.query_experiments(site_code)
5
- '{
6
- "operationName": "getExperiments",
7
- "query": "query getExperiments($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { experiments(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name type site { id code isKameleoonEnabled } status variations { id customJson } deviations { variationId value } respoolTime {variationId value } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
8
- "variables": {
9
- "filter": {
10
- "and": [{
11
- "condition": {
12
- "field": "status",
13
- "operator": "IN",
14
- "parameters": ["ACTIVE", "DEVIATED", "USED_AS_PERSONALIZATION"]
15
- }
16
- },
17
- {
18
- "condition": {
19
- "field": "type",
20
- "operator": "IN",
21
- "parameters": ["SERVER_SIDE", "HYBRID"]
22
- }
23
- },
24
- {
25
- "condition": {
26
- "field": "siteCode",
27
- "operator": "IN",
28
- "parameters": ["' + site_code + '"]
29
- }
30
- }]
31
- },
32
- "sort": [{
33
- "field": "id",
34
- "direction": "ASC"
35
- }]
36
- }
37
- }'
38
- end
39
-
40
- def self.query_feature_flags(site_code, environment)
41
- '{
42
- "operationName": "getFeatureFlags",
43
- "query": "query getFeatureFlags($first: Int, $after: String, $filter: FilteringExpression, $sort: [SortingParameter!]) { featureFlags(first: $first, after: $after, filter: $filter, sort: $sort) { edges { node { id name site { id code isKameleoonEnabled } bypassDeviation status variations { id customJson } respoolTime { variationId value } expositionRate identificationKey featureFlagSdkLanguageType featureStatus schedules { dateStart dateEnd } segment { id name conditionsData { firstLevelOrOperators firstLevel { orOperators conditions { targetingType isInclude ... on CustomDataTargetingCondition { customDataIndex value valueMatchType } } } } } __typename } __typename } pageInfo { endCursor hasNextPage __typename } totalCount __typename } }",
44
- "variables": {
45
- "filter": {
46
- "and": [{
47
- "condition": {
48
- "field": "featureStatus",
49
- "operator": "IN",
50
- "parameters": ["ACTIVATED", "SCHEDULED", "DEACTIVATED"]
51
- }
52
- },
53
- {
54
- "condition": {
55
- "field": "siteCode",
56
- "operator": "IN",
57
- "parameters": ["' + site_code + '"]
58
- }
59
- },
60
- {
61
- "condition": {
62
- "field": "environment.key",
63
- "operator": "IN",
64
- "parameters": ["' + environment + '"]
65
- }
66
- }]
67
- },
68
- "sort": [{
69
- "field": "id",
70
- "direction": "ASC"
71
- }]
72
- }
73
- }'
74
- end
75
- end
76
- end