launchdarkly-server-sdk 6.4.0 → 7.0.0

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