splitclient-rb 7.0.0 → 7.0.1.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.txt +5 -0
- data/lib/splitclient-rb.rb +4 -4
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +4 -0
- data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +4 -0
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +2 -2
- data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +2 -2
- data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +49 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +94 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +112 -0
- data/lib/splitclient-rb/cache/stores/segment_store.rb +1 -7
- data/lib/splitclient-rb/cache/stores/split_store.rb +1 -7
- data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
- data/lib/splitclient-rb/clients/split_client.rb +1 -1
- data/lib/splitclient-rb/engine/parser/split_adapter.rb +18 -1
- data/lib/splitclient-rb/managers/split_manager.rb +10 -9
- data/lib/splitclient-rb/split_config.rb +29 -2
- data/lib/splitclient-rb/split_factory.rb +11 -4
- data/lib/splitclient-rb/split_factory_builder.rb +1 -21
- data/lib/splitclient-rb/version.rb +1 -1
- metadata +8 -8
- data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -184
- data/lib/splitclient-rb/localhost_split_factory.rb +0 -14
- data/lib/splitclient-rb/localhost_utils.rb +0 -59
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f134a9d9e88674dafbdcf9f57b77c73942664cbf14e9df1a7f55066f1fc62742
|
4
|
+
data.tar.gz: ee65206af2cae21099c0a1f3ab5facff024384c5698df3556ad40191d26600a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 698f220a7d5402b844997fddbfd64275f9141bcd5a196b18477df5490d741d150cf17c2cca205626c213e68fcb57efcbea1f037c2e0f3ef39c8a476796062470
|
7
|
+
data.tar.gz: 8bbdf861ad7e4a58cc7a8d5a5942a1882a218f6f36d6ce49019c84f02cf5f97aa0daa64c5ddd65135aa1efe5b4c8c85c97a54b3d0821434dd0cca21581a1fd5a
|
data/CHANGES.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
7.0.1 (Sep 13, 2019)
|
2
|
+
- Updated localhost mode so that parsing of test files results in JSON data for splits analogous to that returned by the Split backend.
|
3
|
+
- Resulting localhost data is now used indifferently from actual backend data by the SDK code.
|
4
|
+
- Removed specific spare code dealing with localhost operations.
|
5
|
+
|
1
6
|
7.0.0 (Aug 23, 2019)
|
2
7
|
- BREAKING CHANGE: block_until_ready is now a method in both split_client and split_manager that needs to be explicitly called. The block_until_ready parameter is now ignored if passed in the configuration, and defaults to 15s unless passed as a parameter of the block_until_ready method.
|
3
8
|
- Added warning to track calls when traffic type does not belong to an existing split (only issued in the online client and when SDK is ready).
|
data/lib/splitclient-rb.rb
CHANGED
@@ -25,18 +25,18 @@ require 'splitclient-rb/cache/senders/impressions_formatter'
|
|
25
25
|
require 'splitclient-rb/cache/senders/impressions_sender'
|
26
26
|
require 'splitclient-rb/cache/senders/metrics_sender'
|
27
27
|
require 'splitclient-rb/cache/senders/events_sender'
|
28
|
+
require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
|
29
|
+
require 'splitclient-rb/cache/stores/store_utils'
|
30
|
+
require 'splitclient-rb/cache/stores/localhost_split_builder'
|
28
31
|
require 'splitclient-rb/cache/stores/sdk_blocker'
|
29
32
|
require 'splitclient-rb/cache/stores/segment_store'
|
30
33
|
require 'splitclient-rb/cache/stores/split_store'
|
34
|
+
require 'splitclient-rb/cache/stores/localhost_split_store'
|
31
35
|
|
32
|
-
require 'splitclient-rb/localhost_utils'
|
33
|
-
require 'splitclient-rb/clients/localhost_split_client'
|
34
36
|
require 'splitclient-rb/clients/split_client'
|
35
|
-
require 'splitclient-rb/managers/localhost_split_manager'
|
36
37
|
require 'splitclient-rb/managers/split_manager'
|
37
38
|
require 'splitclient-rb/split_factory'
|
38
39
|
require 'splitclient-rb/split_factory_builder'
|
39
|
-
require 'splitclient-rb/localhost_split_factory'
|
40
40
|
require 'splitclient-rb/split_config'
|
41
41
|
require 'splitclient-rb/split_logger'
|
42
42
|
require 'splitclient-rb/validators'
|
@@ -6,11 +6,11 @@ module SplitIoClient
|
|
6
6
|
# Repository which forwards impressions interface to the selected adapter
|
7
7
|
class ImpressionsRepository < Repository
|
8
8
|
extend Forwardable
|
9
|
-
def_delegators :@
|
9
|
+
def_delegators :@repository, :add, :add_bulk, :batch, :clear, :empty?
|
10
10
|
|
11
11
|
def initialize(config)
|
12
12
|
super(config)
|
13
|
-
@
|
13
|
+
@repository = case @config.impressions_adapter.class.to_s
|
14
14
|
when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
|
15
15
|
Repositories::Impressions::MemoryRepository.new(@config)
|
16
16
|
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
@@ -4,12 +4,12 @@ module SplitIoClient
|
|
4
4
|
# Repository which forwards impressions interface to the selected adapter
|
5
5
|
class MetricsRepository < Repository
|
6
6
|
extend Forwardable
|
7
|
-
def_delegators :@
|
7
|
+
def_delegators :@repository, :add_count, :add_latency, :add_gauge, :counts, :latencies, :gauges,
|
8
8
|
:clear_counts, :clear_latencies, :clear_gauges, :clear, :fix_latencies
|
9
9
|
|
10
10
|
def initialize(config)
|
11
11
|
super(config)
|
12
|
-
@
|
12
|
+
@repository = case @config.metrics_adapter.class.to_s
|
13
13
|
when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
|
14
14
|
Repositories::Metrics::MemoryRepository.new(@config)
|
15
15
|
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Cache
|
5
|
+
module Senders
|
6
|
+
class LocalhostRepoCleaner
|
7
|
+
def initialize(impressions_repository, metrics_repository, events_repository, config)
|
8
|
+
@impressions_repository = impressions_repository
|
9
|
+
@metrics_repository = metrics_repository
|
10
|
+
@events_repository = events_repository
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
16
|
+
clear_repositories
|
17
|
+
else
|
18
|
+
cleaner_thread
|
19
|
+
|
20
|
+
if defined?(PhusionPassenger)
|
21
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
22
|
+
cleaner_thread if forked
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def cleaner_thread
|
31
|
+
@config.threads[:repo_cleaner] = Thread.new do
|
32
|
+
@config.logger.info('Starting repositories cleanup service')
|
33
|
+
loop do
|
34
|
+
clear_repositories
|
35
|
+
|
36
|
+
sleep(SplitIoClient::Utilities.randomize_interval(@config.features_refresh_rate))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_repositories
|
42
|
+
@impressions_repository.clear
|
43
|
+
@metrics_repository.clear
|
44
|
+
@events_repository.clear
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Cache
|
5
|
+
module Stores
|
6
|
+
class LocalhostSplitBuilder
|
7
|
+
class << self
|
8
|
+
def build_splits(splits)
|
9
|
+
splits.map do |feature, treatments|
|
10
|
+
build_split(feature, treatments)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def build_split(feature, treatments)
|
17
|
+
{
|
18
|
+
name: feature,
|
19
|
+
status: 'ACTIVE',
|
20
|
+
killed: false,
|
21
|
+
trafficAllocation: 100,
|
22
|
+
seed: 2_089_907_429,
|
23
|
+
defaultTreatment: 'control_treatment',
|
24
|
+
configurations: build_configurations(treatments),
|
25
|
+
conditions: build_conditions(treatments)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_configurations(treatments)
|
30
|
+
treatments.reduce({}) do |hash, treatment|
|
31
|
+
hash.merge(treatment[:treatment].to_sym => treatment[:config])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_conditions(treatments)
|
36
|
+
conditions = treatments.map do |treatment|
|
37
|
+
if treatment[:keys]
|
38
|
+
build_whitelist_treatment(treatment[:treatment], Array(treatment[:keys]))
|
39
|
+
else
|
40
|
+
build_rollout_treatment
|
41
|
+
end
|
42
|
+
.merge(partitions: build_partitions(treatment[:treatment], treatments))
|
43
|
+
end
|
44
|
+
|
45
|
+
conditions.sort_by { |condition| condition[:conditionType] }.reverse!
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_whitelist_treatment(treatment_name, whitelist_keys)
|
49
|
+
{
|
50
|
+
conditionType: 'WHITELIST',
|
51
|
+
matcherGroup: {
|
52
|
+
combiner: 'AND',
|
53
|
+
matchers: [{
|
54
|
+
keySelector: nil,
|
55
|
+
matcherType: 'WHITELIST',
|
56
|
+
negate: false,
|
57
|
+
whitelistMatcherData: {
|
58
|
+
whitelist: whitelist_keys
|
59
|
+
}
|
60
|
+
}]
|
61
|
+
},
|
62
|
+
label: "whitelisted #{treatment_name}"
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_rollout_treatment
|
67
|
+
{
|
68
|
+
conditionType: 'ROLLOUT',
|
69
|
+
matcherGroup: {
|
70
|
+
combiner: 'AND',
|
71
|
+
matchers: [
|
72
|
+
{
|
73
|
+
matcherType: 'ALL_KEYS',
|
74
|
+
negate: false
|
75
|
+
}
|
76
|
+
]
|
77
|
+
},
|
78
|
+
label: 'default rule'
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_partitions(current_treatment_name, treatments)
|
83
|
+
treatments.map do |treatment|
|
84
|
+
{
|
85
|
+
treatment: treatment[:treatment],
|
86
|
+
size: treatment[:treatment] == current_treatment_name ? 100 : 0
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Cache
|
5
|
+
module Stores
|
6
|
+
class LocalhostSplitStore
|
7
|
+
attr_reader :splits_repository
|
8
|
+
|
9
|
+
def initialize(splits_repository, config, sdk_blocker = nil)
|
10
|
+
@splits_repository = splits_repository
|
11
|
+
@config = config
|
12
|
+
@sdk_blocker = sdk_blocker
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
17
|
+
store_splits
|
18
|
+
else
|
19
|
+
splits_thread
|
20
|
+
|
21
|
+
if defined?(PhusionPassenger)
|
22
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
23
|
+
splits_thread if forked
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def splits_thread
|
32
|
+
@config.threads[:split_store] = Thread.new do
|
33
|
+
@config.logger.info('Starting splits fetcher service')
|
34
|
+
loop do
|
35
|
+
store_splits
|
36
|
+
|
37
|
+
sleep(StoreUtils.random_interval(@config.features_refresh_rate))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def store_splits
|
43
|
+
load_features.each do |split|
|
44
|
+
store_split(split)
|
45
|
+
end
|
46
|
+
|
47
|
+
if @sdk_blocker
|
48
|
+
@sdk_blocker.splits_ready!
|
49
|
+
@sdk_blocker.segments_ready!
|
50
|
+
end
|
51
|
+
rescue StandardError => error
|
52
|
+
@config.logger.error('Error while parsing the split file. ' \
|
53
|
+
'Check that the input file matches the expected format')
|
54
|
+
@config.log_found_exception(__method__.to_s, error)
|
55
|
+
end
|
56
|
+
|
57
|
+
def store_split(split)
|
58
|
+
@config.logger.debug("storing split (#{split[:name]})") if @config.debug_enabled
|
59
|
+
|
60
|
+
@splits_repository.add_split(split)
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_features
|
64
|
+
yaml_extensions = ['.yml', '.yaml']
|
65
|
+
if yaml_extensions.include? File.extname(@config.split_file)
|
66
|
+
parse_yaml_features
|
67
|
+
else
|
68
|
+
@config.logger.warn('Localhost mode: .split mocks ' \
|
69
|
+
'will be deprecated soon in favor of YAML files, which provide more ' \
|
70
|
+
'targeting power. Take a look in our documentation.')
|
71
|
+
|
72
|
+
parse_plain_text_features
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_plain_text_features
|
77
|
+
splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
|
78
|
+
feature, treatment = line.strip.split(' ')
|
79
|
+
|
80
|
+
next if line.start_with?('#') || line.strip.empty?
|
81
|
+
|
82
|
+
memo[feature] = [{ treatment: treatment }]
|
83
|
+
end
|
84
|
+
|
85
|
+
LocalhostSplitBuilder.build_splits(splits)
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_yaml_features
|
89
|
+
splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
|
90
|
+
feat_symbolized_keys = symbolize_feat_keys(feature)
|
91
|
+
|
92
|
+
feat_name = feature.keys.first
|
93
|
+
|
94
|
+
if memo[feat_name]
|
95
|
+
memo[feat_name] << feat_symbolized_keys
|
96
|
+
else
|
97
|
+
memo[feat_name] = [feat_symbolized_keys]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
LocalhostSplitBuilder.build_splits(splits)
|
102
|
+
end
|
103
|
+
|
104
|
+
def symbolize_feat_keys(yaml_feature)
|
105
|
+
yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
|
106
|
+
memo[k.to_sym] = k == 'config' ? v.to_json : v
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -38,7 +38,7 @@ module SplitIoClient
|
|
38
38
|
store_segments
|
39
39
|
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
40
40
|
|
41
|
-
sleep_for = random_interval(@config.segments_refresh_rate)
|
41
|
+
sleep_for = StoreUtils.random_interval(@config.segments_refresh_rate)
|
42
42
|
@config.logger.debug("Segments store is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
|
43
43
|
sleep(sleep_for)
|
44
44
|
end
|
@@ -53,12 +53,6 @@ module SplitIoClient
|
|
53
53
|
@config.log_found_exception(__method__.to_s, error)
|
54
54
|
end
|
55
55
|
|
56
|
-
def random_interval(interval)
|
57
|
-
random_factor = Random.new.rand(50..100) / 100.0
|
58
|
-
|
59
|
-
interval * random_factor
|
60
|
-
end
|
61
|
-
|
62
56
|
def segments_api
|
63
57
|
@segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @metrics, @segments_repository, @config)
|
64
58
|
end
|
@@ -34,7 +34,7 @@ module SplitIoClient
|
|
34
34
|
loop do
|
35
35
|
store_splits
|
36
36
|
|
37
|
-
sleep(random_interval(@config.features_refresh_rate))
|
37
|
+
sleep(StoreUtils.random_interval(@config.features_refresh_rate))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -57,12 +57,6 @@ module SplitIoClient
|
|
57
57
|
@config.log_found_exception(__method__.to_s, error)
|
58
58
|
end
|
59
59
|
|
60
|
-
def random_interval(interval)
|
61
|
-
random_factor = Random.new.rand(50..100) / 100.0
|
62
|
-
|
63
|
-
interval * random_factor
|
64
|
-
end
|
65
|
-
|
66
60
|
def splits_since(since)
|
67
61
|
splits_api.since(since)
|
68
62
|
end
|
@@ -133,7 +133,7 @@ module SplitIoClient
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
-
if ready? && !@splits_repository.traffic_type_exists(traffic_type_name)
|
136
|
+
if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
|
137
137
|
@config.logger.warn("track: Traffic Type #{traffic_type_name} " \
|
138
138
|
"does not have any corresponding Splits in this environment, make sure you're tracking " \
|
139
139
|
'your events to a valid traffic type defined in the Split console')
|
@@ -44,7 +44,9 @@ module SplitIoClient
|
|
44
44
|
@sdk_blocker = sdk_blocker
|
45
45
|
@config = config
|
46
46
|
|
47
|
-
|
47
|
+
start_localhost_components if @config.localhost_mode
|
48
|
+
|
49
|
+
start_standalone_components if @config.standalone? && !@config.localhost_mode
|
48
50
|
end
|
49
51
|
|
50
52
|
def start_standalone_components
|
@@ -55,6 +57,21 @@ module SplitIoClient
|
|
55
57
|
events_sender
|
56
58
|
end
|
57
59
|
|
60
|
+
def start_localhost_components
|
61
|
+
localhost_split_store
|
62
|
+
localhost_repo_cleaner
|
63
|
+
end
|
64
|
+
|
65
|
+
# Starts thread which loops constantly and retrieves splits from a file source
|
66
|
+
def localhost_split_store
|
67
|
+
LocalhostSplitStore.new(@splits_repository, @config, @sdk_blocker).call
|
68
|
+
end
|
69
|
+
|
70
|
+
# Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
|
71
|
+
def localhost_repo_cleaner
|
72
|
+
LocalhostRepoCleaner.new(@impressions_repository, @metrics_repository, @events_repository, @config).call
|
73
|
+
end
|
74
|
+
|
58
75
|
# Starts thread which loops constantly and stores splits in the splits_repository of choice
|
59
76
|
def split_store
|
60
77
|
SplitStore.new(@splits_repository, @api_key, @metrics, @config, @sdk_blocker).call
|
@@ -3,13 +3,9 @@ module SplitIoClient
|
|
3
3
|
#
|
4
4
|
# Creates a new split manager instance that connects to split.io API.
|
5
5
|
#
|
6
|
-
# @param api_key [String] the API key for your split account
|
7
|
-
#
|
8
6
|
# @return [SplitIoManager] split.io client instance
|
9
|
-
def initialize(
|
10
|
-
@localhost_mode_features = []
|
7
|
+
def initialize(splits_repository = nil, sdk_blocker, config)
|
11
8
|
@splits_repository = splits_repository
|
12
|
-
@adapter = adapter
|
13
9
|
@sdk_blocker = sdk_blocker
|
14
10
|
@config = config
|
15
11
|
end
|
@@ -91,13 +87,18 @@ module SplitIoClient
|
|
91
87
|
return {} unless split
|
92
88
|
|
93
89
|
begin
|
94
|
-
|
95
|
-
|
96
|
-
|
90
|
+
if @config.localhost_mode
|
91
|
+
treatments = split[:conditions]
|
92
|
+
.first[:partitions]
|
93
|
+
.map { |partition| partition[:treatment] }
|
94
|
+
else
|
95
|
+
treatments = split[:conditions]
|
96
|
+
.detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
|
97
|
+
.map { |partition| partition[:treatment] }
|
98
|
+
end
|
97
99
|
rescue StandardError
|
98
100
|
treatments = []
|
99
101
|
end
|
100
|
-
|
101
102
|
{
|
102
103
|
name: name,
|
103
104
|
traffic_type_name: split[:trafficTypeName],
|
@@ -38,7 +38,18 @@ module SplitIoClient
|
|
38
38
|
)
|
39
39
|
@connection_timeout = opts[:connection_timeout] || SplitConfig.default_connection_timeout
|
40
40
|
@read_timeout = opts[:read_timeout] || SplitConfig.default_read_timeout
|
41
|
-
|
41
|
+
|
42
|
+
@logger = opts[:logger] || SplitConfig.default_logger
|
43
|
+
|
44
|
+
if(opts[:reload_rate])
|
45
|
+
@features_refresh_rate = opts[:reload_rate]
|
46
|
+
@logger.warn('Localhost mode: reload_rate will be deprecated soon in favor of ' \
|
47
|
+
'features_refresh_rate. Take a look in our documentation.'
|
48
|
+
)
|
49
|
+
else
|
50
|
+
@features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
|
51
|
+
end
|
52
|
+
|
42
53
|
@segments_refresh_rate = opts[:segments_refresh_rate] || SplitConfig.default_segments_refresh_rate
|
43
54
|
@metrics_refresh_rate = opts[:metrics_refresh_rate] || SplitConfig.default_metrics_refresh_rate
|
44
55
|
|
@@ -56,7 +67,6 @@ module SplitIoClient
|
|
56
67
|
opts[:cache_adapter] || SplitConfig.default_cache_adapter, :map_adapter, nil, @redis_url
|
57
68
|
)
|
58
69
|
|
59
|
-
@logger = opts[:logger] || SplitConfig.default_logger
|
60
70
|
@debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
|
61
71
|
@transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
|
62
72
|
@block_until_ready = SplitConfig.default_block_until_ready
|
@@ -84,9 +94,14 @@ module SplitIoClient
|
|
84
94
|
@events_adapter = SplitConfig.init_cache_adapter(
|
85
95
|
opts[:cache_adapter] || SplitConfig.default_cache_adapter, :queue_adapter, @events_queue_size, @redis_url
|
86
96
|
)
|
97
|
+
|
98
|
+
@split_file = opts[:split_file] || SplitConfig.default_split_file
|
99
|
+
|
87
100
|
@valid_mode = true
|
88
101
|
@split_logger = SplitIoClient::SplitLogger.new(self)
|
89
102
|
@split_validator = SplitIoClient::Validators.new(self)
|
103
|
+
@localhost_mode = opts[:localhost_mode]
|
104
|
+
|
90
105
|
startup_log
|
91
106
|
end
|
92
107
|
|
@@ -235,6 +250,10 @@ module SplitIoClient
|
|
235
250
|
# @return [Integer]
|
236
251
|
attr_accessor :events_queue_size
|
237
252
|
|
253
|
+
attr_accessor :split_file
|
254
|
+
|
255
|
+
attr_accessor :localhost_mode
|
256
|
+
|
238
257
|
#
|
239
258
|
# The default split client configuration
|
240
259
|
#
|
@@ -340,6 +359,14 @@ module SplitIoClient
|
|
340
359
|
500
|
341
360
|
end
|
342
361
|
|
362
|
+
def self.default_split_file
|
363
|
+
File.join(Dir.home, '.split')
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.default_offline_refresh_rate
|
367
|
+
5
|
368
|
+
end
|
369
|
+
|
343
370
|
#
|
344
371
|
# The default logger object
|
345
372
|
#
|
@@ -2,6 +2,8 @@ module SplitIoClient
|
|
2
2
|
class SplitFactory
|
3
3
|
ROOT_PROCESS_ID = Process.pid
|
4
4
|
SINGLETON_WARN = 'We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'
|
5
|
+
LOCALHOST_API_KEY = 'localhost'
|
6
|
+
|
5
7
|
include SplitIoClient::Cache::Repositories
|
6
8
|
include SplitIoClient::Cache::Stores
|
7
9
|
|
@@ -20,10 +22,10 @@ module SplitIoClient
|
|
20
22
|
end
|
21
23
|
|
22
24
|
@api_key = api_key
|
23
|
-
@config = SplitConfig.new
|
25
|
+
@config = SplitConfig.new(config_hash.merge(localhost_mode: @api_key == LOCALHOST_API_KEY ))
|
24
26
|
|
25
27
|
raise 'Invalid SDK mode' unless valid_mode
|
26
|
-
|
28
|
+
|
27
29
|
@splits_repository = SplitsRepository.new(@config)
|
28
30
|
@segments_repository = SegmentsRepository.new(@config)
|
29
31
|
@impressions_repository = ImpressionsRepository.new(@config)
|
@@ -35,7 +37,7 @@ module SplitIoClient
|
|
35
37
|
@adapter = start!
|
36
38
|
|
37
39
|
@client = SplitClient.new(@api_key, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config)
|
38
|
-
@manager = SplitManager.new(@
|
40
|
+
@manager = SplitManager.new(@splits_repository, @sdk_blocker, @config)
|
39
41
|
|
40
42
|
validate_api_key
|
41
43
|
|
@@ -72,7 +74,12 @@ module SplitIoClient
|
|
72
74
|
case @config.mode
|
73
75
|
when :consumer
|
74
76
|
if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter
|
75
|
-
|
77
|
+
if !@config.localhost_mode
|
78
|
+
valid_startup_mode = true
|
79
|
+
else
|
80
|
+
@config.logger.error('Localhost mode cannot be used with Redis. ' \
|
81
|
+
'Use standalone mode and Memory adapter instead.')
|
82
|
+
end
|
76
83
|
else
|
77
84
|
@config.logger.error('Consumer mode cannot be used with Memory adapter. ' \
|
78
85
|
'Use Redis adapter instead.')
|
@@ -1,27 +1,7 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
module SplitIoClient
|
4
2
|
class SplitFactoryBuilder
|
5
3
|
def self.build(api_key, config = {})
|
6
|
-
|
7
|
-
when 'localhost'
|
8
|
-
configuration = SplitConfig.new(config)
|
9
|
-
LocalhostSplitFactory.new(split_file(config[:split_file], configuration.logger), configuration, config[:reload_rate])
|
10
|
-
else
|
11
|
-
SplitFactory.new(api_key, config)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def self.split_file(split_file_path, logger)
|
18
|
-
return split_file_path unless split_file_path.nil?
|
19
|
-
|
20
|
-
logger.warn('Localhost mode: .split mocks ' \
|
21
|
-
'will be deprecated soon in favor of YAML files, which provide more ' \
|
22
|
-
'targeting power. Take a look in our documentation.')
|
23
|
-
|
24
|
-
File.join(Dir.home, '.split')
|
4
|
+
SplitFactory.new(api_key, config)
|
25
5
|
end
|
26
6
|
end
|
27
7
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.1.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Split Software
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: allocation_stats
|
@@ -327,11 +327,14 @@ files:
|
|
327
327
|
- lib/splitclient-rb/cache/senders/events_sender.rb
|
328
328
|
- lib/splitclient-rb/cache/senders/impressions_formatter.rb
|
329
329
|
- lib/splitclient-rb/cache/senders/impressions_sender.rb
|
330
|
+
- lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb
|
330
331
|
- lib/splitclient-rb/cache/senders/metrics_sender.rb
|
332
|
+
- lib/splitclient-rb/cache/stores/localhost_split_builder.rb
|
333
|
+
- lib/splitclient-rb/cache/stores/localhost_split_store.rb
|
331
334
|
- lib/splitclient-rb/cache/stores/sdk_blocker.rb
|
332
335
|
- lib/splitclient-rb/cache/stores/segment_store.rb
|
333
336
|
- lib/splitclient-rb/cache/stores/split_store.rb
|
334
|
-
- lib/splitclient-rb/
|
337
|
+
- lib/splitclient-rb/cache/stores/store_utils.rb
|
335
338
|
- lib/splitclient-rb/clients/split_client.rb
|
336
339
|
- lib/splitclient-rb/engine/api/client.rb
|
337
340
|
- lib/splitclient-rb/engine/api/events.rb
|
@@ -374,9 +377,6 @@ files:
|
|
374
377
|
- lib/splitclient-rb/engine/parser/partition.rb
|
375
378
|
- lib/splitclient-rb/engine/parser/split_adapter.rb
|
376
379
|
- lib/splitclient-rb/exceptions.rb
|
377
|
-
- lib/splitclient-rb/localhost_split_factory.rb
|
378
|
-
- lib/splitclient-rb/localhost_utils.rb
|
379
|
-
- lib/splitclient-rb/managers/localhost_split_manager.rb
|
380
380
|
- lib/splitclient-rb/managers/split_manager.rb
|
381
381
|
- lib/splitclient-rb/redis_metrics_fixer.rb
|
382
382
|
- lib/splitclient-rb/split_config.rb
|
@@ -405,9 +405,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
405
405
|
version: '0'
|
406
406
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
407
407
|
requirements:
|
408
|
-
- - "
|
408
|
+
- - ">"
|
409
409
|
- !ruby/object:Gem::Version
|
410
|
-
version:
|
410
|
+
version: 1.3.1
|
411
411
|
requirements: []
|
412
412
|
rubygems_version: 3.0.3
|
413
413
|
signing_key:
|
@@ -1,184 +0,0 @@
|
|
1
|
-
module SplitIoClient
|
2
|
-
class LocalhostSplitClient
|
3
|
-
include SplitIoClient::LocalhostUtils
|
4
|
-
|
5
|
-
#
|
6
|
-
# variables to if the sdk is being used in localhost mode and store the list of features
|
7
|
-
attr_reader :localhost_mode
|
8
|
-
attr_reader :localhost_mode_features
|
9
|
-
|
10
|
-
#
|
11
|
-
# Creates a new split client instance that reads from the given splits file
|
12
|
-
#
|
13
|
-
# @param splits_file [File] file that contains some splits
|
14
|
-
#
|
15
|
-
# @return [LocalhostSplitIoClient] split.io localhost client instance
|
16
|
-
def initialize(splits_file, config, reload_rate = nil)
|
17
|
-
@localhost_mode = true
|
18
|
-
@localhost_mode_features = []
|
19
|
-
load_localhost_mode_features(splits_file, reload_rate)
|
20
|
-
@config = config
|
21
|
-
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# method that returns the sdk gem version
|
25
|
-
#
|
26
|
-
# @return [string] version value for this sdk
|
27
|
-
def self.sdk_version
|
28
|
-
'ruby-'+SplitIoClient::VERSION
|
29
|
-
end
|
30
|
-
|
31
|
-
#
|
32
|
-
# obtains the treatments and configs for a given set of features
|
33
|
-
#
|
34
|
-
# @param key [string] evaluation key, only used with yaml split files
|
35
|
-
# @param split_names [array] name of the features being validated
|
36
|
-
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
37
|
-
#
|
38
|
-
# @return [hash] map of treatments (split_name, treatment)
|
39
|
-
def get_treatments_with_config(key, split_names, attributes = nil)
|
40
|
-
get_localhost_treatments(key, split_names, attributes, 'get_treatments_with_config')
|
41
|
-
end
|
42
|
-
|
43
|
-
#
|
44
|
-
# obtains the treatments for a given set of features
|
45
|
-
#
|
46
|
-
# @param key [string] evaluation key, only used with yaml split files
|
47
|
-
# @param split_names [array] name of the features being validated
|
48
|
-
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
49
|
-
#
|
50
|
-
# @return [hash] map of treatments (split_name, treatment_name)
|
51
|
-
def get_treatments(key, split_names, attributes = nil)
|
52
|
-
treatments = get_localhost_treatments(key, split_names, attributes)
|
53
|
-
return treatments if treatments.nil?
|
54
|
-
keys = treatments.keys
|
55
|
-
treats = treatments.map { |_,t| t[:treatment] }
|
56
|
-
Hash[keys.zip(treats)]
|
57
|
-
end
|
58
|
-
|
59
|
-
#
|
60
|
-
# obtains the treatment for a given feature
|
61
|
-
#
|
62
|
-
# @param key [string] evaluation key, only used with yaml split files
|
63
|
-
# @param split_name [string] name of the feature that is being validated
|
64
|
-
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
65
|
-
#
|
66
|
-
# @return [string] corresponding treatment
|
67
|
-
def get_treatment(key, split_name, attributes = nil)
|
68
|
-
get_localhost_treatment(key, split_name, attributes)[:treatment]
|
69
|
-
end
|
70
|
-
|
71
|
-
#
|
72
|
-
# obtains the treatment and config for a given feature
|
73
|
-
#
|
74
|
-
# @param key [string] evaluation key, only used with yaml split files
|
75
|
-
# @param split_name [string] name of the feature that is being validated
|
76
|
-
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
77
|
-
#
|
78
|
-
# @return [hash] corresponding treatment and config
|
79
|
-
def get_treatment_with_config(key, split_name, attributes = nil)
|
80
|
-
get_localhost_treatment(key, split_name, attributes, 'get_treatment_with_config')
|
81
|
-
end
|
82
|
-
|
83
|
-
def track
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
#
|
89
|
-
# method to check if the sdk is running in localhost mode based on api key
|
90
|
-
#
|
91
|
-
# @return [boolean] True if is in localhost mode, false otherwise
|
92
|
-
def is_localhost_mode?
|
93
|
-
true
|
94
|
-
end
|
95
|
-
|
96
|
-
# @param key [string] evaluation key, only used with yaml split files
|
97
|
-
# @param split_name [string] name of the feature that is being validated
|
98
|
-
#
|
99
|
-
# @return [Hash] corresponding treatment and config, control otherwise
|
100
|
-
def get_localhost_treatment(key, split_name, attributes, calling_method = 'get_treatment')
|
101
|
-
control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, config: nil }
|
102
|
-
parsed_control_treatment = parsed_treatment(control_treatment)
|
103
|
-
|
104
|
-
bucketing_key, matching_key = keys_from_key(key)
|
105
|
-
return parsed_control_treatment unless @config.split_validator.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
|
106
|
-
|
107
|
-
sanitized_split_name = split_name.to_s.strip
|
108
|
-
|
109
|
-
if split_name.to_s != sanitized_split_name
|
110
|
-
@config.logger.warn("get_treatment: split_name #{split_name} has extra whitespace, trimming")
|
111
|
-
split_name = sanitized_split_name
|
112
|
-
end
|
113
|
-
|
114
|
-
treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && has_key(h[:keys], key) }.last
|
115
|
-
|
116
|
-
if treatment.nil?
|
117
|
-
treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && h[:keys] == nil }.last
|
118
|
-
end
|
119
|
-
|
120
|
-
if treatment && treatment[:treatment]
|
121
|
-
{
|
122
|
-
treatment: treatment[:treatment],
|
123
|
-
config: treatment[:config]
|
124
|
-
}
|
125
|
-
else
|
126
|
-
parsed_control_treatment
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def get_localhost_treatments(key, split_names, attributes = nil, calling_method = 'get_treatments')
|
131
|
-
return nil unless @config.split_validator.valid_get_treatments_parameters(calling_method, split_names)
|
132
|
-
|
133
|
-
sanitized_split_names = sanitize_split_names(calling_method, split_names)
|
134
|
-
|
135
|
-
if sanitized_split_names.empty?
|
136
|
-
@config.logger.error("#{calling_method}: split_names must be a non-empty Array")
|
137
|
-
return {}
|
138
|
-
end
|
139
|
-
|
140
|
-
split_names.each_with_object({}) do |split_name, memo|
|
141
|
-
memo.merge!(split_name => get_treatment_with_config(key, split_name, attributes))
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def sanitize_split_names(calling_method, split_names)
|
146
|
-
split_names.compact.uniq.select do |split_name|
|
147
|
-
if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
|
148
|
-
true
|
149
|
-
elsif split_name.is_a?(String) && split_name.empty?
|
150
|
-
@config.logger.warn("#{calling_method}: you passed an empty split_name, split_name must be a non-empty String or a Symbol")
|
151
|
-
false
|
152
|
-
else
|
153
|
-
@config.logger.warn("#{calling_method}: you passed an invalid split_name, split_name must be a non-empty String or a Symbol")
|
154
|
-
false
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def parsed_treatment(treatment_data)
|
160
|
-
{
|
161
|
-
treatment: treatment_data[:treatment],
|
162
|
-
config: treatment_data[:config]
|
163
|
-
}
|
164
|
-
end
|
165
|
-
|
166
|
-
def has_key(keys, key)
|
167
|
-
case keys
|
168
|
-
when Array then keys.include? key
|
169
|
-
when String then keys == key
|
170
|
-
else
|
171
|
-
false
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def keys_from_key(key)
|
176
|
-
case key
|
177
|
-
when Hash
|
178
|
-
key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k }
|
179
|
-
else
|
180
|
-
[nil, key].map { |k| k.nil? ? nil : k }
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module SplitIoClient
|
2
|
-
class LocalhostSplitFactory
|
3
|
-
attr_reader :client, :manager
|
4
|
-
|
5
|
-
def initialize(splits_file, config, reload_rate = nil, logger = nil)
|
6
|
-
@splits_file = splits_file
|
7
|
-
@reload_rate = reload_rate
|
8
|
-
@config = config
|
9
|
-
|
10
|
-
@client = LocalhostSplitClient.new(@splits_file, @config, @reload_rate)
|
11
|
-
@manager = LocalhostSplitManager.new(@splits_file, @reload_rate)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
module SplitIoClient
|
2
|
-
module LocalhostUtils
|
3
|
-
|
4
|
-
require 'yaml'
|
5
|
-
#
|
6
|
-
# method to set localhost mode features by reading the given .splits
|
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
|
-
# @return nil
|
11
|
-
def load_localhost_mode_features(splits_file, reload_rate = nil)
|
12
|
-
return @localhost_mode_features unless File.exists?(splits_file)
|
13
|
-
|
14
|
-
store_features(splits_file)
|
15
|
-
|
16
|
-
return unless reload_rate
|
17
|
-
|
18
|
-
Thread.new do
|
19
|
-
loop do
|
20
|
-
@localhost_mode_features = []
|
21
|
-
store_features(splits_file)
|
22
|
-
|
23
|
-
sleep(SplitIoClient::Utilities.randomize_interval(reload_rate))
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def store_features(splits_file)
|
29
|
-
yaml_extensions = [".yml", ".yaml"]
|
30
|
-
if yaml_extensions.include? File.extname(splits_file)
|
31
|
-
store_yaml_features(splits_file)
|
32
|
-
else
|
33
|
-
store_plain_text_features(splits_file)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def store_plain_text_features(splits_file)
|
40
|
-
File.open(splits_file).each do |line|
|
41
|
-
feature, treatment = line.strip.split(' ')
|
42
|
-
|
43
|
-
next if line.start_with?('#') || line.strip.empty?
|
44
|
-
|
45
|
-
@localhost_mode_features << { feature: feature, treatment: treatment, key: nil, config: nil }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def store_yaml_features(splits_file)
|
50
|
-
YAML.load(File.read(splits_file)).each do |feature|
|
51
|
-
feat_symbolized_keys = feature[feature.keys.first].inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
52
|
-
|
53
|
-
feat_symbolized_keys[:config] = feat_symbolized_keys[:config].to_json
|
54
|
-
|
55
|
-
@localhost_mode_features << { feature: feature.keys.first }.merge(feat_symbolized_keys)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,60 +0,0 @@
|
|
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
|
-
features = @localhost_mode_features.find_all { |feat| feat[:feature] == split_name }
|
25
|
-
|
26
|
-
return nil if features.nil?
|
27
|
-
|
28
|
-
treatments = features.map { |feat| feat[:treatment] }
|
29
|
-
|
30
|
-
configs = Hash[ features.map { |feat| [ feat[:treatment].to_sym, feat[:config] ] } ]
|
31
|
-
|
32
|
-
{
|
33
|
-
change_number: nil,
|
34
|
-
killed: false,
|
35
|
-
name: split_name,
|
36
|
-
traffic_type: nil,
|
37
|
-
treatments: treatments,
|
38
|
-
configs: configs
|
39
|
-
}
|
40
|
-
end
|
41
|
-
|
42
|
-
#
|
43
|
-
# method to get the split list from the client
|
44
|
-
#
|
45
|
-
# @returns Array of split view
|
46
|
-
def splits
|
47
|
-
split_names.map do |split_name|
|
48
|
-
split(split_name)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
#
|
53
|
-
# method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
|
54
|
-
#
|
55
|
-
# @returns [object] array of split names (String)
|
56
|
-
def split_names
|
57
|
-
@localhost_mode_features.map{ |feat| feat[:feature]}.uniq
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|