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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -4
  3. data/lib/ldclient-rb/config.rb +50 -70
  4. data/lib/ldclient-rb/context.rb +65 -50
  5. data/lib/ldclient-rb/evaluation_detail.rb +5 -1
  6. data/lib/ldclient-rb/events.rb +81 -8
  7. data/lib/ldclient-rb/impl/big_segments.rb +1 -1
  8. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  9. data/lib/ldclient-rb/impl/context.rb +3 -3
  10. data/lib/ldclient-rb/impl/context_filter.rb +30 -9
  11. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  12. data/lib/ldclient-rb/impl/data_store.rb +59 -0
  13. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  14. data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
  15. data/lib/ldclient-rb/impl/event_sender.rb +1 -0
  16. data/lib/ldclient-rb/impl/event_types.rb +61 -3
  17. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  18. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
  19. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
  20. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
  21. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +19 -2
  22. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  23. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  24. data/lib/ldclient-rb/impl/model/feature_flag.rb +25 -3
  25. data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
  26. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  27. data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
  28. data/lib/ldclient-rb/in_memory_store.rb +7 -0
  29. data/lib/ldclient-rb/integrations/file_data.rb +1 -1
  30. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
  31. data/lib/ldclient-rb/integrations/test_data.rb +3 -3
  32. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
  33. data/lib/ldclient-rb/interfaces.rb +671 -0
  34. data/lib/ldclient-rb/ldclient.rb +313 -22
  35. data/lib/ldclient-rb/migrations.rb +230 -0
  36. data/lib/ldclient-rb/polling.rb +51 -5
  37. data/lib/ldclient-rb/reference.rb +11 -0
  38. data/lib/ldclient-rb/requestor.rb +5 -5
  39. data/lib/ldclient-rb/stream.rb +91 -29
  40. data/lib/ldclient-rb/util.rb +89 -0
  41. data/lib/ldclient-rb/version.rb +1 -1
  42. data/lib/ldclient-rb.rb +1 -0
  43. 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
- return ERR_KIND_INVALID_CHARS unless kind.match?(/^[\w.-]+$/)
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
- return ERR_KEY_EMPTY if key == ""
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
- return ERR_NAME_NON_STRING unless name.nil? || name.is_a?(String)
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
- return filter_single_context(context, true) unless context.multi_kind?
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
- filtered[:anonymous] = true if context.get_value(:anonymous)
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
@@ -64,6 +64,7 @@ module LaunchDarkly
64
64
  begin
65
65
  res_time = Time.httpdate(response.headers["date"])
66
66
  rescue ArgumentError
67
+ # Ignored
67
68
  end
68
69
  end
69
70
  return EventSenderResult.new(true, false, res_time)
@@ -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
- def initialize(timestamp, context)
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)