launchdarkly-server-sdk 6.2.5 → 7.0.0

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