launchdarkly-server-sdk 6.3.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -4
- data/lib/ldclient-rb/config.rb +112 -62
- data/lib/ldclient-rb/context.rb +444 -0
- data/lib/ldclient-rb/evaluation_detail.rb +26 -22
- data/lib/ldclient-rb/events.rb +256 -146
- data/lib/ldclient-rb/flags_state.rb +26 -15
- data/lib/ldclient-rb/impl/big_segments.rb +18 -18
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +59 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +386 -142
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +7 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +136 -0
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +19 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +38 -30
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +24 -11
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +109 -12
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +255 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
- data/lib/ldclient-rb/impl/repeating_task.rb +3 -4
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +59 -1
- data/lib/ldclient-rb/in_memory_store.rb +9 -2
- data/lib/ldclient-rb/integrations/consul.rb +2 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +2 -2
- data/lib/ldclient-rb/integrations/file_data.rb +4 -4
- data/lib/ldclient-rb/integrations/redis.rb +5 -5
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +287 -62
- data/lib/ldclient-rb/integrations/test_data.rb +18 -14
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +20 -9
- data/lib/ldclient-rb/interfaces.rb +600 -14
- data/lib/ldclient-rb/ldclient.rb +314 -134
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +52 -6
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +9 -11
- data/lib/ldclient-rb/stream.rb +96 -34
- data/lib/ldclient-rb/util.rb +97 -14
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +3 -4
- metadata +65 -23
- data/lib/ldclient-rb/event_summarizer.rb +0 -55
- data/lib/ldclient-rb/file_data_source.rb +0 -23
- data/lib/ldclient-rb/impl/event_factory.rb +0 -126
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,126 +0,0 @@
|
|
1
|
-
|
2
|
-
module LaunchDarkly
|
3
|
-
module Impl
|
4
|
-
# Event constructors are centralized here to avoid mistakes and repetitive logic.
|
5
|
-
# The LDClient owns two instances of EventFactory: one that always embeds evaluation reasons
|
6
|
-
# in the events (for when variation_detail is called) and one that doesn't.
|
7
|
-
#
|
8
|
-
# Note that these methods do not set the "creationDate" property, because in the Ruby client,
|
9
|
-
# that is done by EventProcessor.add_event().
|
10
|
-
class EventFactory
|
11
|
-
def initialize(with_reasons)
|
12
|
-
@with_reasons = with_reasons
|
13
|
-
end
|
14
|
-
|
15
|
-
def new_eval_event(flag, user, detail, default_value, prereq_of_flag = nil)
|
16
|
-
add_experiment_data = is_experiment(flag, detail.reason)
|
17
|
-
e = {
|
18
|
-
kind: 'feature',
|
19
|
-
key: flag[:key],
|
20
|
-
user: user,
|
21
|
-
variation: detail.variation_index,
|
22
|
-
value: detail.value,
|
23
|
-
default: default_value,
|
24
|
-
version: flag[:version]
|
25
|
-
}
|
26
|
-
# the following properties are handled separately so we don't waste bandwidth on unused keys
|
27
|
-
e[:trackEvents] = true if add_experiment_data || flag[:trackEvents]
|
28
|
-
e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
|
29
|
-
e[:prereqOf] = prereq_of_flag[:key] if !prereq_of_flag.nil?
|
30
|
-
e[:reason] = detail.reason if add_experiment_data || @with_reasons
|
31
|
-
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
32
|
-
e
|
33
|
-
end
|
34
|
-
|
35
|
-
def new_default_event(flag, user, default_value, reason)
|
36
|
-
e = {
|
37
|
-
kind: 'feature',
|
38
|
-
key: flag[:key],
|
39
|
-
user: user,
|
40
|
-
value: default_value,
|
41
|
-
default: default_value,
|
42
|
-
version: flag[:version]
|
43
|
-
}
|
44
|
-
e[:trackEvents] = true if flag[:trackEvents]
|
45
|
-
e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
|
46
|
-
e[:reason] = reason if @with_reasons
|
47
|
-
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
48
|
-
e
|
49
|
-
end
|
50
|
-
|
51
|
-
def new_unknown_flag_event(key, user, default_value, reason)
|
52
|
-
e = {
|
53
|
-
kind: 'feature',
|
54
|
-
key: key,
|
55
|
-
user: user,
|
56
|
-
value: default_value,
|
57
|
-
default: default_value
|
58
|
-
}
|
59
|
-
e[:reason] = reason if @with_reasons
|
60
|
-
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
61
|
-
e
|
62
|
-
end
|
63
|
-
|
64
|
-
def new_identify_event(user)
|
65
|
-
{
|
66
|
-
kind: 'identify',
|
67
|
-
key: user[:key],
|
68
|
-
user: user
|
69
|
-
}
|
70
|
-
end
|
71
|
-
|
72
|
-
def new_alias_event(current_context, previous_context)
|
73
|
-
{
|
74
|
-
kind: 'alias',
|
75
|
-
key: current_context[:key],
|
76
|
-
contextKind: context_to_context_kind(current_context),
|
77
|
-
previousKey: previous_context[:key],
|
78
|
-
previousContextKind: context_to_context_kind(previous_context)
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
|
-
def new_custom_event(event_name, user, data, metric_value)
|
83
|
-
e = {
|
84
|
-
kind: 'custom',
|
85
|
-
key: event_name,
|
86
|
-
user: user
|
87
|
-
}
|
88
|
-
e[:data] = data if !data.nil?
|
89
|
-
e[:metricValue] = metric_value if !metric_value.nil?
|
90
|
-
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
91
|
-
e
|
92
|
-
end
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
|
-
def context_to_context_kind(user)
|
97
|
-
if !user.nil? && user[:anonymous]
|
98
|
-
return "anonymousUser"
|
99
|
-
else
|
100
|
-
return "user"
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def is_experiment(flag, reason)
|
105
|
-
return false if !reason
|
106
|
-
|
107
|
-
if reason.in_experiment
|
108
|
-
return true
|
109
|
-
end
|
110
|
-
|
111
|
-
case reason[:kind]
|
112
|
-
when 'RULE_MATCH'
|
113
|
-
index = reason[:ruleIndex]
|
114
|
-
if !index.nil?
|
115
|
-
rules = flag[:rules] || []
|
116
|
-
return index >= 0 && index < rules.length && rules[index][:trackEvents]
|
117
|
-
end
|
118
|
-
when 'FALLTHROUGH'
|
119
|
-
return !!flag[:trackEventsFallthrough]
|
120
|
-
end
|
121
|
-
false
|
122
|
-
end
|
123
|
-
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
data/lib/ldclient-rb/newrelic.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module LaunchDarkly
|
2
|
-
# @private
|
3
|
-
class LDNewRelic
|
4
|
-
begin
|
5
|
-
require "newrelic_rpm"
|
6
|
-
NR_ENABLED = defined?(::NewRelic::Agent.add_custom_parameters)
|
7
|
-
rescue ScriptError, StandardError
|
8
|
-
NR_ENABLED = false
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.annotate_transaction(key, value)
|
12
|
-
if NR_ENABLED
|
13
|
-
::NewRelic::Agent.add_custom_parameters(key.to_s => value.to_s)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,88 +0,0 @@
|
|
1
|
-
require "ldclient-rb/interfaces"
|
2
|
-
require "ldclient-rb/impl/integrations/redis_impl"
|
3
|
-
|
4
|
-
module LaunchDarkly
|
5
|
-
#
|
6
|
-
# An implementation of the LaunchDarkly client's feature store that uses a Redis
|
7
|
-
# instance. This object holds feature flags and related data received from the
|
8
|
-
# streaming API. Feature data can also be further cached in memory to reduce overhead
|
9
|
-
# of calls to Redis.
|
10
|
-
#
|
11
|
-
# To use this class, you must first have the `redis` and `connection-pool` gems
|
12
|
-
# installed. Then, create an instance and store it in the `feature_store` property
|
13
|
-
# of your client configuration.
|
14
|
-
#
|
15
|
-
# @deprecated Use the factory method in {LaunchDarkly::Integrations::Redis} instead. This specific
|
16
|
-
# implementation class may be changed or removed in the future.
|
17
|
-
#
|
18
|
-
class RedisFeatureStore
|
19
|
-
include LaunchDarkly::Interfaces::FeatureStore
|
20
|
-
|
21
|
-
# Note that this class is now just a facade around CachingStoreWrapper, which is in turn delegating
|
22
|
-
# to RedisFeatureStoreCore where the actual database logic is. This class was retained for historical
|
23
|
-
# reasons, so that existing code can still call RedisFeatureStore.new. In the future, we will migrate
|
24
|
-
# away from exposing these concrete classes and use factory methods instead.
|
25
|
-
|
26
|
-
#
|
27
|
-
# Constructor for a RedisFeatureStore instance.
|
28
|
-
#
|
29
|
-
# @param opts [Hash] the configuration options
|
30
|
-
# @option opts [String] :redis_url URL of the Redis instance (shortcut for omitting redis_opts)
|
31
|
-
# @option opts [Hash] :redis_opts options to pass to the Redis constructor (if you want to specify more than just redis_url)
|
32
|
-
# @option opts [String] :prefix namespace prefix to add to all hash keys used by LaunchDarkly
|
33
|
-
# @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
|
34
|
-
# @option opts [Integer] :max_connections size of the Redis connection pool
|
35
|
-
# @option opts [Integer] :expiration expiration time for the in-memory cache, in seconds; 0 for no local caching
|
36
|
-
# @option opts [Integer] :capacity maximum number of feature flags (or related objects) to cache locally
|
37
|
-
# @option opts [Object] :pool custom connection pool, if desired
|
38
|
-
# @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool.
|
39
|
-
#
|
40
|
-
def initialize(opts = {})
|
41
|
-
core = LaunchDarkly::Impl::Integrations::Redis::RedisFeatureStoreCore.new(opts)
|
42
|
-
@wrapper = LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
|
43
|
-
end
|
44
|
-
|
45
|
-
#
|
46
|
-
# Default value for the `redis_url` constructor parameter; points to an instance of Redis
|
47
|
-
# running at `localhost` with its default port.
|
48
|
-
#
|
49
|
-
def self.default_redis_url
|
50
|
-
LaunchDarkly::Integrations::Redis::default_redis_url
|
51
|
-
end
|
52
|
-
|
53
|
-
#
|
54
|
-
# Default value for the `prefix` constructor parameter.
|
55
|
-
#
|
56
|
-
def self.default_prefix
|
57
|
-
LaunchDarkly::Integrations::Redis::default_prefix
|
58
|
-
end
|
59
|
-
|
60
|
-
def get(kind, key)
|
61
|
-
@wrapper.get(kind, key)
|
62
|
-
end
|
63
|
-
|
64
|
-
def all(kind)
|
65
|
-
@wrapper.all(kind)
|
66
|
-
end
|
67
|
-
|
68
|
-
def delete(kind, key, version)
|
69
|
-
@wrapper.delete(kind, key, version)
|
70
|
-
end
|
71
|
-
|
72
|
-
def init(all_data)
|
73
|
-
@wrapper.init(all_data)
|
74
|
-
end
|
75
|
-
|
76
|
-
def upsert(kind, item)
|
77
|
-
@wrapper.upsert(kind, item)
|
78
|
-
end
|
79
|
-
|
80
|
-
def initialized?
|
81
|
-
@wrapper.initialized?
|
82
|
-
end
|
83
|
-
|
84
|
-
def stop
|
85
|
-
@wrapper.stop
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require "json"
|
2
|
-
require "set"
|
3
|
-
|
4
|
-
module LaunchDarkly
|
5
|
-
# @private
|
6
|
-
class UserFilter
|
7
|
-
def initialize(config)
|
8
|
-
@all_attributes_private = config.all_attributes_private
|
9
|
-
@private_attribute_names = Set.new(config.private_attribute_names.map(&:to_sym))
|
10
|
-
end
|
11
|
-
|
12
|
-
def transform_user_props(user_props)
|
13
|
-
return nil if user_props.nil?
|
14
|
-
|
15
|
-
user_private_attrs = Set.new((user_props[:privateAttributeNames] || []).map(&:to_sym))
|
16
|
-
|
17
|
-
filtered_user_props, removed = filter_values(user_props, user_private_attrs, ALLOWED_TOP_LEVEL_KEYS, IGNORED_TOP_LEVEL_KEYS)
|
18
|
-
custom = user_props[:custom]
|
19
|
-
if !custom.nil?
|
20
|
-
filtered_user_props[:custom], removed_custom = filter_values(custom, user_private_attrs)
|
21
|
-
removed.merge(removed_custom)
|
22
|
-
end
|
23
|
-
|
24
|
-
unless removed.empty?
|
25
|
-
# note, :privateAttributeNames is what the developer sets; :privateAttrs is what we send to the server
|
26
|
-
filtered_user_props[:privateAttrs] = removed.to_a.sort.map { |s| s.to_s }
|
27
|
-
end
|
28
|
-
return filtered_user_props
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
ALLOWED_TOP_LEVEL_KEYS = Set.new([:key, :secondary, :ip, :country, :email,
|
34
|
-
:firstName, :lastName, :avatar, :name, :anonymous, :custom])
|
35
|
-
IGNORED_TOP_LEVEL_KEYS = Set.new([:custom, :key, :anonymous])
|
36
|
-
|
37
|
-
def filter_values(props, user_private_attrs, allowed_keys = [], keys_to_leave_as_is = [])
|
38
|
-
is_valid_key = lambda { |key| allowed_keys.empty? || allowed_keys.include?(key) }
|
39
|
-
removed_keys = Set.new(props.keys.select { |key|
|
40
|
-
# Note that if is_valid_key returns false, we don't explicitly *remove* the key (which would place
|
41
|
-
# it in the privateAttrs list) - we just silently drop it when we calculate filtered_hash.
|
42
|
-
is_valid_key.call(key) && !keys_to_leave_as_is.include?(key) && private_attr?(key, user_private_attrs)
|
43
|
-
})
|
44
|
-
filtered_hash = props.select { |key, value| !removed_keys.include?(key) && is_valid_key.call(key) }
|
45
|
-
[filtered_hash, removed_keys]
|
46
|
-
end
|
47
|
-
|
48
|
-
def private_attr?(name, user_private_attrs)
|
49
|
-
@all_attributes_private || @private_attribute_names.include?(name) || user_private_attrs.include?(name)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|