launchdarkly-server-sdk 7.0.2 → 8.4.2
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 +9 -4
- data/lib/ldclient-rb/config.rb +50 -70
- data/lib/ldclient-rb/context.rb +65 -50
- data/lib/ldclient-rb/evaluation_detail.rb +5 -1
- data/lib/ldclient-rb/events.rb +81 -8
- data/lib/ldclient-rb/impl/big_segments.rb +1 -1
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +3 -3
- data/lib/ldclient-rb/impl/context_filter.rb +30 -9
- 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/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/event_sender.rb +1 -0
- data/lib/ldclient-rb/impl/event_types.rb +61 -3
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +19 -2
- 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/feature_flag.rb +25 -3
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/in_memory_store.rb +7 -0
- data/lib/ldclient-rb/integrations/file_data.rb +1 -1
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
- data/lib/ldclient-rb/integrations/test_data.rb +3 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
- data/lib/ldclient-rb/interfaces.rb +671 -0
- data/lib/ldclient-rb/ldclient.rb +313 -22
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/polling.rb +51 -5
- data/lib/ldclient-rb/reference.rb +11 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +91 -29
- data/lib/ldclient-rb/util.rb +89 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +1 -0
- metadata +44 -6
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
module Impl
|
5
|
+
|
6
|
+
#
|
7
|
+
# A generic mechanism for registering event listeners and broadcasting
|
8
|
+
# events to them.
|
9
|
+
#
|
10
|
+
# The SDK maintains an instance of this for each available type of listener
|
11
|
+
# (flag change, data store status, etc.). They are all intended to share a
|
12
|
+
# single executor service; notifications are submitted individually to this
|
13
|
+
# service for each listener.
|
14
|
+
#
|
15
|
+
class Broadcaster
|
16
|
+
def initialize(executor, logger)
|
17
|
+
@listeners = Concurrent::Set.new
|
18
|
+
@executor = executor
|
19
|
+
@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Register a listener to this broadcaster.
|
24
|
+
#
|
25
|
+
# @param listener [#update]
|
26
|
+
#
|
27
|
+
def add_listener(listener)
|
28
|
+
unless listener.respond_to? :update
|
29
|
+
logger.warn("listener (#{listener.class}) does not respond to :update method. ignoring as registered listener")
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
listeners.add(listener)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Removes a registered listener from this broadcaster.
|
38
|
+
#
|
39
|
+
def remove_listener(listener)
|
40
|
+
listeners.delete(listener)
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_listeners?
|
44
|
+
!listeners.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Broadcast the provided event to all registered listeners.
|
49
|
+
#
|
50
|
+
# Each listener will be notified using the broadcasters executor. This
|
51
|
+
# method is non-blocking.
|
52
|
+
#
|
53
|
+
def broadcast(event)
|
54
|
+
listeners.each do |listener|
|
55
|
+
executor.post do
|
56
|
+
begin
|
57
|
+
listener.update(event)
|
58
|
+
rescue StandardError => e
|
59
|
+
logger.error("listener (#{listener.class}) raised exception (#{e}) processing event (#{event.class})")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# @return [Concurrent::ThreadPoolExecutor]
|
69
|
+
attr_reader :executor
|
70
|
+
|
71
|
+
# @return [Logger]
|
72
|
+
attr_reader :logger
|
73
|
+
|
74
|
+
# @return [Concurrent::Set]
|
75
|
+
attr_reader :listeners
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -40,7 +40,7 @@ module LaunchDarkly
|
|
40
40
|
return ERR_KIND_NON_STRING unless kind.is_a?(String)
|
41
41
|
return ERR_KIND_CANNOT_BE_KIND if kind == "kind"
|
42
42
|
return ERR_KIND_CANNOT_BE_MULTI if kind == "multi"
|
43
|
-
|
43
|
+
ERR_KIND_INVALID_CHARS unless kind.match?(/^[\w.-]+$/)
|
44
44
|
end
|
45
45
|
|
46
46
|
#
|
@@ -51,7 +51,7 @@ module LaunchDarkly
|
|
51
51
|
#
|
52
52
|
def self.validate_key(key)
|
53
53
|
return ERR_KEY_NON_STRING unless key.is_a?(String)
|
54
|
-
|
54
|
+
ERR_KEY_EMPTY if key == ""
|
55
55
|
end
|
56
56
|
|
57
57
|
#
|
@@ -61,7 +61,7 @@ module LaunchDarkly
|
|
61
61
|
# @return [String, nil]
|
62
62
|
#
|
63
63
|
def self.validate_name(name)
|
64
|
-
|
64
|
+
ERR_NAME_NON_STRING unless name.nil? || name.is_a?(String)
|
65
65
|
end
|
66
66
|
|
67
67
|
#
|
@@ -23,14 +23,32 @@ module LaunchDarkly
|
|
23
23
|
# @return [Hash]
|
24
24
|
#
|
25
25
|
def filter(context)
|
26
|
-
|
26
|
+
internal_filter(context, false)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Return a hash representation of the provided context with attribute
|
31
|
+
# redaction applied.
|
32
|
+
#
|
33
|
+
# If a context is anonyomous, all attributes will be redacted except
|
34
|
+
# for key, kind, and anonymous.
|
35
|
+
#
|
36
|
+
# @param context [LaunchDarkly::LDContext]
|
37
|
+
# @return [Hash]
|
38
|
+
#
|
39
|
+
def filter_redact_anonymous(context)
|
40
|
+
internal_filter(context, true)
|
41
|
+
end
|
42
|
+
|
43
|
+
private def internal_filter(context, redact_anonymous)
|
44
|
+
return filter_single_context(context, true, redact_anonymous) unless context.multi_kind?
|
27
45
|
|
28
46
|
filtered = {kind: 'multi'}
|
29
47
|
(0...context.individual_context_count).each do |i|
|
30
48
|
c = context.individual_context(i)
|
31
49
|
next if c.nil?
|
32
50
|
|
33
|
-
filtered[c.kind] = filter_single_context(c, false)
|
51
|
+
filtered[c.kind] = filter_single_context(c, false, redact_anonymous)
|
34
52
|
end
|
35
53
|
|
36
54
|
filtered
|
@@ -43,22 +61,24 @@ module LaunchDarkly
|
|
43
61
|
# @param include_kind [Boolean]
|
44
62
|
# @return [Hash]
|
45
63
|
#
|
46
|
-
private def filter_single_context(context, include_kind)
|
64
|
+
private def filter_single_context(context, include_kind, redact_anonymous)
|
47
65
|
filtered = {key: context.key}
|
48
66
|
|
49
67
|
filtered[:kind] = context.kind if include_kind
|
50
|
-
|
68
|
+
|
69
|
+
anonymous = context.get_value(:anonymous)
|
70
|
+
filtered[:anonymous] = true if anonymous
|
51
71
|
|
52
72
|
redacted = []
|
53
73
|
private_attributes = @private_attributes.concat(context.private_attributes)
|
54
74
|
|
55
75
|
name = context.get_value(:name)
|
56
|
-
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted)
|
76
|
+
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted, anonymous && redact_anonymous)
|
57
77
|
filtered[:name] = name
|
58
78
|
end
|
59
79
|
|
60
80
|
context.get_custom_attribute_names.each do |attribute|
|
61
|
-
unless check_whole_attribute_private(attribute, private_attributes, redacted)
|
81
|
+
unless check_whole_attribute_private(attribute, private_attributes, redacted, anonymous && redact_anonymous)
|
62
82
|
value = context.get_value(attribute)
|
63
83
|
filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted)
|
64
84
|
end
|
@@ -75,10 +95,11 @@ module LaunchDarkly
|
|
75
95
|
# @param attribute [Symbol]
|
76
96
|
# @param private_attributes [Array<Reference>]
|
77
97
|
# @param redacted [Array<Symbol>]
|
98
|
+
# @param redact_all [Boolean]
|
78
99
|
# @return [Boolean]
|
79
100
|
#
|
80
|
-
private def check_whole_attribute_private(attribute, private_attributes, redacted)
|
81
|
-
if @all_attributes_private
|
101
|
+
private def check_whole_attribute_private(attribute, private_attributes, redacted, redact_all)
|
102
|
+
if @all_attributes_private || redact_all
|
82
103
|
redacted << attribute
|
83
104
|
return true
|
84
105
|
end
|
@@ -142,4 +163,4 @@ module LaunchDarkly
|
|
142
163
|
end
|
143
164
|
end
|
144
165
|
end
|
145
|
-
end
|
166
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require "concurrent"
|
2
|
+
require "forwardable"
|
3
|
+
require "ldclient-rb/impl/dependency_tracker"
|
4
|
+
require "ldclient-rb/interfaces"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
module LaunchDarkly
|
8
|
+
module Impl
|
9
|
+
module DataSource
|
10
|
+
class StatusProvider
|
11
|
+
include LaunchDarkly::Interfaces::DataSource::StatusProvider
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :@status_broadcaster, :add_listener, :remove_listener
|
15
|
+
|
16
|
+
def initialize(status_broadcaster, update_sink)
|
17
|
+
# @type [Broadcaster]
|
18
|
+
@status_broadcaster = status_broadcaster
|
19
|
+
# @type [UpdateSink]
|
20
|
+
@data_source_update_sink = update_sink
|
21
|
+
end
|
22
|
+
|
23
|
+
def status
|
24
|
+
@data_source_update_sink.current_status
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class UpdateSink
|
29
|
+
include LaunchDarkly::Interfaces::DataSource::UpdateSink
|
30
|
+
|
31
|
+
# @return [LaunchDarkly::Interfaces::DataSource::Status]
|
32
|
+
attr_reader :current_status
|
33
|
+
|
34
|
+
def initialize(data_store, status_broadcaster, flag_change_broadcaster)
|
35
|
+
# @type [LaunchDarkly::Interfaces::FeatureStore]
|
36
|
+
@data_store = data_store
|
37
|
+
# @type [Broadcaster]
|
38
|
+
@status_broadcaster = status_broadcaster
|
39
|
+
# @type [Broadcaster]
|
40
|
+
@flag_change_broadcaster = flag_change_broadcaster
|
41
|
+
@dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new
|
42
|
+
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
45
|
+
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
46
|
+
Time.now,
|
47
|
+
nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
def init(all_data)
|
51
|
+
old_data = nil
|
52
|
+
monitor_store_update do
|
53
|
+
if @flag_change_broadcaster.has_listeners?
|
54
|
+
old_data = {}
|
55
|
+
LaunchDarkly::ALL_KINDS.each do |kind|
|
56
|
+
old_data[kind] = @data_store.all(kind)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@data_store.init(all_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
update_full_dependency_tracker(all_data)
|
64
|
+
|
65
|
+
return if old_data.nil?
|
66
|
+
|
67
|
+
send_change_events(
|
68
|
+
compute_changed_items_for_full_data_set(old_data, all_data)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def upsert(kind, item)
|
73
|
+
monitor_store_update { @data_store.upsert(kind, item) }
|
74
|
+
|
75
|
+
# TODO(sc-197908): We only want to do this if the store successfully
|
76
|
+
# updates the record.
|
77
|
+
@dependency_tracker.update_dependencies_from(kind, item[:key], item)
|
78
|
+
if @flag_change_broadcaster.has_listeners?
|
79
|
+
affected_items = Set.new
|
80
|
+
@dependency_tracker.add_affected_items(affected_items, {kind: kind, key: item[:key]})
|
81
|
+
send_change_events(affected_items)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete(kind, key, version)
|
86
|
+
monitor_store_update { @data_store.delete(kind, key, version) }
|
87
|
+
|
88
|
+
@dependency_tracker.update_dependencies_from(kind, key, nil)
|
89
|
+
if @flag_change_broadcaster.has_listeners?
|
90
|
+
affected_items = Set.new
|
91
|
+
@dependency_tracker.add_affected_items(affected_items, {kind: kind, key: key})
|
92
|
+
send_change_events(affected_items)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_status(new_state, new_error)
|
97
|
+
return if new_state.nil?
|
98
|
+
|
99
|
+
status_to_broadcast = nil
|
100
|
+
|
101
|
+
@mutex.synchronize do
|
102
|
+
old_status = @current_status
|
103
|
+
|
104
|
+
if new_state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED && old_status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING
|
105
|
+
# See {LaunchDarkly::Interfaces::DataSource::UpdateSink#update_status} for more information
|
106
|
+
new_state = LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING
|
107
|
+
end
|
108
|
+
|
109
|
+
unless new_state == old_status.state && new_error.nil?
|
110
|
+
@current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
111
|
+
new_state,
|
112
|
+
new_state == current_status.state ? current_status.state_since : Time.now,
|
113
|
+
new_error.nil? ? current_status.last_error : new_error
|
114
|
+
)
|
115
|
+
status_to_broadcast = current_status
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@status_broadcaster.broadcast(status_to_broadcast) unless status_to_broadcast.nil?
|
120
|
+
end
|
121
|
+
|
122
|
+
private def update_full_dependency_tracker(all_data)
|
123
|
+
@dependency_tracker.reset
|
124
|
+
all_data.each do |kind, items|
|
125
|
+
items.each do |key, item|
|
126
|
+
@dependency_tracker.update_dependencies_from(kind, item.key, item)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
#
|
133
|
+
# Method to monitor updates to the store. You provide a block to update
|
134
|
+
# the store. This mthod wraps that block, catching and re-raising all
|
135
|
+
# errors, and notifying all status listeners of the error.
|
136
|
+
#
|
137
|
+
private def monitor_store_update
|
138
|
+
begin
|
139
|
+
yield
|
140
|
+
rescue => e
|
141
|
+
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR, 0, e.to_s, Time.now)
|
142
|
+
update_status(LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED, error_info)
|
143
|
+
raise
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
#
|
149
|
+
# @param [Hash] old_data
|
150
|
+
# @param [Hash] new_data
|
151
|
+
# @return [Set]
|
152
|
+
#
|
153
|
+
private def compute_changed_items_for_full_data_set(old_data, new_data)
|
154
|
+
affected_items = Set.new
|
155
|
+
|
156
|
+
LaunchDarkly::ALL_KINDS.each do |kind|
|
157
|
+
old_items = old_data[kind] || {}
|
158
|
+
new_items = new_data[kind] || {}
|
159
|
+
|
160
|
+
old_items.keys.concat(new_items.keys).each do |key|
|
161
|
+
old_item = old_items[key]
|
162
|
+
new_item = new_items[key]
|
163
|
+
|
164
|
+
next if old_item.nil? && new_item.nil?
|
165
|
+
|
166
|
+
if old_item.nil? || new_item.nil? || old_item[:version] < new_item[:version]
|
167
|
+
@dependency_tracker.add_affected_items(affected_items, {kind: kind, key: key.to_s})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
affected_items
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# @param affected_items [Set]
|
177
|
+
#
|
178
|
+
private def send_change_events(affected_items)
|
179
|
+
affected_items.each do |item|
|
180
|
+
if item[:kind] == LaunchDarkly::FEATURES
|
181
|
+
@flag_change_broadcaster.broadcast(LaunchDarkly::Interfaces::FlagChange.new(item[:key]))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require "ldclient-rb/interfaces"
|
3
|
+
|
4
|
+
module LaunchDarkly
|
5
|
+
module Impl
|
6
|
+
module DataStore
|
7
|
+
class StatusProvider
|
8
|
+
include LaunchDarkly::Interfaces::DataStore::StatusProvider
|
9
|
+
|
10
|
+
def initialize(store, update_sink)
|
11
|
+
# @type [LaunchDarkly::Impl::FeatureStoreClientWrapper]
|
12
|
+
@store = store
|
13
|
+
# @type [UpdateSink]
|
14
|
+
@update_sink = update_sink
|
15
|
+
end
|
16
|
+
|
17
|
+
def status
|
18
|
+
@update_sink.last_status.get
|
19
|
+
end
|
20
|
+
|
21
|
+
def monitoring_enabled?
|
22
|
+
@store.monitoring_enabled?
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_listener(listener)
|
26
|
+
@update_sink.broadcaster.add_listener(listener)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_listener(listener)
|
30
|
+
@update_sink.broadcaster.remove_listener(listener)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UpdateSink
|
35
|
+
include LaunchDarkly::Interfaces::DataStore::UpdateSink
|
36
|
+
|
37
|
+
# @return [LaunchDarkly::Impl::Broadcaster]
|
38
|
+
attr_reader :broadcaster
|
39
|
+
|
40
|
+
# @return [Concurrent::AtomicReference]
|
41
|
+
attr_reader :last_status
|
42
|
+
|
43
|
+
def initialize(broadcaster)
|
44
|
+
@broadcaster = broadcaster
|
45
|
+
@last_status = Concurrent::AtomicReference.new(
|
46
|
+
LaunchDarkly::Interfaces::DataStore::Status.new(true, false)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_status(status)
|
51
|
+
return if status.nil?
|
52
|
+
|
53
|
+
old_status = @last_status.get_and_set(status)
|
54
|
+
@broadcaster.broadcast(status) unless old_status == status
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module LaunchDarkly
|
2
|
+
module Impl
|
3
|
+
class DependencyTracker
|
4
|
+
def initialize
|
5
|
+
@from = {}
|
6
|
+
@to = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
# Updates the dependency graph when an item has changed.
|
11
|
+
#
|
12
|
+
# @param from_kind [Object] the changed item's kind
|
13
|
+
# @param from_key [String] the changed item's key
|
14
|
+
# @param from_item [Object] the changed item
|
15
|
+
#
|
16
|
+
def update_dependencies_from(from_kind, from_key, from_item)
|
17
|
+
from_what = { kind: from_kind, key: from_key }
|
18
|
+
updated_dependencies = DependencyTracker.compute_dependencies_from(from_kind, from_item)
|
19
|
+
|
20
|
+
old_dependency_set = @from[from_what]
|
21
|
+
unless old_dependency_set.nil?
|
22
|
+
old_dependency_set.each do |kind_and_key|
|
23
|
+
deps_to_this_old_dep = @to[kind_and_key]
|
24
|
+
deps_to_this_old_dep&.delete(from_what)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
@from[from_what] = updated_dependencies
|
29
|
+
updated_dependencies.each do |kind_and_key|
|
30
|
+
deps_to_this_new_dep = @to[kind_and_key]
|
31
|
+
if deps_to_this_new_dep.nil?
|
32
|
+
deps_to_this_new_dep = Set.new
|
33
|
+
@to[kind_and_key] = deps_to_this_new_dep
|
34
|
+
end
|
35
|
+
deps_to_this_new_dep.add(from_what)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.segment_keys_from_clauses(clauses)
|
40
|
+
clauses.flat_map do |clause|
|
41
|
+
if clause.op == :segmentMatch
|
42
|
+
clause.values.map { |value| {kind: LaunchDarkly::SEGMENTS, key: value }}
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# @param from_kind [String]
|
51
|
+
# @param from_item [LaunchDarkly::Impl::Model::FeatureFlag, LaunchDarkly::Impl::Model::Segment]
|
52
|
+
# @return [Set]
|
53
|
+
#
|
54
|
+
def self.compute_dependencies_from(from_kind, from_item)
|
55
|
+
return Set.new if from_item.nil?
|
56
|
+
|
57
|
+
if from_kind == LaunchDarkly::FEATURES
|
58
|
+
prereq_keys = from_item.prerequisites.map { |prereq| {kind: from_kind, key: prereq.key} }
|
59
|
+
segment_keys = from_item.rules.flat_map { |rule| DependencyTracker.segment_keys_from_clauses(rule.clauses) }
|
60
|
+
|
61
|
+
results = Set.new(prereq_keys)
|
62
|
+
results.merge(segment_keys)
|
63
|
+
elsif from_kind == LaunchDarkly::SEGMENTS
|
64
|
+
kind_and_keys = from_item.rules.flat_map do |rule|
|
65
|
+
DependencyTracker.segment_keys_from_clauses(rule.clauses)
|
66
|
+
end
|
67
|
+
Set.new(kind_and_keys)
|
68
|
+
else
|
69
|
+
Set.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Clear any tracked dependencies and reset the tracking state to a clean slate.
|
75
|
+
#
|
76
|
+
def reset
|
77
|
+
@from.clear
|
78
|
+
@to.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Populates the given set with the union of the initial item and all items that directly or indirectly
|
83
|
+
# depend on it (based on the current state of the dependency graph).
|
84
|
+
#
|
85
|
+
# @param items_out [Set]
|
86
|
+
# @param initial_modified_item [Object]
|
87
|
+
#
|
88
|
+
def add_affected_items(items_out, initial_modified_item)
|
89
|
+
return if items_out.include? initial_modified_item
|
90
|
+
|
91
|
+
items_out.add(initial_modified_item)
|
92
|
+
affected_items = @to[initial_modified_item]
|
93
|
+
|
94
|
+
return if affected_items.nil?
|
95
|
+
|
96
|
+
affected_items.each do |affected_item|
|
97
|
+
add_affected_items(items_out, affected_item)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module LaunchDarkly
|
2
|
+
module Impl
|
3
|
+
#
|
4
|
+
# Simple helper class for returning formatted data.
|
5
|
+
#
|
6
|
+
# The variation methods make use of the new hook support. Those methods all need to return an evaluation detail, and
|
7
|
+
# some other unstructured bit of data.
|
8
|
+
#
|
9
|
+
class EvaluationWithHookResult
|
10
|
+
#
|
11
|
+
# Return the evaluation detail that was generated as part of the evaluation.
|
12
|
+
#
|
13
|
+
# @return [LaunchDarkly::EvaluationDetail]
|
14
|
+
#
|
15
|
+
attr_reader :evaluation_detail
|
16
|
+
|
17
|
+
#
|
18
|
+
# All purpose container for additional return values from the wrapping method
|
19
|
+
#
|
20
|
+
# @return [any]
|
21
|
+
#
|
22
|
+
attr_reader :results
|
23
|
+
|
24
|
+
#
|
25
|
+
# @param evaluation_detail [LaunchDarkly::EvaluationDetail]
|
26
|
+
# @param results [any]
|
27
|
+
#
|
28
|
+
def initialize(evaluation_detail, results = nil)
|
29
|
+
@evaluation_detail = evaluation_detail
|
30
|
+
@results = results
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,23 +1,33 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module LaunchDarkly
|
2
4
|
module Impl
|
3
5
|
class Event
|
4
6
|
# @param timestamp [Integer]
|
5
7
|
# @param context [LaunchDarkly::LDContext]
|
6
|
-
|
8
|
+
# @param sampling_ratio [Integer, nil]
|
9
|
+
# @param exclude_from_summaries [Boolean]
|
10
|
+
def initialize(timestamp, context, sampling_ratio = nil, exclude_from_summaries = false)
|
7
11
|
@timestamp = timestamp
|
8
12
|
@context = context
|
13
|
+
@sampling_ratio = sampling_ratio
|
14
|
+
@exclude_from_summaries = exclude_from_summaries
|
9
15
|
end
|
10
16
|
|
11
17
|
# @return [Integer]
|
12
18
|
attr_reader :timestamp
|
13
19
|
# @return [LaunchDarkly::LDContext]
|
14
20
|
attr_reader :context
|
21
|
+
# @return [Integer, nil]
|
22
|
+
attr_reader :sampling_ratio
|
23
|
+
# @return [Boolean]
|
24
|
+
attr_reader :exclude_from_summaries
|
15
25
|
end
|
16
26
|
|
17
27
|
class EvalEvent < Event
|
18
28
|
def initialize(timestamp, context, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
|
19
|
-
track_events = false, debug_until = nil, prereq_of = nil)
|
20
|
-
super(timestamp, context)
|
29
|
+
track_events = false, debug_until = nil, prereq_of = nil, sampling_ratio = nil, exclude_from_summaries = false)
|
30
|
+
super(timestamp, context, sampling_ratio, exclude_from_summaries)
|
21
31
|
@key = key
|
22
32
|
@version = version
|
23
33
|
@variation = variation
|
@@ -41,6 +51,54 @@ module LaunchDarkly
|
|
41
51
|
attr_reader :prereq_of
|
42
52
|
end
|
43
53
|
|
54
|
+
class MigrationOpEvent < Event
|
55
|
+
#
|
56
|
+
# A migration op event represents the results of a migration-assisted read or write operation.
|
57
|
+
#
|
58
|
+
# The event includes optional measurements reporting on consistency checks, error reporting, and operation latency
|
59
|
+
# values.
|
60
|
+
#
|
61
|
+
# @param timestamp [Integer]
|
62
|
+
# @param context [LaunchDarkly::LDContext]
|
63
|
+
# @param key [string]
|
64
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag, nil]
|
65
|
+
# @param operation [Symbol]
|
66
|
+
# @param default_stage [Symbol]
|
67
|
+
# @param evaluation [LaunchDarkly::EvaluationDetail]
|
68
|
+
# @param invoked [Set]
|
69
|
+
# @param consistency_check [Boolean, nil]
|
70
|
+
# @param consistency_check_ratio [Integer, nil]
|
71
|
+
# @param errors [Set]
|
72
|
+
# @param latencies [Hash<Symbol, Float>]
|
73
|
+
#
|
74
|
+
def initialize(timestamp, context, key, flag, operation, default_stage, evaluation, invoked, consistency_check, consistency_check_ratio, errors, latencies)
|
75
|
+
super(timestamp, context)
|
76
|
+
@operation = operation
|
77
|
+
@key = key
|
78
|
+
@version = flag&.version
|
79
|
+
@sampling_ratio = flag&.sampling_ratio
|
80
|
+
@default = default_stage
|
81
|
+
@evaluation = evaluation
|
82
|
+
@consistency_check = consistency_check
|
83
|
+
@consistency_check_ratio = consistency_check.nil? ? nil : consistency_check_ratio
|
84
|
+
@invoked = invoked
|
85
|
+
@errors = errors
|
86
|
+
@latencies = latencies
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :operation
|
90
|
+
attr_reader :key
|
91
|
+
attr_reader :version
|
92
|
+
attr_reader :sampling_ratio
|
93
|
+
attr_reader :default
|
94
|
+
attr_reader :evaluation
|
95
|
+
attr_reader :consistency_check
|
96
|
+
attr_reader :consistency_check_ratio
|
97
|
+
attr_reader :invoked
|
98
|
+
attr_reader :errors
|
99
|
+
attr_reader :latencies
|
100
|
+
end
|
101
|
+
|
44
102
|
class IdentifyEvent < Event
|
45
103
|
def initialize(timestamp, context)
|
46
104
|
super(timestamp, context)
|