launchdarkly-server-sdk 6.4.0 → 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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ldclient-rb/config.rb +102 -56
  3. data/lib/ldclient-rb/context.rb +487 -0
  4. data/lib/ldclient-rb/evaluation_detail.rb +20 -20
  5. data/lib/ldclient-rb/events.rb +77 -132
  6. data/lib/ldclient-rb/flags_state.rb +4 -4
  7. data/lib/ldclient-rb/impl/big_segments.rb +17 -17
  8. data/lib/ldclient-rb/impl/context.rb +96 -0
  9. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  10. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  11. data/lib/ldclient-rb/impl/evaluator.rb +379 -131
  12. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  13. data/lib/ldclient-rb/impl/evaluator_helpers.rb +28 -31
  14. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  15. data/lib/ldclient-rb/impl/event_sender.rb +6 -6
  16. data/lib/ldclient-rb/impl/event_summarizer.rb +12 -7
  17. data/lib/ldclient-rb/impl/event_types.rb +18 -30
  18. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
  19. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +29 -29
  20. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
  21. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +92 -12
  22. data/lib/ldclient-rb/impl/model/clause.rb +39 -0
  23. data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
  24. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
  25. data/lib/ldclient-rb/impl/model/segment.rb +126 -0
  26. data/lib/ldclient-rb/impl/model/serialization.rb +52 -12
  27. data/lib/ldclient-rb/impl/repeating_task.rb +1 -1
  28. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  29. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  30. data/lib/ldclient-rb/impl/util.rb +2 -2
  31. data/lib/ldclient-rb/in_memory_store.rb +2 -2
  32. data/lib/ldclient-rb/integrations/consul.rb +1 -1
  33. data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
  34. data/lib/ldclient-rb/integrations/file_data.rb +3 -3
  35. data/lib/ldclient-rb/integrations/redis.rb +4 -4
  36. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +218 -62
  37. data/lib/ldclient-rb/integrations/test_data.rb +16 -12
  38. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +9 -9
  39. data/lib/ldclient-rb/interfaces.rb +14 -14
  40. data/lib/ldclient-rb/ldclient.rb +94 -144
  41. data/lib/ldclient-rb/memoized_value.rb +1 -1
  42. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  43. data/lib/ldclient-rb/polling.rb +2 -2
  44. data/lib/ldclient-rb/reference.rb +274 -0
  45. data/lib/ldclient-rb/requestor.rb +5 -5
  46. data/lib/ldclient-rb/stream.rb +7 -8
  47. data/lib/ldclient-rb/util.rb +4 -19
  48. data/lib/ldclient-rb/version.rb +1 -1
  49. data/lib/ldclient-rb.rb +2 -3
  50. metadata +34 -17
  51. data/lib/ldclient-rb/file_data_source.rb +0 -23
  52. data/lib/ldclient-rb/newrelic.rb +0 -17
  53. data/lib/ldclient-rb/redis_store.rb +0 -88
  54. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,4 +1,3 @@
1
-
2
1
  module LaunchDarkly
3
2
  module Impl
4
3
  # Encapsulates the logic for percentage rollouts.
@@ -6,64 +5,64 @@ module LaunchDarkly
6
5
  # Applies either a fixed variation or a rollout for a rule (or the fallthrough rule).
7
6
  #
8
7
  # @param flag [Object] the feature flag
9
- # @param rule [Object] the rule
10
- # @param user [Object] the user properties
11
- # @return [Number] the variation index, or nil if there is an error
12
- def self.variation_index_for_user(flag, rule, user)
13
-
14
- variation = rule[:variation]
15
- return variation, false if !variation.nil? # fixed variation
16
- rollout = rule[:rollout]
8
+ # @param vr [LaunchDarkly::Impl::Model::VariationOrRollout] the variation/rollout properties
9
+ # @param context [LaunchDarkly::LDContext] the context properties
10
+ # @return [Array<[Number, nil], Boolean>] the variation index, or nil if there is an error
11
+ # @raise [InvalidReferenceException]
12
+ def self.variation_index_for_context(flag, vr, context)
13
+ variation = vr.variation
14
+ return variation, false unless variation.nil? # fixed variation
15
+ rollout = vr.rollout
17
16
  return nil, false if rollout.nil?
18
- variations = rollout[:variations]
17
+ variations = rollout.variations
19
18
  if !variations.nil? && variations.length > 0 # percentage rollout
20
- bucket_by = rollout[:bucketBy].nil? ? "key" : rollout[:bucketBy]
19
+ rollout_is_experiment = rollout.is_experiment
20
+ bucket_by = rollout_is_experiment ? nil : rollout.bucket_by
21
+ bucket_by = 'key' if bucket_by.nil?
21
22
 
22
- seed = rollout[:seed]
23
- bucket = bucket_user(user, flag[:key], bucket_by, flag[:salt], seed) # may not be present
24
- sum = 0;
23
+ seed = rollout.seed
24
+ bucket = bucket_context(context, rollout.context_kind, flag.key, bucket_by, flag.salt, seed) # may not be present
25
+ in_experiment = rollout_is_experiment && !bucket.nil?
26
+ sum = 0
25
27
  variations.each do |variate|
26
- if rollout[:kind] == "experiment" && !variate[:untracked]
27
- in_experiment = true
28
- end
29
-
30
- sum += variate[:weight].to_f / 100000.0
31
- if bucket < sum
32
- return variate[:variation], !!in_experiment
28
+ sum += variate.weight.to_f / 100000.0
29
+ if bucket.nil? || bucket < sum
30
+ return variate.variation, in_experiment && !variate.untracked
33
31
  end
34
32
  end
35
- # The user's bucket value was greater than or equal to the end of the last bucket. This could happen due
33
+ # The context's bucket value was greater than or equal to the end of the last bucket. This could happen due
36
34
  # to a rounding error, or due to the fact that we are scaling to 100000 rather than 99999, or the flag
37
35
  # data could contain buckets that don't actually add up to 100000. Rather than returning an error in
38
- # this case (or changing the scaling, which would potentially change the results for *all* users), we
39
- # will simply put the user in the last bucket.
36
+ # this case (or changing the scaling, which would potentially change the results for *all* contexts), we
37
+ # will simply put the context in the last bucket.
40
38
  last_variation = variations[-1]
41
- in_experiment = rollout[:kind] == "experiment" && !last_variation[:untracked]
42
-
43
- [last_variation[:variation], in_experiment]
39
+ [last_variation.variation, in_experiment && !last_variation.untracked]
44
40
  else # the rule isn't well-formed
45
41
  [nil, false]
46
42
  end
47
43
  end
48
44
 
49
- # Returns a user's bucket value as a floating-point value in `[0, 1)`.
45
+ # Returns a context's bucket value as a floating-point value in `[0, 1)`.
50
46
  #
51
- # @param user [Object] the user properties
47
+ # @param context [LDContext] the context properties
48
+ # @param context_kind [String, nil] the context kind to match against
52
49
  # @param key [String] the feature flag key (or segment key, if this is for a segment rule)
53
- # @param bucket_by [String|Symbol] the name of the user attribute to be used for bucketing
50
+ # @param bucket_by [String|Symbol] the name of the context attribute to be used for bucketing
54
51
  # @param salt [String] the feature flag's or segment's salt value
55
- # @return [Number] the bucket value, from 0 inclusive to 1 exclusive
56
- def self.bucket_user(user, key, bucket_by, salt, seed)
57
- return nil unless user[:key]
52
+ # @return [Float, nil] the bucket value, from 0 inclusive to 1 exclusive
53
+ # @raise [InvalidReferenceException] Raised if the clause.attribute is an invalid reference
54
+ def self.bucket_context(context, context_kind, key, bucket_by, salt, seed)
55
+ matched_context = context.individual_context(context_kind || LaunchDarkly::LDContext::KIND_DEFAULT)
56
+ return nil if matched_context.nil?
58
57
 
59
- id_hash = bucketable_string_value(EvaluatorOperators.user_value(user, bucket_by))
60
- if id_hash.nil?
61
- return 0.0
62
- end
58
+ reference = (context_kind.nil? || context_kind.empty?) ? Reference.create_literal(bucket_by) : Reference.create(bucket_by)
59
+ raise InvalidReferenceException.new(reference.error) unless reference.error.nil?
63
60
 
64
- if user[:secondary]
65
- id_hash += "." + user[:secondary].to_s
66
- end
61
+ context_value = matched_context.get_value_for_reference(reference)
62
+ return 0.0 if context_value.nil?
63
+
64
+ id_hash = bucketable_string_value(context_value)
65
+ return 0.0 if id_hash.nil?
67
66
 
68
67
  if seed
69
68
  hash_key = "%d.%s" % [seed, id_hash]
@@ -71,7 +70,7 @@ module LaunchDarkly
71
70
  hash_key = "%s.%s.%s" % [key, salt, id_hash]
72
71
  end
73
72
 
74
- hash_val = (Digest::SHA1.hexdigest(hash_key))[0..14]
73
+ hash_val = Digest::SHA1.hexdigest(hash_key)[0..14]
75
74
  hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
76
75
  end
77
76
 
@@ -6,48 +6,45 @@ require "ldclient-rb/evaluation_detail"
6
6
  module LaunchDarkly
7
7
  module Impl
8
8
  module EvaluatorHelpers
9
- def self.off_result(flag, logger = nil)
10
- pre = flag[:_preprocessed]
11
- pre ? pre.off_result : evaluation_detail_for_off_variation(flag, EvaluationReason::off, logger)
12
- end
13
-
14
- def self.target_match_result(target, flag, logger = nil)
15
- pre = target[:_preprocessed]
16
- pre ? pre.match_result : evaluation_detail_for_variation(
17
- flag, target[:variation], EvaluationReason::target_match, logger)
18
- end
19
-
20
- def self.prerequisite_failed_result(prereq, flag, logger = nil)
21
- pre = prereq[:_preprocessed]
22
- pre ? pre.failed_result : evaluation_detail_for_off_variation(
23
- flag, EvaluationReason::prerequisite_failed(prereq[:key]), logger
24
- )
25
- end
26
-
27
- def self.fallthrough_precomputed_results(flag)
28
- pre = flag[:_preprocessed]
29
- pre ? pre.fallthrough_factory : nil
30
- end
31
-
32
- def self.rule_precomputed_results(rule)
33
- pre = rule[:_preprocessed]
34
- pre ? pre.all_match_results : nil
35
- end
36
-
9
+ #
10
+ # @param flag [LaunchDarkly::Impl::Model::FeatureFlag]
11
+ # @param reason [LaunchDarkly::EvaluationReason]
12
+ #
37
13
  def self.evaluation_detail_for_off_variation(flag, reason, logger = nil)
38
- index = flag[:offVariation]
14
+ index = flag.off_variation
39
15
  index.nil? ? EvaluationDetail.new(nil, nil, reason) : evaluation_detail_for_variation(flag, index, reason, logger)
40
16
  end
41
17
 
18
+ #
19
+ # @param flag [LaunchDarkly::Impl::Model::FeatureFlag]
20
+ # @param index [Integer]
21
+ # @param reason [LaunchDarkly::EvaluationReason]
22
+ #
42
23
  def self.evaluation_detail_for_variation(flag, index, reason, logger = nil)
43
- vars = flag[:variations] || []
24
+ vars = flag.variations
44
25
  if index < 0 || index >= vars.length
45
- logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index") unless logger.nil?
26
+ logger.error("[LDClient] Data inconsistency in feature flag \"#{flag.key}\": invalid variation index") unless logger.nil?
46
27
  EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
47
28
  else
48
29
  EvaluationDetail.new(vars[index], index, reason)
49
30
  end
50
31
  end
32
+
33
+ #
34
+ # @param context [LaunchDarkly::LDContext]
35
+ # @param kind [String, nil]
36
+ # @param keys [Enumerable<String>]
37
+ # @return [Boolean]
38
+ #
39
+ def self.context_key_in_target_list(context, kind, keys)
40
+ return false unless keys.is_a? Enumerable
41
+ return false if keys.empty?
42
+
43
+ matched_context = context.individual_context(kind || LaunchDarkly::LDContext::KIND_DEFAULT)
44
+ return false if matched_context.nil?
45
+
46
+ keys.include? matched_context.key
47
+ end
51
48
  end
52
49
  end
53
50
  end
@@ -9,24 +9,24 @@ module LaunchDarkly
9
9
  # Applies an operator to produce a boolean result.
10
10
  #
11
11
  # @param op [Symbol] one of the supported LaunchDarkly operators, as a symbol
12
- # @param user_value the value of the user attribute that is referenced in the current clause (left-hand
12
+ # @param context_value the value of the context attribute that is referenced in the current clause (left-hand
13
13
  # side of the expression)
14
- # @param clause_value the constant value that `user_value` is being compared to (right-hand side of the
14
+ # @param clause_value the constant value that `context_value` is being compared to (right-hand side of the
15
15
  # expression)
16
16
  # @return [Boolean] true if the expression should be considered a match; false if it is not a match, or
17
17
  # if the values cannot be compared because they are of the wrong types, or if the operator is unknown
18
- def self.apply(op, user_value, clause_value)
18
+ def self.apply(op, context_value, clause_value)
19
19
  case op
20
20
  when :in
21
- user_value == clause_value
21
+ context_value == clause_value
22
22
  when :startsWith
23
- string_op(user_value, clause_value, lambda { |a, b| a.start_with? b })
23
+ string_op(context_value, clause_value, lambda { |a, b| a.start_with? b })
24
24
  when :endsWith
25
- string_op(user_value, clause_value, lambda { |a, b| a.end_with? b })
25
+ string_op(context_value, clause_value, lambda { |a, b| a.end_with? b })
26
26
  when :contains
27
- string_op(user_value, clause_value, lambda { |a, b| a.include? b })
27
+ string_op(context_value, clause_value, lambda { |a, b| a.include? b })
28
28
  when :matches
29
- string_op(user_value, clause_value, lambda { |a, b|
29
+ string_op(context_value, clause_value, lambda { |a, b|
30
30
  begin
31
31
  re = Regexp.new b
32
32
  !re.match(a).nil?
@@ -35,76 +35,47 @@ module LaunchDarkly
35
35
  end
36
36
  })
37
37
  when :lessThan
38
- numeric_op(user_value, clause_value, lambda { |a, b| a < b })
38
+ numeric_op(context_value, clause_value, lambda { |a, b| a < b })
39
39
  when :lessThanOrEqual
40
- numeric_op(user_value, clause_value, lambda { |a, b| a <= b })
40
+ numeric_op(context_value, clause_value, lambda { |a, b| a <= b })
41
41
  when :greaterThan
42
- numeric_op(user_value, clause_value, lambda { |a, b| a > b })
42
+ numeric_op(context_value, clause_value, lambda { |a, b| a > b })
43
43
  when :greaterThanOrEqual
44
- numeric_op(user_value, clause_value, lambda { |a, b| a >= b })
44
+ numeric_op(context_value, clause_value, lambda { |a, b| a >= b })
45
45
  when :before
46
- date_op(user_value, clause_value, lambda { |a, b| a < b })
46
+ date_op(context_value, clause_value, lambda { |a, b| a < b })
47
47
  when :after
48
- date_op(user_value, clause_value, lambda { |a, b| a > b })
48
+ date_op(context_value, clause_value, lambda { |a, b| a > b })
49
49
  when :semVerEqual
50
- semver_op(user_value, clause_value, lambda { |a, b| a == b })
50
+ semver_op(context_value, clause_value, lambda { |a, b| a == b })
51
51
  when :semVerLessThan
52
- semver_op(user_value, clause_value, lambda { |a, b| a < b })
52
+ semver_op(context_value, clause_value, lambda { |a, b| a < b })
53
53
  when :semVerGreaterThan
54
- semver_op(user_value, clause_value, lambda { |a, b| a > b })
54
+ semver_op(context_value, clause_value, lambda { |a, b| a > b })
55
55
  when :segmentMatch
56
56
  # We should never reach this; it can't be evaluated based on just two parameters, because it requires
57
- # looking up the segment from the data store. Instead, we special-case this operator in clause_match_user.
57
+ # looking up the segment from the data store. Instead, we special-case this operator in clause_match_context.
58
58
  false
59
59
  else
60
60
  false
61
61
  end
62
62
  end
63
63
 
64
- # Retrieves the value of a user attribute by name.
65
- #
66
- # Built-in attributes correspond to top-level properties in the user object. They are treated as strings and
67
- # non-string values are coerced to strings, except for `anonymous` which is meant to be a boolean if present
68
- # and is not currently coerced. This behavior is consistent with earlier versions of the Ruby SDK, but is not
69
- # guaranteed to be consistent with other SDKs, since the evaluator specification is based on the strongly-typed
70
- # SDKs where it is not possible for an attribute to have the wrong type.
71
- #
72
- # Custom attributes correspond to properties within the `custom` property, if any, and can be of any type.
73
- #
74
- # @param user [Object] the user properties
75
- # @param attribute [String|Symbol] the attribute to get, for instance `:key` or `:name` or `:some_custom_attr`
76
- # @return the attribute value, or nil if the attribute is unknown
77
- def self.user_value(user, attribute)
78
- attribute = attribute.to_sym
79
- if BUILTINS.include? attribute
80
- value = user[attribute]
81
- return nil if value.nil?
82
- (attribute == :anonymous) ? value : value.to_s
83
- elsif !user[:custom].nil?
84
- user[:custom][attribute]
85
- else
86
- nil
87
- end
88
- end
89
-
90
64
  private
91
65
 
92
- BUILTINS = Set[:key, :secondary, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
93
66
  NUMERIC_VERSION_COMPONENTS_REGEX = Regexp.new("^[0-9.]*")
94
-
95
- private_constant :BUILTINS
96
67
  private_constant :NUMERIC_VERSION_COMPONENTS_REGEX
97
68
 
98
- def self.string_op(user_value, clause_value, fn)
99
- (user_value.is_a? String) && (clause_value.is_a? String) && fn.call(user_value, clause_value)
69
+ def self.string_op(context_value, clause_value, fn)
70
+ (context_value.is_a? String) && (clause_value.is_a? String) && fn.call(context_value, clause_value)
100
71
  end
101
72
 
102
- def self.numeric_op(user_value, clause_value, fn)
103
- (user_value.is_a? Numeric) && (clause_value.is_a? Numeric) && fn.call(user_value, clause_value)
73
+ def self.numeric_op(context_value, clause_value, fn)
74
+ (context_value.is_a? Numeric) && (clause_value.is_a? Numeric) && fn.call(context_value, clause_value)
104
75
  end
105
76
 
106
- def self.date_op(user_value, clause_value, fn)
107
- ud = to_date(user_value)
77
+ def self.date_op(context_value, clause_value, fn)
78
+ ud = to_date(context_value)
108
79
  if !ud.nil?
109
80
  cd = to_date(clause_value)
110
81
  !cd.nil? && fn.call(ud, cd)
@@ -113,8 +84,8 @@ module LaunchDarkly
113
84
  end
114
85
  end
115
86
 
116
- def self.semver_op(user_value, clause_value, fn)
117
- uv = to_semver(user_value)
87
+ def self.semver_op(context_value, clause_value, fn)
88
+ uv = to_semver(context_value)
118
89
  if !uv.nil?
119
90
  cv = to_semver(clause_value)
120
91
  !cv.nil? && fn.call(uv, cv)
@@ -8,7 +8,7 @@ module LaunchDarkly
8
8
  EventSenderResult = Struct.new(:success, :must_shutdown, :time_from_server)
9
9
 
10
10
  class EventSender
11
- CURRENT_SCHEMA_VERSION = 3
11
+ CURRENT_SCHEMA_VERSION = 4
12
12
  DEFAULT_RETRY_INTERVAL = 1
13
13
 
14
14
  def initialize(sdk_key, config, http_client = nil, retry_interval = DEFAULT_RETRY_INTERVAL)
@@ -33,7 +33,7 @@ module LaunchDarkly
33
33
  begin
34
34
  http_client = @http_client_pool.acquire()
35
35
  response = nil
36
- (0..1).each do |attempt|
36
+ 2.times do |attempt|
37
37
  if attempt > 0
38
38
  @logger.warn { "[LDClient] Will retry posting events after #{@retry_interval} second" }
39
39
  sleep(@retry_interval)
@@ -43,13 +43,13 @@ module LaunchDarkly
43
43
  headers = {}
44
44
  headers["content-type"] = "application/json"
45
45
  Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| headers[k] = v }
46
- if !is_diagnostic
46
+ unless is_diagnostic
47
47
  headers["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
48
48
  headers["X-LaunchDarkly-Payload-ID"] = payload_id
49
49
  end
50
50
  response = http_client.request("POST", uri, {
51
51
  headers: headers,
52
- body: event_data
52
+ body: event_data,
53
53
  })
54
54
  rescue StandardError => exn
55
55
  @logger.warn { "[LDClient] Error sending events: #{exn.inspect}." }
@@ -60,7 +60,7 @@ module LaunchDarkly
60
60
  body = response.to_s
61
61
  if status >= 200 && status < 300
62
62
  res_time = nil
63
- if !response.headers["date"].nil?
63
+ unless response.headers["date"].nil?
64
64
  begin
65
65
  res_time = Time.httpdate(response.headers["date"])
66
66
  rescue ArgumentError
@@ -77,7 +77,7 @@ module LaunchDarkly
77
77
  end
78
78
  end
79
79
  # used up our retries
80
- return EventSenderResult.new(false, false, nil)
80
+ EventSenderResult.new(false, false, nil)
81
81
  ensure
82
82
  @http_client_pool.release(http_client)
83
83
  end
@@ -1,15 +1,16 @@
1
1
  require "ldclient-rb/impl/event_types"
2
+ require "set"
2
3
 
3
4
  module LaunchDarkly
4
5
  module Impl
5
6
  EventSummary = Struct.new(:start_date, :end_date, :counters)
6
7
 
7
- EventSummaryFlagInfo = Struct.new(:default, :versions)
8
+ EventSummaryFlagInfo = Struct.new(:default, :versions, :context_kinds)
8
9
 
9
10
  EventSummaryFlagVariationCounter = Struct.new(:value, :count)
10
11
 
11
12
  # Manages the state of summarizable information for the EventProcessor, including the
12
- # event counters and user deduplication. Note that the methods of this class are
13
+ # event counters and context deduplication. Note that the methods of this class are
13
14
  # deliberately not thread-safe; the EventProcessor is responsible for enforcing
14
15
  # synchronization across both the summarizer and the event queue.
15
16
  class EventSummarizer
@@ -22,26 +23,31 @@ module LaunchDarkly
22
23
 
23
24
  # Adds this event to our counters, if it is a type of event we need to count.
24
25
  def summarize_event(event)
25
- return if !event.is_a?(LaunchDarkly::Impl::EvalEvent)
26
+ return unless event.is_a?(LaunchDarkly::Impl::EvalEvent)
26
27
 
27
28
  counters_for_flag = @counters[event.key]
28
29
  if counters_for_flag.nil?
29
- counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new)
30
+ counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new, Set.new)
30
31
  @counters[event.key] = counters_for_flag
31
32
  end
33
+
32
34
  counters_for_flag_version = counters_for_flag.versions[event.version]
33
35
  if counters_for_flag_version.nil?
34
36
  counters_for_flag_version = Hash.new
35
37
  counters_for_flag.versions[event.version] = counters_for_flag_version
36
38
  end
39
+
40
+ counters_for_flag.context_kinds.merge(event.context.kinds)
41
+
37
42
  variation_counter = counters_for_flag_version[event.variation]
38
43
  if variation_counter.nil?
39
44
  counters_for_flag_version[event.variation] = EventSummaryFlagVariationCounter.new(event.value, 1)
40
45
  else
41
46
  variation_counter.count = variation_counter.count + 1
42
47
  end
48
+
43
49
  time = event.timestamp
44
- if !time.nil?
50
+ unless time.nil?
45
51
  @start_date = time if @start_date == 0 || time < @start_date
46
52
  @end_date = time if time > @end_date
47
53
  end
@@ -49,8 +55,7 @@ module LaunchDarkly
49
55
 
50
56
  # Returns a snapshot of the current summarized event data, and resets this state.
51
57
  def snapshot
52
- ret = EventSummary.new(@start_date, @end_date, @counters)
53
- ret
58
+ EventSummary.new(@start_date, @end_date, @counters)
54
59
  end
55
60
 
56
61
  def clear
@@ -1,20 +1,23 @@
1
1
  module LaunchDarkly
2
2
  module Impl
3
3
  class Event
4
- def initialize(timestamp, user)
4
+ # @param timestamp [Integer]
5
+ # @param context [LaunchDarkly::LDContext]
6
+ def initialize(timestamp, context)
5
7
  @timestamp = timestamp
6
- @user = user
8
+ @context = context
7
9
  end
8
10
 
11
+ # @return [Integer]
9
12
  attr_reader :timestamp
10
- attr_reader :kind
11
- attr_reader :user
13
+ # @return [LaunchDarkly::LDContext]
14
+ attr_reader :context
12
15
  end
13
16
 
14
17
  class EvalEvent < Event
15
- def initialize(timestamp, user, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
18
+ def initialize(timestamp, context, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
16
19
  track_events = false, debug_until = nil, prereq_of = nil)
17
- super(timestamp, user)
20
+ super(timestamp, context)
18
21
  @key = key
19
22
  @version = version
20
23
  @variation = variation
@@ -39,17 +42,17 @@ module LaunchDarkly
39
42
  end
40
43
 
41
44
  class IdentifyEvent < Event
42
- def initialize(timestamp, user)
43
- super(timestamp, user)
45
+ def initialize(timestamp, context)
46
+ super(timestamp, context)
44
47
  end
45
48
  end
46
49
 
47
50
  class CustomEvent < Event
48
- def initialize(timestamp, user, key, data = nil, metric_value = nil)
49
- super(timestamp, user)
51
+ def initialize(timestamp, context, key, data = nil, metric_value = nil)
52
+ super(timestamp, context)
50
53
  @key = key
51
- @data = data if !data.nil?
52
- @metric_value = metric_value if !metric_value.nil?
54
+ @data = data unless data.nil?
55
+ @metric_value = metric_value unless metric_value.nil?
53
56
  end
54
57
 
55
58
  attr_reader :key
@@ -57,30 +60,15 @@ module LaunchDarkly
57
60
  attr_reader :metric_value
58
61
  end
59
62
 
60
- class AliasEvent < Event
61
- def initialize(timestamp, key, context_kind, previous_key, previous_context_kind)
62
- super(timestamp, nil)
63
- @key = key
64
- @context_kind = context_kind
65
- @previous_key = previous_key
66
- @previous_context_kind = previous_context_kind
67
- end
68
-
69
- attr_reader :key
70
- attr_reader :context_kind
71
- attr_reader :previous_key
72
- attr_reader :previous_context_kind
73
- end
74
-
75
63
  class IndexEvent < Event
76
- def initialize(timestamp, user)
77
- super(timestamp, user)
64
+ def initialize(timestamp, context)
65
+ super(timestamp, context)
78
66
  end
79
67
  end
80
68
 
81
69
  class DebugEvent < Event
82
70
  def initialize(eval_event)
83
- super(eval_event.timestamp, eval_event.user)
71
+ super(eval_event.timestamp, eval_event.context)
84
72
  @eval_event = eval_event
85
73
  end
86
74
 
@@ -16,14 +16,14 @@ module LaunchDarkly
16
16
  end
17
17
 
18
18
  def initialize(opts)
19
- if !CONSUL_ENABLED
19
+ unless CONSUL_ENABLED
20
20
  raise RuntimeError.new("can't use Consul feature store without the 'diplomat' gem")
21
21
  end
22
22
 
23
23
  @prefix = (opts[:prefix] || LaunchDarkly::Integrations::Consul.default_prefix) + '/'
24
24
  @logger = opts[:logger] || Config.default_logger
25
- Diplomat.configuration = opts[:consul_config] if !opts[:consul_config].nil?
26
- Diplomat.configuration.url = opts[:url] if !opts[:url].nil?
25
+ Diplomat.configuration = opts[:consul_config] unless opts[:consul_config].nil?
26
+ Diplomat.configuration.url = opts[:url] unless opts[:url].nil?
27
27
  @logger.info("ConsulFeatureStore: using Consul host at #{Diplomat.configuration.url}")
28
28
  end
29
29
 
@@ -51,10 +51,10 @@ module LaunchDarkly
51
51
  unused_old_keys.each do |key|
52
52
  ops.push({ 'KV' => { 'Verb' => 'delete', 'Key' => key } })
53
53
  end
54
-
54
+
55
55
  # Now set the special key that we check in initialized_internal?
56
56
  ops.push({ 'KV' => { 'Verb' => 'set', 'Key' => inited_key, 'Value' => '' } })
57
-
57
+
58
58
  ConsulUtil.batch_operations(ops)
59
59
 
60
60
  @logger.info { "Initialized database with #{num_items} items" }
@@ -70,7 +70,7 @@ module LaunchDarkly
70
70
  results = Diplomat::Kv.get(kind_key(kind), { recurse: true }, :return)
71
71
  (results == "" ? [] : results).each do |result|
72
72
  value = result[:value]
73
- if !value.nil?
73
+ unless value.nil?
74
74
  item = Model.deserialize(kind, value)
75
75
  items_out[item[:key].to_sym] = item
76
76
  end
@@ -132,7 +132,7 @@ module LaunchDarkly
132
132
  def kind_key(kind)
133
133
  @prefix + kind[:namespace] + '/'
134
134
  end
135
-
135
+
136
136
  def inited_key
137
137
  @prefix + '$inited'
138
138
  end