Skip to content

Commit 37b51da

Browse files
committed
Merge branch 'master' of github.com:launchdarkly/ruby-client
2 parents d343a88 + c38e9eb commit 37b51da

17 files changed

+1400
-187
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
machine:
5555
image: circleci/classic:latest
5656
environment:
57-
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-1.7.22 jruby-9.0.5.0"
57+
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-9.0.5.0"
5858
steps:
5959
- run: sudo apt-get -q update
6060
- run: sudo apt-get -qy install redis-server

lib/ldclient-rb.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
require "ldclient-rb/newrelic"
1010
require "ldclient-rb/stream"
1111
require "ldclient-rb/polling"
12-
require "ldclient-rb/event_serializer"
12+
require "ldclient-rb/user_filter"
13+
require "ldclient-rb/simple_lru_cache"
14+
require "ldclient-rb/non_blocking_thread_pool"
15+
require "ldclient-rb/event_summarizer"
1316
require "ldclient-rb/events"
1417
require "ldclient-rb/redis_store"
1518
require "ldclient-rb/requestor"

lib/ldclient-rb/config.rb

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ class Config
5454
# @option opts [Boolean] :send_events (true) Whether or not to send events back to LaunchDarkly.
5555
# This differs from `offline` in that it affects only the sending of client-side events, not
5656
# streaming or polling for events from the server.
57-
#
57+
# @option opts [Integer] :user_keys_capacity (1000) The number of user keys that the event processor
58+
# can remember at any one time, so that duplicate user details will not be sent in analytics events.
59+
# @option opts [Float] :user_keys_flush_interval (300) The interval in seconds at which the event
60+
# processor will reset its set of known user keys.
61+
# @option opts [Boolean] :inline_users_in_events (false) Whether to include full user details in every
62+
# analytics event. By default, events will only include the user key, except for one "index" event
63+
# that provides the full details for the user.
64+
# @option opts [Object] :update_processor An object that will receive feature flag data from LaunchDarkly.
65+
# Defaults to either the streaming or the polling processor, can be customized for tests.
5866
# @return [type] [description]
5967
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
6068
def initialize(opts = {})
@@ -76,6 +84,10 @@ def initialize(opts = {})
7684
@all_attributes_private = opts[:all_attributes_private] || false
7785
@private_attribute_names = opts[:private_attribute_names] || []
7886
@send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
87+
@user_keys_capacity = opts[:user_keys_capacity] || Config.default_user_keys_capacity
88+
@user_keys_flush_interval = opts[:user_keys_flush_interval] || Config.default_user_keys_flush_interval
89+
@inline_users_in_events = opts[:inline_users_in_events] || false
90+
@update_processor = opts[:update_processor]
7991
end
8092

8193
#
@@ -186,6 +198,26 @@ def offline?
186198
#
187199
attr_reader :send_events
188200

201+
#
202+
# The number of user keys that the event processor can remember at any one time, so that
203+
# duplicate user details will not be sent in analytics events.
204+
#
205+
attr_reader :user_keys_capacity
206+
207+
#
208+
# The interval in seconds at which the event processor will reset its set of known user keys.
209+
#
210+
attr_reader :user_keys_flush_interval
211+
212+
#
213+
# Whether to include full user details in every
214+
# analytics event. By default, events will only include the user key, except for one "index" event
215+
# that provides the full details for the user.
216+
#
217+
attr_reader :inline_users_in_events
218+
219+
attr_reader :update_processor
220+
189221
#
190222
# The default LaunchDarkly client configuration. This configuration sets
191223
# reasonable defaults for most users.
@@ -264,5 +296,13 @@ def self.default_poll_interval
264296
def self.default_send_events
265297
true
266298
end
299+
300+
def self.default_user_keys_capacity
301+
1000
302+
end
303+
304+
def self.default_user_keys_flush_interval
305+
300
306+
end
267307
end
268308
end

lib/ldclient-rb/evaluation.rb

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class EvaluationError < StandardError
114114
# generated during prerequisite evaluation. Raises EvaluationError if the flag is not well-formed
115115
# Will return nil, but not raise an exception, indicating that the rules (including fallthrough) did not match
116116
# In that case, the caller should return the default value.
117-
def evaluate(flag, user, store)
117+
def evaluate(flag, user, store, logger)
118118
if flag.nil?
119119
raise EvaluationError, "Flag does not exist"
120120
end
@@ -126,20 +126,23 @@ def evaluate(flag, user, store)
126126
events = []
127127

128128
if flag[:on]
129-
res = eval_internal(flag, user, store, events)
130-
131-
return { value: res, events: events } if !res.nil?
129+
res = eval_internal(flag, user, store, events, logger)
130+
if !res.nil?
131+
res[:events] = events
132+
return res
133+
end
132134
end
133135

134-
if !flag[:offVariation].nil? && flag[:offVariation] < flag[:variations].length
135-
value = flag[:variations][flag[:offVariation]]
136-
return { value: value, events: events }
136+
offVariation = flag[:offVariation]
137+
if !offVariation.nil? && offVariation < flag[:variations].length
138+
value = flag[:variations][offVariation]
139+
return { variation: offVariation, value: value, events: events }
137140
end
138141

139-
{ value: nil, events: events }
142+
{ variation: nil, value: nil, events: events }
140143
end
141144

142-
def eval_internal(flag, user, store, events)
145+
def eval_internal(flag, user, store, events, logger)
143146
failed_prereq = false
144147
# Evaluate prerequisites, if any
145148
(flag[:prerequisites] || []).each do |prerequisite|
@@ -149,14 +152,23 @@ def eval_internal(flag, user, store, events)
149152
failed_prereq = true
150153
else
151154
begin
152-
prereq_res = eval_internal(prereq_flag, user, store, events)
153-
variation = get_variation(prereq_flag, prerequisite[:variation])
154-
events.push(kind: "feature", key: prereq_flag[:key], value: prereq_res, version: prereq_flag[:version], prereqOf: flag[:key])
155-
if prereq_res.nil? || prereq_res != variation
155+
prereq_res = eval_internal(prereq_flag, user, store, events, logger)
156+
event = {
157+
kind: "feature",
158+
key: prereq_flag[:key],
159+
variation: prereq_res.nil? ? nil : prereq_res[:variation],
160+
value: prereq_res.nil? ? nil : prereq_res[:value],
161+
version: prereq_flag[:version],
162+
prereqOf: flag[:key],
163+
trackEvents: prereq_flag[:trackEvents],
164+
debugEventsUntilDate: prereq_flag[:debugEventsUntilDate]
165+
}
166+
events.push(event)
167+
if prereq_res.nil? || prereq_res[:variation] != prerequisite[:variation]
156168
failed_prereq = true
157169
end
158170
rescue => exn
159-
@config.logger.error { "[LDClient] Error evaluating prerequisite: #{exn.inspect}" }
171+
logger.error { "[LDClient] Error evaluating prerequisite: #{exn.inspect}" }
160172
failed_prereq = true
161173
end
162174
end
@@ -175,7 +187,9 @@ def eval_rules(flag, user, store)
175187
# Check user target matches
176188
(flag[:targets] || []).each do |target|
177189
(target[:values] || []).each do |value|
178-
return get_variation(flag, target[:variation]) if value == user[:key]
190+
if value == user[:key]
191+
return { variation: target[:variation], value: get_variation(flag, target[:variation]) }
192+
end
179193
end
180194
end
181195

@@ -245,15 +259,17 @@ def clause_match_user_no_segments(clause, user)
245259

246260
def variation_for_user(rule, user, flag)
247261
if !rule[:variation].nil? # fixed variation
248-
return get_variation(flag, rule[:variation])
262+
return { variation: rule[:variation], value: get_variation(flag, rule[:variation]) }
249263
elsif !rule[:rollout].nil? # percentage rollout
250264
rollout = rule[:rollout]
251265
bucket_by = rollout[:bucketBy].nil? ? "key" : rollout[:bucketBy]
252266
bucket = bucket_user(user, flag[:key], bucket_by, flag[:salt])
253267
sum = 0;
254268
rollout[:variations].each do |variate|
255269
sum += variate[:weight].to_f / 100000.0
256-
return get_variation(flag, variate[:variation]) if bucket < sum
270+
if bucket < sum
271+
return { variation: variate[:variation], value: get_variation(flag, variate[:variation]) }
272+
end
257273
end
258274
nil
259275
else # the rule isn't well-formed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
module LaunchDarkly
3+
EventSummary = Struct.new(:start_date, :end_date, :counters)
4+
5+
# Manages the state of summarizable information for the EventProcessor, including the
6+
# event counters and user deduplication. Note that the methods of this class are
7+
# deliberately not thread-safe; the EventProcessor is responsible for enforcing
8+
# synchronization across both the summarizer and the event queue.
9+
class EventSummarizer
10+
def initialize
11+
clear
12+
end
13+
14+
# Adds this event to our counters, if it is a type of event we need to count.
15+
def summarize_event(event)
16+
if event[:kind] == "feature"
17+
counter_key = {
18+
key: event[:key],
19+
version: event[:version],
20+
variation: event[:variation]
21+
}
22+
c = @counters[counter_key]
23+
if c.nil?
24+
@counters[counter_key] = {
25+
value: event[:value],
26+
default: event[:default],
27+
count: 1
28+
}
29+
else
30+
c[:count] = c[:count] + 1
31+
end
32+
time = event[:creationDate]
33+
if !time.nil?
34+
@start_date = time if @start_date == 0 || time < @start_date
35+
@end_date = time if time > @end_date
36+
end
37+
end
38+
end
39+
40+
# Returns a snapshot of the current summarized event data, and resets this state.
41+
def snapshot
42+
ret = EventSummary.new(@start_date, @end_date, @counters)
43+
ret
44+
end
45+
46+
def clear
47+
@start_date = 0
48+
@end_date = 0
49+
@counters = {}
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)