splitclient-rb 3.1.2 → 3.1.3.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +7 -0
  3. data/Detailed-README.md +20 -5
  4. data/exe/splitio +54 -3
  5. data/lib/cache/adapters/memory_adapters/map_adapter.rb +6 -0
  6. data/lib/cache/adapters/redis_adapter.rb +6 -1
  7. data/lib/cache/repositories/impressions/memory_repository.rb +8 -2
  8. data/lib/cache/repositories/impressions/redis_repository.rb +2 -2
  9. data/lib/cache/repositories/impressions_repository.rb +3 -0
  10. data/lib/cache/repositories/metrics_repository.rb +1 -0
  11. data/lib/cache/repositories/repository.rb +1 -1
  12. data/lib/cache/repositories/segments_repository.rb +16 -1
  13. data/lib/cache/repositories/splits_repository.rb +21 -7
  14. data/lib/cache/senders/impressions_sender.rb +16 -12
  15. data/lib/cache/senders/metrics_sender.rb +41 -0
  16. data/lib/cache/stores/sdk_blocker.rb +9 -8
  17. data/lib/cache/stores/segment_store.rb +1 -1
  18. data/lib/engine/api/client.rb +15 -2
  19. data/lib/engine/api/metrics.rb +57 -0
  20. data/lib/engine/parser/split_adapter.rb +9 -140
  21. data/lib/engine/parser/split_treatment.rb +7 -5
  22. data/lib/engine/partitions/treatments.rb +1 -5
  23. data/lib/splitclient-rb.rb +14 -4
  24. data/lib/splitclient-rb/clients/localhost_split_client.rb +89 -0
  25. data/lib/splitclient-rb/clients/split_client.rb +114 -0
  26. data/lib/splitclient-rb/localhost_split_factory.rb +6 -187
  27. data/lib/splitclient-rb/localhost_utils.rb +36 -0
  28. data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
  29. data/lib/splitclient-rb/managers/split_manager.rb +77 -0
  30. data/lib/splitclient-rb/split_config.rb +20 -0
  31. data/lib/splitclient-rb/split_factory.rb +16 -217
  32. data/lib/splitclient-rb/split_factory_builder.rb +3 -2
  33. data/lib/splitclient-rb/version.rb +1 -1
  34. data/lib/splitclient-rb_utilitites.rb +24 -19
  35. data/splitclient-rb.gemspec +1 -1
  36. data/{splitio.yml → splitio.yml.example} +0 -0
  37. metadata +26 -19
@@ -0,0 +1,114 @@
1
+ module SplitIoClient
2
+ class SplitClient
3
+ #
4
+ # Creates a new split client instance that connects to split.io API.
5
+ #
6
+ # @param api_key [String] the API key for your split account
7
+ #
8
+ # @return [SplitIoClient] split.io client instance
9
+ def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository)
10
+ @config = config
11
+
12
+ @splits_repository = splits_repository
13
+ @segments_repository = segments_repository
14
+ @impressions_repository = impressions_repository
15
+ @metrics_repository = metrics_repository
16
+
17
+ @adapter = adapter
18
+ end
19
+
20
+ def get_treatments(key, split_names, attributes = nil)
21
+ bucketing_key, matching_key = keys_from_key(key)
22
+ bucketing_key = matching_key if bucketing_key.nil?
23
+
24
+ treatments =
25
+ @splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
26
+ memo.merge!(name => get_treatment(key, name, attributes, data, false))
27
+ end
28
+
29
+ if @config.impressions_queue_size > 0
30
+ @impressions_repository.add_bulk(matching_key, bucketing_key, treatments, (Time.now.to_f * 1000.0).to_i)
31
+ end
32
+
33
+ treatments
34
+ end
35
+
36
+ #
37
+ # obtains the treatment for a given feature
38
+ #
39
+ # @param key [String/Hash] user id or hash with matching_key/bucketing_key
40
+ # @param split_name [String/Array] name of the feature that is being validated or array of them
41
+ #
42
+ # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
43
+ def get_treatment(key, split_name, attributes = nil, split_data = nil, store_impressions = true)
44
+ bucketing_key, matching_key = keys_from_key(key)
45
+ bucketing_key = matching_key if bucketing_key.nil?
46
+
47
+ if matching_key.nil?
48
+ @config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
49
+ return Treatments::CONTROL
50
+ end
51
+
52
+ if split_name.nil?
53
+ @config.logger.warn('split_name was null for key: ' + key)
54
+ return Treatments::CONTROL
55
+ end
56
+
57
+ start = Time.now
58
+ result = nil
59
+
60
+ begin
61
+ split = split_data ? split_data : @splits_repository.get_split(split_name)
62
+
63
+ result = if split.nil?
64
+ Treatments::CONTROL
65
+ else
66
+ SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository).call(
67
+ { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
68
+ )
69
+ end
70
+ rescue StandardError => error
71
+ @config.log_found_exception(__method__.to_s, error)
72
+ end
73
+
74
+ result = result.nil? ? Treatments::CONTROL : result
75
+
76
+ begin
77
+ latency = (Time.now - start) * 1000.0
78
+ if @config.impressions_queue_size > 0 && store_impressions
79
+ # Disable impressions if @config.impressions_queue_size == -1
80
+ @impressions_repository.add(split_name,
81
+ 'key_name' => matching_key,
82
+ 'bucketing_key' => bucketing_key,
83
+ 'treatment' => result,
84
+ 'time' => (Time.now.to_f * 1000.0).to_i
85
+ )
86
+ end
87
+
88
+ # Measure
89
+ @adapter.metrics.time("sdk.get_treatment", latency)
90
+ rescue StandardError => error
91
+ @config.log_found_exception(__method__.to_s, error)
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ def keys_from_key(key)
98
+ case key.class.to_s
99
+ when 'Hash'
100
+ key.values_at(:bucketing_key, :matching_key)
101
+ when 'String'
102
+ [key, key]
103
+ end
104
+ end
105
+
106
+ #
107
+ # method that returns the sdk gem version
108
+ #
109
+ # @return [string] version value for this sdk
110
+ def self.sdk_version
111
+ 'ruby-'+SplitIoClient::VERSION
112
+ end
113
+ end
114
+ end
@@ -1,194 +1,13 @@
1
- require 'logger'
2
1
  module SplitIoClient
2
+ class LocalhostSplitFactory
3
+ attr_reader :client, :manager
3
4
 
4
- #
5
- # main class for localhost split client sdk
6
- #
7
- class LocalhostSplitFactory < NoMethodError
8
- class LocalhostSplitManager < NoMethodError
9
- #
10
- # constant that defines the localhost mode
11
- LOCALHOST_MODE = 'localhost'
12
-
13
- #
14
- # object that acts as an api adapter connector. used to get and post to api endpoints
15
- attr_reader :adapter
16
-
17
- #
18
- # Creates a new split manager instance that holds the splits from a given file
19
- #
20
- # @param splits_file [File] the .split file that contains the splits
21
- #
22
- # @return [LocalhostSplitIoManager] split.io localhost manager instance
23
- def initialize(splits_file)
24
- @localhost_mode = true
25
- @localhost_mode_features = []
26
- load_localhost_mode_features(splits_file)
27
- end
28
-
29
- #
30
- # method to set localhost mode features by reading the given .splits
31
- #
32
- # @param splits_file [File] the .split file that contains the splits
33
- # @returns [void]
34
- def load_localhost_mode_features(splits_file)
35
- if File.exists?(splits_file)
36
- line_num=0
37
- File.open(splits_file).each do |line|
38
- line_data = line.strip.split(" ")
39
- @localhost_mode_features << {feature: line_data[0], treatment: line_data[1]} unless line.start_with?('#') || line.strip.empty?
40
- end
41
- end
42
- @localhost_mode_features
43
- end
44
-
45
- #
46
- # method to get a split view
47
- #
48
- # @returns a split view
49
- def split(split_name)
50
- @localhost_mode_features.find {|x| x[:feature] == split_name}
51
- end
52
-
53
- #
54
- # method to get the split list from the client
55
- #
56
- # @returns [object] array of splits
57
- def splits
58
- @localhost_mode_features
59
- end
60
-
61
- #
62
- # method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
63
- #
64
- # @returns [object] array of split names (String)
65
- def split_names
66
- @localhost_mode_features.each_with_object([]) do |split, memo|
67
- memo << split[:feature]
68
- end
69
- end
70
- end
71
-
72
- class LocalhostSplitClient < NoMethodError
73
- #
74
- # constant that defines the localhost mode
75
- LOCALHOST_MODE = 'localhost'
76
-
77
- #
78
- # variables to if the sdk is being used in localhost mode and store the list of features
79
- attr_reader :localhost_mode
80
- attr_reader :localhost_mode_features
81
-
82
- #
83
- # Creates a new split client instance that reads from the given splits file
84
- #
85
- # @param splits_file [File] file that contains some splits
86
- #
87
- # @return [LocalhostSplitIoClient] split.io localhost client instance
88
- def initialize(splits_file)
89
- @localhost_mode = true
90
- @localhost_mode_features = []
91
- load_localhost_mode_features(splits_file)
92
- end
93
-
94
- def get_treatments(key, split_names, attributes = nil)
95
- split_names.each_with_object({}) do |name, memo|
96
- puts "name #{name} memo #{memo}"
97
- memo.merge!(name => get_treatment(key, name, attributes))
98
- end
99
- end
100
-
101
- #
102
- # obtains the treatment for a given feature
103
- #
104
- # @param id [string] user id
105
- # @param feature [string] name of the feature that is being validated
106
- #
107
- # @return [Treatment] treatment constant value
108
- def get_treatment(id, feature, attributes = nil)
109
- unless id
110
- @config.logger.warn('id was null for feature: ' + feature)
111
- return Treatments::CONTROL
112
- end
113
-
114
- unless feature
115
- @config.logger.warn('feature was null for id: ' + id)
116
- return Treatments::CONTROL
117
- end
118
- result = get_localhost_treatment(feature)
119
- end
120
-
121
-
122
- #
123
- # auxiliary method to get the treatments avoding exceptions
124
- #
125
- # @param id [string] user id
126
- # @param feature [string] name of the feature that is being validated
127
- #
128
- # @return [Treatment] tretment constant value
129
- def get_treatment_without_exception_handling(id, feature, attributes = nil)
130
- get_treatment(id, feature, attributes)
131
- end
132
-
133
- #
134
- # method that returns the sdk gem version
135
- #
136
- # @return [string] version value for this sdk
137
- def self.sdk_version
138
- 'RubyClientSDK-'+SplitIoClient::VERSION
139
- end
140
-
141
- #
142
- # method to check if the sdk is running in localhost mode based on api key
143
- #
144
- # @return [boolean] True if is in localhost mode, false otherwise
145
- def is_localhost_mode?
146
- true
147
- end
148
-
149
- #
150
- # method to set localhost mode features by reading .splits file located at home directory
151
- #
152
- # @returns [void]
153
- def load_localhost_mode_features(splits_file)
154
- if File.exists?(splits_file)
155
- line_num=0
156
- File.open(splits_file).each do |line|
157
- line_data = line.strip.split(" ")
158
- @localhost_mode_features << {feature: line_data[0], treatment: line_data[1]} unless line.start_with?('#') || line.strip.empty?
159
- end
160
- end
161
- end
162
-
163
- #
164
- # method to check the treatment for the given feature in localhost mode
165
- #
166
- # @return [boolean] true if the feature is available in localhost mode, false otherwise
167
- def get_localhost_treatment(feature)
168
- localhost_result = Treatments::CONTROL
169
- treatment = @localhost_mode_features.select{|h| h[:feature] == feature}.last
170
- localhost_result = treatment[:treatment] if !treatment.nil?
171
- localhost_result
172
- end
173
-
174
- private :get_treatment_without_exception_handling, :is_localhost_mode?,
175
- :load_localhost_mode_features, :get_localhost_treatment
176
-
177
- end
178
-
179
- private_constant :LocalhostSplitClient
180
- private_constant :LocalhostSplitManager
181
-
182
- def initialize(splits_file)
5
+ def initialize(splits_file, reload_rate = nil)
183
6
  @splits_file = splits_file
184
- end
185
-
186
- def client
187
- @client ||= LocalhostSplitClient.new(@splits_file)
188
- end
7
+ @reload_rate = reload_rate
189
8
 
190
- def manager
191
- @manager ||= LocalhostSplitManager.new(@splits_file)
9
+ @client = LocalhostSplitClient.new(@splits_file, @reload_rate)
10
+ @manager = LocalhostSplitManager.new(@splits_file, @reload_rate)
192
11
  end
193
12
  end
194
13
  end
@@ -0,0 +1,36 @@
1
+ module SplitIoClient
2
+ module LocalhostUtils
3
+ #
4
+ # method to set localhost mode features by reading the given .splits
5
+ #
6
+ # @param splits_file [File] the .split file that contains the splits
7
+ # @param reload_rate [Integer] the number of seconds to reload splits_file
8
+ # @return nil
9
+ def load_localhost_mode_features(splits_file, reload_rate = nil)
10
+ return @localhost_mode_features unless File.exists?(splits_file)
11
+
12
+ store_features(splits_file)
13
+
14
+ return unless reload_rate
15
+
16
+ Thread.new do
17
+ loop do
18
+ @localhost_mode_features = []
19
+ store_features(splits_file)
20
+
21
+ sleep(::Utilities.randomize_interval(reload_rate))
22
+ end
23
+ end
24
+ end
25
+
26
+ def store_features(splits_file)
27
+ File.open(splits_file).each do |line|
28
+ feature, treatment = line.strip.split(' ')
29
+
30
+ next if line.start_with?('#') || line.strip.empty?
31
+
32
+ @localhost_mode_features << { feature: feature, treatment: treatment }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ module SplitIoClient
2
+ class LocalhostSplitManager
3
+ include SplitIoClient::LocalhostUtils
4
+
5
+ #
6
+ # Creates a new split manager instance that holds the splits from a given file
7
+ #
8
+ # @param splits_file [File] the .split file that contains the splits
9
+ # @param reload_rate [Integer] the number of seconds to reload splits_file
10
+ #
11
+ # @return [LocalhostSplitIoManager] split.io localhost manager instance
12
+ def initialize(splits_file, reload_rate = nil)
13
+ @localhost_mode = true
14
+ @localhost_mode_features = []
15
+
16
+ load_localhost_mode_features(splits_file, reload_rate)
17
+ end
18
+
19
+ #
20
+ # method to get a split view
21
+ #
22
+ # @returns a split view
23
+ def split(split_name)
24
+ @localhost_mode_features.find { |x| x[:feature] == split_name }
25
+ end
26
+
27
+ #
28
+ # method to get the split list from the client
29
+ #
30
+ # @returns [object] array of splits
31
+ def splits
32
+ @localhost_mode_features
33
+ end
34
+
35
+ #
36
+ # method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
37
+ #
38
+ # @returns [object] array of split names (String)
39
+ def split_names
40
+ @localhost_mode_features.each_with_object([]) do |split, memo|
41
+ memo << split[:feature]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ module SplitIoClient
2
+ class SplitManager
3
+ #
4
+ # Creates a new split manager instance that connects to split.io API.
5
+ #
6
+ # @param api_key [String] the API key for your split account
7
+ #
8
+ # @return [SplitIoManager] split.io client instance
9
+ def initialize(api_key, config = {}, adapter = nil, splits_repository = nil)
10
+ @localhost_mode_features = []
11
+ @config = config
12
+ @splits_repository = splits_repository
13
+ @adapter = adapter
14
+ end
15
+
16
+ #
17
+ # method to get the split list from the client
18
+ #
19
+ # @returns [object] array of splits
20
+ def splits
21
+ return if @splits_repository.nil?
22
+
23
+ @splits_repository.splits.each_with_object([]) do |(name, split), memo|
24
+ split_model = Engine::Models::Split.new(split)
25
+
26
+ memo << build_split_view(name, split) unless split_model.archived?
27
+ end
28
+ end
29
+
30
+ #
31
+ # method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
32
+ #
33
+ # @returns [object] array of split names (String)
34
+ def split_names
35
+ return if @splits_repository.nil?
36
+
37
+ @splits_repository.split_names
38
+ end
39
+
40
+ #
41
+ # method to get a split view
42
+ #
43
+ # @returns a split view
44
+ def split(split_name)
45
+ if @splits_repository
46
+ split = @splits_repository.get_split(split_name)
47
+
48
+ build_split_view(split_name, split) unless split.nil? or split_model(split).archived?
49
+ end
50
+ end
51
+
52
+ def build_split_view(name, split)
53
+ return {} unless split
54
+
55
+ treatments =
56
+ if split[:conditions] && split[:conditions][0][:partitions]
57
+ split[:conditions][0][:partitions].map { |partition| partition[:treatment] }
58
+ else
59
+ []
60
+ end
61
+
62
+ {
63
+ name: name,
64
+ traffic_type_name: split[:trafficTypeName],
65
+ killed: split[:killed],
66
+ treatments: treatments,
67
+ change_number: split[:changeNumber]
68
+ }
69
+ end
70
+
71
+ private
72
+
73
+ def split_model(split)
74
+ split_model = Engine::Models::Split.new(split)
75
+ end
76
+ end
77
+ end