launchdarkly-server-sdk 7.0.2 → 8.4.2

Sign up to get free protection for your applications and to get access to all the features.
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)