launchdarkly-server-sdk 6.2.5 → 7.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/lib/ldclient-rb/config.rb +203 -43
  4. data/lib/ldclient-rb/context.rb +487 -0
  5. data/lib/ldclient-rb/evaluation_detail.rb +85 -26
  6. data/lib/ldclient-rb/events.rb +185 -146
  7. data/lib/ldclient-rb/flags_state.rb +25 -14
  8. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  9. data/lib/ldclient-rb/impl/context.rb +96 -0
  10. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  11. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  12. data/lib/ldclient-rb/impl/evaluator.rb +428 -132
  13. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  14. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  15. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  16. data/lib/ldclient-rb/impl/event_sender.rb +6 -6
  17. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  18. data/lib/ldclient-rb/impl/event_types.rb +78 -0
  19. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
  20. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +92 -28
  21. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  22. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +165 -32
  23. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  24. data/lib/ldclient-rb/impl/model/clause.rb +39 -0
  25. data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
  26. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  27. data/lib/ldclient-rb/impl/model/segment.rb +126 -0
  28. data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
  29. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  30. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  31. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  32. data/lib/ldclient-rb/impl/util.rb +62 -1
  33. data/lib/ldclient-rb/in_memory_store.rb +2 -2
  34. data/lib/ldclient-rb/integrations/consul.rb +9 -2
  35. data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
  36. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  37. data/lib/ldclient-rb/integrations/redis.rb +43 -3
  38. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +594 -0
  39. data/lib/ldclient-rb/integrations/test_data.rb +213 -0
  40. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +14 -9
  41. data/lib/ldclient-rb/integrations.rb +2 -51
  42. data/lib/ldclient-rb/interfaces.rb +151 -1
  43. data/lib/ldclient-rb/ldclient.rb +175 -133
  44. data/lib/ldclient-rb/memoized_value.rb +1 -1
  45. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  46. data/lib/ldclient-rb/polling.rb +22 -41
  47. data/lib/ldclient-rb/reference.rb +274 -0
  48. data/lib/ldclient-rb/requestor.rb +7 -7
  49. data/lib/ldclient-rb/stream.rb +9 -9
  50. data/lib/ldclient-rb/util.rb +11 -17
  51. data/lib/ldclient-rb/version.rb +1 -1
  52. data/lib/ldclient-rb.rb +2 -4
  53. metadata +49 -23
  54. data/lib/ldclient-rb/event_summarizer.rb +0 -55
  55. data/lib/ldclient-rb/file_data_source.rb +0 -314
  56. data/lib/ldclient-rb/impl/event_factory.rb +0 -126
  57. data/lib/ldclient-rb/newrelic.rb +0 -17
  58. data/lib/ldclient-rb/redis_store.rb +0 -88
  59. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,5 +1,8 @@
1
+ require "ldclient-rb/impl/context_filter"
1
2
  require "ldclient-rb/impl/diagnostic_events"
2
3
  require "ldclient-rb/impl/event_sender"
4
+ require "ldclient-rb/impl/event_summarizer"
5
+ require "ldclient-rb/impl/event_types"
3
6
  require "ldclient-rb/impl/util"
4
7
 
5
8
  require "concurrent"
@@ -18,7 +21,7 @@ require "time"
18
21
  # On a separate worker thread, EventDispatcher consumes events from the inbox. These are considered
19
22
  # "input events" because they may or may not actually be sent to LaunchDarkly; most flag evaluation
20
23
  # events are not sent, but are counted and the counters become part of a single summary event.
21
- # EventDispatcher updates those counters, creates "index" events for any users that have not been seen
24
+ # EventDispatcher updates those counters, creates "index" events for any contexts that have not been seen
22
25
  # recently, and places any events that will be sent to LaunchDarkly into the "outbox" queue.
23
26
  #
24
27
  # When it is time to flush events to LaunchDarkly, the contents of the outbox are handed off to
@@ -26,16 +29,30 @@ require "time"
26
29
  #
27
30
 
28
31
  module LaunchDarkly
29
- MAX_FLUSH_WORKERS = 5
30
- USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
31
- :avatar, :name ]
32
+ module EventProcessorMethods
33
+ def record_eval_event(
34
+ context,
35
+ key,
36
+ version = nil,
37
+ variation = nil,
38
+ value = nil,
39
+ reason = nil,
40
+ default = nil,
41
+ track_events = false,
42
+ debug_until = nil,
43
+ prereq_of = nil
44
+ )
45
+ end
32
46
 
33
- private_constant :MAX_FLUSH_WORKERS
34
- private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
47
+ def record_identify_event(context)
48
+ end
35
49
 
36
- # @private
37
- class NullEventProcessor
38
- def add_event(event)
50
+ def record_custom_event(
51
+ context,
52
+ key,
53
+ data = nil,
54
+ metric_value = nil
55
+ )
39
56
  end
40
57
 
41
58
  def flush
@@ -45,12 +62,12 @@ module LaunchDarkly
45
62
  end
46
63
  end
47
64
 
65
+ MAX_FLUSH_WORKERS = 5
66
+ private_constant :MAX_FLUSH_WORKERS
67
+
48
68
  # @private
49
- class EventMessage
50
- def initialize(event)
51
- @event = event
52
- end
53
- attr_reader :event
69
+ class NullEventProcessor
70
+ include EventProcessorMethods
54
71
  end
55
72
 
56
73
  # @private
@@ -58,7 +75,7 @@ module LaunchDarkly
58
75
  end
59
76
 
60
77
  # @private
61
- class FlushUsersMessage
78
+ class FlushContextsMessage
62
79
  end
63
80
 
64
81
  # @private
@@ -70,7 +87,7 @@ module LaunchDarkly
70
87
  def initialize
71
88
  @reply = Concurrent::Semaphore.new(0)
72
89
  end
73
-
90
+
74
91
  def completed
75
92
  @reply.release
76
93
  end
@@ -90,6 +107,8 @@ module LaunchDarkly
90
107
 
91
108
  # @private
92
109
  class EventProcessor
110
+ include EventProcessorMethods
111
+
93
112
  def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test_properties = nil)
94
113
  raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
95
114
  @logger = config.logger
@@ -98,10 +117,10 @@ module LaunchDarkly
98
117
  post_to_inbox(FlushMessage.new)
99
118
  end
100
119
  @flush_task.execute
101
- @users_flush_task = Concurrent::TimerTask.new(execution_interval: config.user_keys_flush_interval) do
102
- post_to_inbox(FlushUsersMessage.new)
120
+ @contexts_flush_task = Concurrent::TimerTask.new(execution_interval: config.context_keys_flush_interval) do
121
+ post_to_inbox(FlushContextsMessage.new)
103
122
  end
104
- @users_flush_task.execute
123
+ @contexts_flush_task.execute
105
124
  if !diagnostic_accumulator.nil?
106
125
  interval = test_properties && test_properties.has_key?(:diagnostic_recording_interval) ?
107
126
  test_properties[:diagnostic_recording_interval] :
@@ -116,16 +135,36 @@ module LaunchDarkly
116
135
  @stopped = Concurrent::AtomicBoolean.new(false)
117
136
  @inbox_full = Concurrent::AtomicBoolean.new(false)
118
137
 
119
- event_sender = test_properties && test_properties.has_key?(:event_sender) ?
120
- test_properties[:event_sender] :
121
- Impl::EventSender.new(sdk_key, config, client ? client : Util.new_http_client(config.events_uri, config))
138
+ event_sender = (test_properties || {})[:event_sender] ||
139
+ Impl::EventSender.new(sdk_key, config, client || Util.new_http_client(config.events_uri, config))
140
+
141
+ @timestamp_fn = (test_properties || {})[:timestamp_fn] || proc { Impl::Util.current_time_millis }
122
142
 
123
143
  EventDispatcher.new(@inbox, sdk_key, config, diagnostic_accumulator, event_sender)
124
144
  end
125
145
 
126
- def add_event(event)
127
- event[:creationDate] = Impl::Util.current_time_millis
128
- post_to_inbox(EventMessage.new(event))
146
+ def record_eval_event(
147
+ context,
148
+ key,
149
+ version = nil,
150
+ variation = nil,
151
+ value = nil,
152
+ reason = nil,
153
+ default = nil,
154
+ track_events = false,
155
+ debug_until = nil,
156
+ prereq_of = nil
157
+ )
158
+ post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, context, key, version, variation, value, reason,
159
+ default, track_events, debug_until, prereq_of))
160
+ end
161
+
162
+ def record_identify_event(context)
163
+ post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, context))
164
+ end
165
+
166
+ def record_custom_event(context, key, data = nil, metric_value = nil)
167
+ post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, context, key, data, metric_value))
129
168
  end
130
169
 
131
170
  def flush
@@ -137,8 +176,8 @@ module LaunchDarkly
137
176
  # final shutdown, which includes a final flush, is done synchronously
138
177
  if @stopped.make_true
139
178
  @flush_task.shutdown
140
- @users_flush_task.shutdown
141
- @diagnostic_event_task.shutdown if !@diagnostic_event_task.nil?
179
+ @contexts_flush_task.shutdown
180
+ @diagnostic_event_task.shutdown unless @diagnostic_event_task.nil?
142
181
  # Note that here we are not calling post_to_inbox, because we *do* want to wait if the inbox
143
182
  # is full; an orderly shutdown can't happen unless these messages are received.
144
183
  @inbox << FlushMessage.new
@@ -155,9 +194,11 @@ module LaunchDarkly
155
194
  sync_msg.wait_for_completion
156
195
  end
157
196
 
158
- private
197
+ private def timestamp
198
+ @timestamp_fn.call()
199
+ end
159
200
 
160
- def post_to_inbox(message)
201
+ private def post_to_inbox(message)
161
202
  begin
162
203
  @inbox.push(message, non_block=true)
163
204
  rescue ThreadError
@@ -180,13 +221,13 @@ module LaunchDarkly
180
221
  @diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
181
222
  @event_sender = event_sender
182
223
 
183
- @user_keys = SimpleLRUCacheSet.new(config.user_keys_capacity)
224
+ @context_keys = SimpleLRUCacheSet.new(config.context_keys_capacity)
184
225
  @formatter = EventOutputFormatter.new(config)
185
226
  @disabled = Concurrent::AtomicBoolean.new(false)
186
227
  @last_known_past_time = Concurrent::AtomicReference.new(0)
187
- @deduplicated_users = 0
228
+ @deduplicated_contexts = 0
188
229
  @events_in_last_batch = 0
189
-
230
+
190
231
  outbox = EventBuffer.new(config.capacity, config.logger)
191
232
  flush_workers = NonBlockingThreadPool.new(MAX_FLUSH_WORKERS)
192
233
 
@@ -209,12 +250,10 @@ module LaunchDarkly
209
250
  begin
210
251
  message = inbox.pop
211
252
  case message
212
- when EventMessage
213
- dispatch_event(message.event, outbox)
214
253
  when FlushMessage
215
254
  trigger_flush(outbox, flush_workers)
216
- when FlushUsersMessage
217
- @user_keys.clear
255
+ when FlushContextsMessage
256
+ @context_keys.clear
218
257
  when DiagnosticEventMessage
219
258
  send_and_reset_diagnostics(outbox, diagnostic_event_workers)
220
259
  when TestSyncMessage
@@ -224,6 +263,8 @@ module LaunchDarkly
224
263
  do_shutdown(flush_workers, diagnostic_event_workers)
225
264
  running = false
226
265
  message.completed
266
+ else
267
+ dispatch_event(message, outbox)
227
268
  end
228
269
  rescue => e
229
270
  Util.log_exception(@config.logger, "Unexpected error in event processor", e)
@@ -234,7 +275,7 @@ module LaunchDarkly
234
275
  def do_shutdown(flush_workers, diagnostic_event_workers)
235
276
  flush_workers.shutdown
236
277
  flush_workers.wait_for_termination
237
- if !diagnostic_event_workers.nil?
278
+ unless diagnostic_event_workers.nil?
238
279
  diagnostic_event_workers.shutdown
239
280
  diagnostic_event_workers.wait_for_termination
240
281
  end
@@ -244,7 +285,7 @@ module LaunchDarkly
244
285
  def synchronize_for_testing(flush_workers, diagnostic_event_workers)
245
286
  # Used only by unit tests. Wait until all active flush workers have finished.
246
287
  flush_workers.wait_all
247
- diagnostic_event_workers.wait_all if !diagnostic_event_workers.nil?
288
+ diagnostic_event_workers.wait_all unless diagnostic_event_workers.nil?
248
289
  end
249
290
 
250
291
  def dispatch_event(event, outbox)
@@ -257,45 +298,39 @@ module LaunchDarkly
257
298
  # the event (if tracked) and once for debugging.
258
299
  will_add_full_event = false
259
300
  debug_event = nil
260
- if event[:kind] == "feature"
261
- will_add_full_event = event[:trackEvents]
301
+ if event.is_a?(LaunchDarkly::Impl::EvalEvent)
302
+ will_add_full_event = event.track_events
262
303
  if should_debug_event(event)
263
- debug_event = event.clone
264
- debug_event[:debug] = true
304
+ debug_event = LaunchDarkly::Impl::DebugEvent.new(event)
265
305
  end
266
306
  else
267
307
  will_add_full_event = true
268
308
  end
269
309
 
270
- # For each user we haven't seen before, we add an index event - unless this is already
271
- # an identify event for that user.
272
- if !(will_add_full_event && @config.inline_users_in_events)
273
- if event.has_key?(:user) && !notice_user(event[:user]) && event[:kind] != "identify"
274
- outbox.add_event({
275
- kind: "index",
276
- creationDate: event[:creationDate],
277
- user: event[:user]
278
- })
279
- end
310
+ # For each context we haven't seen before, we add an index event - unless this is already
311
+ # an identify event for that context.
312
+ if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
313
+ outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.context))
280
314
  end
281
315
 
282
316
  outbox.add_event(event) if will_add_full_event
283
- outbox.add_event(debug_event) if !debug_event.nil?
317
+ outbox.add_event(debug_event) unless debug_event.nil?
284
318
  end
285
319
 
286
- # Add to the set of users we've noticed, and return true if the user was already known to us.
287
- def notice_user(user)
288
- if user.nil? || !user.has_key?(:key)
289
- true
290
- else
291
- known = @user_keys.add(user[:key].to_s)
292
- @deduplicated_users += 1 if known
293
- known
294
- end
320
+ #
321
+ # Add to the set of contexts we've noticed, and return true if the context
322
+ # was already known to us.
323
+ # @param context [LaunchDarkly::LDContext]
324
+ # @return [Boolean]
325
+ #
326
+ def notice_context(context)
327
+ known = @context_keys.add(context.fully_qualified_key)
328
+ @deduplicated_contexts += 1 if known
329
+ known
295
330
  end
296
331
 
297
332
  def should_debug_event(event)
298
- debug_until = event[:debugEventsUntilDate]
333
+ debug_until = event.debug_until
299
334
  if !debug_until.nil?
300
335
  last_past = @last_known_past_time.value
301
336
  debug_until > last_past && debug_until > Impl::Util.current_time_millis
@@ -309,7 +344,7 @@ module LaunchDarkly
309
344
  return
310
345
  end
311
346
 
312
- payload = outbox.get_payload
347
+ payload = outbox.get_payload
313
348
  if !payload.events.empty? || !payload.summary.counters.empty?
314
349
  count = payload.events.length + (payload.summary.counters.empty? ? 0 : 1)
315
350
  @events_in_last_batch = count
@@ -319,7 +354,7 @@ module LaunchDarkly
319
354
  events_out = @formatter.make_output_events(payload.events, payload.summary)
320
355
  result = @event_sender.send_event_data(events_out.to_json, "#{events_out.length} events", false)
321
356
  @disabled.value = true if result.must_shutdown
322
- if !result.time_from_server.nil?
357
+ unless result.time_from_server.nil?
323
358
  @last_known_past_time.value = (result.time_from_server.to_f * 1000).to_i
324
359
  end
325
360
  rescue => e
@@ -335,8 +370,8 @@ module LaunchDarkly
335
370
  def send_and_reset_diagnostics(outbox, diagnostic_event_workers)
336
371
  return if @diagnostic_accumulator.nil?
337
372
  dropped_count = outbox.get_and_clear_dropped_count
338
- event = @diagnostic_accumulator.create_periodic_event_and_reset(dropped_count, @deduplicated_users, @events_in_last_batch)
339
- @deduplicated_users = 0
373
+ event = @diagnostic_accumulator.create_periodic_event_and_reset(dropped_count, @deduplicated_contexts, @events_in_last_batch)
374
+ @deduplicated_contexts = 0
340
375
  @events_in_last_batch = 0
341
376
  send_diagnostic_event(event, diagnostic_event_workers)
342
377
  end
@@ -365,17 +400,16 @@ module LaunchDarkly
365
400
  @capacity_exceeded = false
366
401
  @dropped_events = 0
367
402
  @events = []
368
- @summarizer = EventSummarizer.new
403
+ @summarizer = LaunchDarkly::Impl::EventSummarizer.new
369
404
  end
370
405
 
371
406
  def add_event(event)
372
407
  if @events.length < @capacity
373
- @logger.debug { "[LDClient] Enqueueing event: #{event.to_json}" }
374
408
  @events.push(event)
375
409
  @capacity_exceeded = false
376
410
  else
377
411
  @dropped_events += 1
378
- if !@capacity_exceeded
412
+ unless @capacity_exceeded
379
413
  @capacity_exceeded = true
380
414
  @logger.warn { "[LDClient] Exceeded event queue capacity. Increase capacity to avoid dropping events." }
381
415
  end
@@ -387,7 +421,7 @@ module LaunchDarkly
387
421
  end
388
422
 
389
423
  def get_payload
390
- return FlushPayload.new(@events, @summarizer.snapshot)
424
+ FlushPayload.new(@events, @summarizer.snapshot)
391
425
  end
392
426
 
393
427
  def get_and_clear_dropped_count
@@ -404,113 +438,118 @@ module LaunchDarkly
404
438
 
405
439
  # @private
406
440
  class EventOutputFormatter
441
+ FEATURE_KIND = 'feature'
442
+ IDENTIFY_KIND = 'identify'
443
+ CUSTOM_KIND = 'custom'
444
+ INDEX_KIND = 'index'
445
+ DEBUG_KIND = 'debug'
446
+ SUMMARY_KIND = 'summary'
447
+
407
448
  def initialize(config)
408
- @inline_users = config.inline_users_in_events
409
- @user_filter = UserFilter.new(config)
449
+ @context_filter = LaunchDarkly::Impl::ContextFilter.new(config.all_attributes_private, config.private_attributes)
410
450
  end
411
451
 
412
452
  # Transforms events into the format used for event sending.
413
453
  def make_output_events(events, summary)
414
454
  events_out = events.map { |e| make_output_event(e) }
415
- if !summary.counters.empty?
455
+ unless summary.counters.empty?
416
456
  events_out.push(make_summary_event(summary))
417
457
  end
418
458
  events_out
419
459
  end
420
460
 
421
- private
422
-
423
- def process_user(event)
424
- filtered = @user_filter.transform_user_props(event[:user])
425
- Util.stringify_attrs(filtered, USER_ATTRS_TO_STRINGIFY_FOR_EVENTS)
426
- end
461
+ private def make_output_event(event)
462
+ case event
427
463
 
428
- def make_output_event(event)
429
- case event[:kind]
430
- when "feature"
431
- is_debug = event[:debug]
464
+ when LaunchDarkly::Impl::EvalEvent
432
465
  out = {
433
- kind: is_debug ? "debug" : "feature",
434
- creationDate: event[:creationDate],
435
- key: event[:key],
436
- value: event[:value]
466
+ kind: FEATURE_KIND,
467
+ creationDate: event.timestamp,
468
+ key: event.key,
469
+ value: event.value,
437
470
  }
438
- out[:default] = event[:default] if event.has_key?(:default)
439
- out[:variation] = event[:variation] if event.has_key?(:variation)
440
- out[:version] = event[:version] if event.has_key?(:version)
441
- out[:prereqOf] = event[:prereqOf] if event.has_key?(:prereqOf)
442
- out[:contextKind] = event[:contextKind] if event.has_key?(:contextKind)
443
- if @inline_users || is_debug
444
- out[:user] = process_user(event)
445
- else
446
- out[:userKey] = event[:user][:key]
447
- end
448
- out[:reason] = event[:reason] if !event[:reason].nil?
471
+ out[:default] = event.default unless event.default.nil?
472
+ out[:variation] = event.variation unless event.variation.nil?
473
+ out[:version] = event.version unless event.version.nil?
474
+ out[:prereqOf] = event.prereq_of unless event.prereq_of.nil?
475
+ out[:contextKeys] = event.context.keys
476
+ out[:reason] = event.reason unless event.reason.nil?
449
477
  out
450
- when "identify"
478
+
479
+ when LaunchDarkly::Impl::IdentifyEvent
451
480
  {
452
- kind: "identify",
453
- creationDate: event[:creationDate],
454
- key: event[:user][:key].to_s,
455
- user: process_user(event)
481
+ kind: IDENTIFY_KIND,
482
+ creationDate: event.timestamp,
483
+ key: event.context.fully_qualified_key,
484
+ context: @context_filter.filter(event.context),
456
485
  }
457
- when "custom"
486
+
487
+ when LaunchDarkly::Impl::CustomEvent
458
488
  out = {
459
- kind: "custom",
460
- creationDate: event[:creationDate],
461
- key: event[:key]
489
+ kind: CUSTOM_KIND,
490
+ creationDate: event.timestamp,
491
+ key: event.key,
462
492
  }
463
- out[:data] = event[:data] if event.has_key?(:data)
464
- if @inline_users
465
- out[:user] = process_user(event)
466
- else
467
- out[:userKey] = event[:user][:key]
468
- end
469
- out[:metricValue] = event[:metricValue] if event.has_key?(:metricValue)
470
- out[:contextKind] = event[:contextKind] if event.has_key?(:contextKind)
493
+ out[:data] = event.data unless event.data.nil?
494
+ out[:contextKeys] = event.context.keys
495
+ out[:metricValue] = event.metric_value unless event.metric_value.nil?
471
496
  out
472
- when "index"
497
+
498
+ when LaunchDarkly::Impl::IndexEvent
473
499
  {
474
- kind: "index",
475
- creationDate: event[:creationDate],
476
- user: process_user(event)
500
+ kind: INDEX_KIND,
501
+ creationDate: event.timestamp,
502
+ context: @context_filter.filter(event.context),
477
503
  }
504
+
505
+ when LaunchDarkly::Impl::DebugEvent
506
+ original = event.eval_event
507
+ out = {
508
+ kind: DEBUG_KIND,
509
+ creationDate: original.timestamp,
510
+ key: original.key,
511
+ context: @context_filter.filter(original.context),
512
+ value: original.value,
513
+ }
514
+ out[:default] = original.default unless original.default.nil?
515
+ out[:variation] = original.variation unless original.variation.nil?
516
+ out[:version] = original.version unless original.version.nil?
517
+ out[:prereqOf] = original.prereq_of unless original.prereq_of.nil?
518
+ out[:reason] = original.reason unless original.reason.nil?
519
+ out
520
+
478
521
  else
479
- event
522
+ nil
480
523
  end
481
524
  end
482
525
 
483
526
  # Transforms the summary data into the format used for event sending.
484
- def make_summary_event(summary)
527
+ private def make_summary_event(summary)
485
528
  flags = {}
486
- summary[:counters].each { |ckey, cval|
487
- flag = flags[ckey[:key]]
488
- if flag.nil?
489
- flag = {
490
- default: cval[:default],
491
- counters: []
492
- }
493
- flags[ckey[:key]] = flag
494
- end
495
- c = {
496
- value: cval[:value],
497
- count: cval[:count]
498
- }
499
- if !ckey[:variation].nil?
500
- c[:variation] = ckey[:variation]
501
- end
502
- if ckey[:version].nil?
503
- c[:unknown] = true
504
- else
505
- c[:version] = ckey[:version]
529
+ summary.counters.each do |flagKey, flagInfo|
530
+ counters = []
531
+ flagInfo.versions.each do |version, variations|
532
+ variations.each do |variation, counter|
533
+ c = {
534
+ value: counter.value,
535
+ count: counter.count,
536
+ }
537
+ c[:variation] = variation unless variation.nil?
538
+ if version.nil?
539
+ c[:unknown] = true
540
+ else
541
+ c[:version] = version
542
+ end
543
+ counters.push(c)
544
+ end
506
545
  end
507
- flag[:counters].push(c)
508
- }
546
+ flags[flagKey] = { default: flagInfo.default, counters: counters, contextKinds: flagInfo.context_kinds.to_a }
547
+ end
509
548
  {
510
- kind: "summary",
549
+ kind: SUMMARY_KIND,
511
550
  startDate: summary[:start_date],
512
551
  endDate: summary[:end_date],
513
- features: flags
552
+ features: flags,
514
553
  }
515
554
  end
516
555
  end
@@ -2,7 +2,7 @@ require 'json'
2
2
 
3
3
  module LaunchDarkly
4
4
  #
5
- # A snapshot of the state of all feature flags with regard to a specific user, generated by
5
+ # A snapshot of the state of all feature flags with regard to a specific context, generated by
6
6
  # calling the {LDClient#all_flags_state}. Serializing this object to JSON using
7
7
  # `JSON.generate` (or the `to_json` method) will produce the appropriate data structure for
8
8
  # bootstrapping the LaunchDarkly JavaScript client.
@@ -16,26 +16,37 @@ module LaunchDarkly
16
16
 
17
17
  # Used internally to build the state map.
18
18
  # @private
19
- def add_flag(flag, value, variation, reason = nil, details_only_if_tracked = false)
20
- key = flag[:key]
21
- @flag_values[key] = value
19
+ def add_flag(flag_state, with_reasons, details_only_if_tracked)
20
+ key = flag_state[:key]
21
+ @flag_values[key] = flag_state[:value]
22
22
  meta = {}
23
- with_details = !details_only_if_tracked || flag[:trackEvents]
24
- if !with_details && flag[:debugEventsUntilDate]
25
- with_details = flag[:debugEventsUntilDate] > Impl::Util::current_time_millis
23
+
24
+ omit_details = false
25
+ if details_only_if_tracked
26
+ if !flag_state[:trackEvents] && !flag_state[:trackReason] && !(flag_state[:debugEventsUntilDate] && flag_state[:debugEventsUntilDate] > Impl::Util::current_time_millis)
27
+ omit_details = true
28
+ end
29
+ end
30
+
31
+ reason = (!with_reasons and !flag_state[:trackReason]) ? nil : flag_state[:reason]
32
+
33
+ if !reason.nil? && !omit_details
34
+ meta[:reason] = reason
26
35
  end
27
- if with_details
28
- meta[:version] = flag[:version]
29
- meta[:reason] = reason if !reason.nil?
36
+
37
+ unless omit_details
38
+ meta[:version] = flag_state[:version]
30
39
  end
31
- meta[:variation] = variation if !variation.nil?
32
- meta[:trackEvents] = true if flag[:trackEvents]
33
- meta[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
40
+
41
+ meta[:variation] = flag_state[:variation] unless flag_state[:variation].nil?
42
+ meta[:trackEvents] = true if flag_state[:trackEvents]
43
+ meta[:trackReason] = true if flag_state[:trackReason]
44
+ meta[:debugEventsUntilDate] = flag_state[:debugEventsUntilDate] if flag_state[:debugEventsUntilDate]
34
45
  @flag_metadata[key] = meta
35
46
  end
36
47
 
37
48
  # Returns true if this object contains a valid snapshot of feature flag state, or false if the
38
- # state could not be computed (for instance, because the client was offline or there was no user).
49
+ # state could not be computed (for instance, because the client was offline or there was no context).
39
50
  def valid?
40
51
  @valid
41
52
  end