splitclient-rb 7.0.0 → 7.0.1.pre.rc1
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.
- 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
|