kameleoon-client-ruby 1.1.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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