ldclient-rb 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a6cfa6490a0f46a455e7d65cbb9cdab695692e3
4
- data.tar.gz: 6a749bb22220adfde7323876fa7174e50da15e70
3
+ metadata.gz: aed3c4885f97dd1ad3f8dad1e9ea37ae53931fd6
4
+ data.tar.gz: a33110bc3288b9529aebc076e2fa5c6c405ea41d
5
5
  SHA512:
6
- metadata.gz: f05eb914183f31142ef03bbad0319dda44cd25cbbd2723619aca74ba70f6e2a2043db40da35480da65c5ac64bd6bc6a08afb3b8379787c58e51a7a8ed6fed9c8
7
- data.tar.gz: 53d7974ab225003f95e9fe636dff25b30c706873a13b4a1e24a33914f309ab66a8bce7438fd2382b43afc4e1ca8cc159e6b640b7cf0733ebd62c361aa42d11e2
6
+ metadata.gz: 0e8fe67e4835f5a961a5bd65b3ef383f85910253c361202b75f18dff585dbb8e210b1bc388297cd771111409bf4f0620f8aeb9197d8db17da3a644976c87ff1d
7
+ data.tar.gz: 85ad89319527662c86f18c6d472476b887f0382715577bf6fcf105c32dce3f24b51ea888a0022daff017cb9ffe4f1a3601edaa24f4d85d073de35284e478ac5b
@@ -2,18 +2,14 @@
2
2
 
3
3
  All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
- ## [5.1.0] - 2018-08-27
5
+ ## [5.2.0] - 2018-08-29
6
6
  ### Added:
7
- - The new `LDClient` method `all_flags_state()` should be used instead of `all_flags()` if you are passing flag data to the front end for use with the JavaScript SDK. It preserves some flag metadata that the front end requires in order to send analytics events correctly. Versions 2.5.0 and above of the JavaScript SDK are able to use this metadata, but the output of `all_flags_state()` will still work with older versions.
8
- - The `all_flags_state()` method also allows you to select only client-side-enabled flags to pass to the front end, by using the option `client_side_only: true`.
9
-
10
- ### Changed:
11
- - Unexpected exceptions are now logged at `ERROR` level, and exception stacktraces at `DEBUG` level. Previously, both were being logged at `WARN` level.
12
-
13
- ### Deprecated:
14
- - `LDClient.all_flags()`
7
+ - The new `LDClient` method `variation_detail` allows you to evaluate a feature flag (using the same parameters as you would for `variation`) and receive more information about how the value was calculated. This information is returned in an `EvaluationDetail` object, which contains both the result value and a "reason" object which will tell you, for instance, if the user was individually targeted for the flag or was matched by one of the flag's rules, or if the flag returned the default value due to an error.
15
8
 
9
+ ### Fixed:
10
+ - Evaluating a prerequisite feature flag did not produce an analytics event if the prerequisite flag was off.
16
11
 
12
+
17
13
  ## [5.1.0] - 2018-08-27
18
14
  ### Added:
19
15
  - The new `LDClient` method `all_flags_state()` should be used instead of `all_flags()` if you are passing flag data to the front end for use with the JavaScript SDK. It preserves some flag metadata that the front end requires in order to send analytics events correctly. Versions 2.5.0 and above of the JavaScript SDK are able to use this metadata, but the output of `all_flags_state()` will still work with older versions.
@@ -2,6 +2,37 @@ require "date"
2
2
  require "semantic"
3
3
 
4
4
  module LaunchDarkly
5
+ # An object returned by `LDClient.variation_detail`, combining the result of a flag evaluation with
6
+ # an explanation of how it was calculated.
7
+ class EvaluationDetail
8
+ def initialize(value, variation_index, reason)
9
+ @value = value
10
+ @variation_index = variation_index
11
+ @reason = reason
12
+ end
13
+
14
+ # @return [Object] The result of the flag evaluation. This will be either one of the flag's
15
+ # variations or the default value that was passed to the `variation` method.
16
+ attr_reader :value
17
+
18
+ # @return [int|nil] The index of the returned value within the flag's list of variations, e.g.
19
+ # 0 for the first variation - or `nil` if the default value was returned.
20
+ attr_reader :variation_index
21
+
22
+ # @return [Hash] An object describing the main factor that influenced the flag evaluation value.
23
+ attr_reader :reason
24
+
25
+ # @return [boolean] True if the flag evaluated to the default value rather than to one of its
26
+ # variations.
27
+ def default_value?
28
+ variation_index.nil?
29
+ end
30
+
31
+ def ==(other)
32
+ @value == other.value && @variation_index == other.variation_index && @reason == other.reason
33
+ end
34
+ end
35
+
5
36
  module Evaluation
6
37
  BUILTINS = [:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
7
38
 
@@ -107,113 +138,103 @@ module LaunchDarkly
107
138
  end
108
139
  }
109
140
 
110
- class EvaluationError < StandardError
141
+ # Used internally to hold an evaluation result and the events that were generated from prerequisites.
142
+ EvalResult = Struct.new(:detail, :events)
143
+
144
+ def error_result(errorKind, value = nil)
145
+ EvaluationDetail.new(value, nil, { kind: 'ERROR', errorKind: errorKind })
111
146
  end
112
147
 
113
- # Evaluates a feature flag, returning a hash containing the evaluation result and any events
114
- # generated during prerequisite evaluation. Raises EvaluationError if the flag is not well-formed
115
- # Will return nil, but not raise an exception, indicating that the rules (including fallthrough) did not match
116
- # In that case, the caller should return the default value.
148
+ # Evaluates a feature flag and returns an EvalResult. The result.value will be nil if the flag returns
149
+ # the default value. Error conditions produce a result with an error reason, not an exception.
117
150
  def evaluate(flag, user, store, logger)
118
- if flag.nil?
119
- raise EvaluationError, "Flag does not exist"
120
- end
121
-
122
151
  if user.nil? || user[:key].nil?
123
- raise EvaluationError, "Invalid user"
152
+ return EvalResult.new(error_result('USER_NOT_SPECIFIED'), [])
124
153
  end
125
154
 
126
155
  events = []
156
+ detail = eval_internal(flag, user, store, events, logger)
157
+ return EvalResult.new(detail, events)
158
+ end
159
+
160
+ def eval_internal(flag, user, store, events, logger)
161
+ if !flag[:on]
162
+ return get_off_value(flag, { kind: 'OFF' }, logger)
163
+ end
127
164
 
128
- if flag[:on]
129
- res = eval_internal(flag, user, store, events, logger)
130
- if !res.nil?
131
- res[:events] = events
132
- return res
165
+ prereq_failure_reason = check_prerequisites(flag, user, store, events, logger)
166
+ if !prereq_failure_reason.nil?
167
+ return get_off_value(flag, prereq_failure_reason, logger)
168
+ end
169
+
170
+ # Check user target matches
171
+ (flag[:targets] || []).each do |target|
172
+ (target[:values] || []).each do |value|
173
+ if value == user[:key]
174
+ return get_variation(flag, target[:variation], { kind: 'TARGET_MATCH' }, logger)
175
+ end
176
+ end
177
+ end
178
+
179
+ # Check custom rules
180
+ rules = flag[:rules] || []
181
+ rules.each_index do |i|
182
+ rule = rules[i]
183
+ if rule_match_user(rule, user, store)
184
+ return get_value_for_variation_or_rollout(flag, rule, user,
185
+ { kind: 'RULE_MATCH', ruleIndex: i, ruleId: rule[:id] }, logger)
133
186
  end
134
187
  end
135
188
 
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 }
189
+ # Check the fallthrough rule
190
+ if !flag[:fallthrough].nil?
191
+ return get_value_for_variation_or_rollout(flag, flag[:fallthrough], user,
192
+ { kind: 'FALLTHROUGH' }, logger)
140
193
  end
141
194
 
142
- { variation: nil, value: nil, events: events }
195
+ return EvaluationDetail.new(nil, nil, { kind: 'FALLTHROUGH' })
143
196
  end
144
197
 
145
- def eval_internal(flag, user, store, events, logger)
146
- failed_prereq = false
147
- # Evaluate prerequisites, if any
198
+ def check_prerequisites(flag, user, store, events, logger)
148
199
  (flag[:prerequisites] || []).each do |prerequisite|
149
- prereq_flag = store.get(FEATURES, prerequisite[:key])
200
+ prereq_ok = true
201
+ prereq_key = prerequisite[:key]
202
+ prereq_flag = store.get(FEATURES, prereq_key)
150
203
 
151
- if prereq_flag.nil? || !prereq_flag[:on]
152
- failed_prereq = true
204
+ if prereq_flag.nil?
205
+ logger.error { "[LDClient] Could not retrieve prerequisite flag \"#{prereq_key}\" when evaluating \"#{flag[:key]}\"" }
206
+ prereq_ok = false
153
207
  else
154
208
  begin
155
209
  prereq_res = eval_internal(prereq_flag, user, store, events, logger)
210
+ # Note that if the prerequisite flag is off, we don't consider it a match no matter what its
211
+ # off variation was. But we still need to evaluate it in order to generate an event.
212
+ if !prereq_flag[:on] || prereq_res.variation_index != prerequisite[:variation]
213
+ prereq_ok = false
214
+ end
156
215
  event = {
157
216
  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],
217
+ key: prereq_key,
218
+ variation: prereq_res.variation_index,
219
+ value: prereq_res.value,
161
220
  version: prereq_flag[:version],
162
221
  prereqOf: flag[:key],
163
222
  trackEvents: prereq_flag[:trackEvents],
164
223
  debugEventsUntilDate: prereq_flag[:debugEventsUntilDate]
165
224
  }
166
225
  events.push(event)
167
- if prereq_res.nil? || prereq_res[:variation] != prerequisite[:variation]
168
- failed_prereq = true
169
- end
170
226
  rescue => exn
171
- logger.error { "[LDClient] Error evaluating prerequisite: #{exn.inspect}" }
172
- failed_prereq = true
227
+ Util.log_exception(logger, "Error evaluating prerequisite flag \"#{prereq_key}\" for flag \"{flag[:key]}\"", exn)
228
+ prereq_ok = false
173
229
  end
174
230
  end
175
- end
176
-
177
- if failed_prereq
178
- return nil
179
- end
180
- # The prerequisites were satisfied.
181
- # Now walk through the evaluation steps and get the correct
182
- # variation index
183
- eval_rules(flag, user, store)
184
- end
185
-
186
- def eval_rules(flag, user, store)
187
- # Check user target matches
188
- (flag[:targets] || []).each do |target|
189
- (target[:values] || []).each do |value|
190
- if value == user[:key]
191
- return { variation: target[:variation], value: get_variation(flag, target[:variation]) }
192
- end
231
+ if !prereq_ok
232
+ return { kind: 'PREREQUISITE_FAILED', prerequisiteKey: prereq_key }
193
233
  end
194
234
  end
195
-
196
- # Check custom rules
197
- (flag[:rules] || []).each do |rule|
198
- return variation_for_user(rule, user, flag) if rule_match_user(rule, user, store)
199
- end
200
-
201
- # Check the fallthrough rule
202
- if !flag[:fallthrough].nil?
203
- return variation_for_user(flag[:fallthrough], user, flag)
204
- end
205
-
206
- # Not even the fallthrough matched-- return the off variation or default
207
235
  nil
208
236
  end
209
237
 
210
- def get_variation(flag, index)
211
- if index >= flag[:variations].length
212
- raise EvaluationError, "Invalid variation index"
213
- end
214
- flag[:variations][index]
215
- end
216
-
217
238
  def rule_match_user(rule, user, store)
218
239
  return false if !rule[:clauses]
219
240
 
@@ -242,9 +263,8 @@ module LaunchDarkly
242
263
  return false if val.nil?
243
264
 
244
265
  op = OPERATORS[clause[:op].to_sym]
245
-
246
266
  if op.nil?
247
- raise EvaluationError, "Unsupported operator #{clause[:op]} in evaluation"
267
+ return false
248
268
  end
249
269
 
250
270
  if val.is_a? Enumerable
@@ -257,9 +277,9 @@ module LaunchDarkly
257
277
  maybe_negate(clause, match_any(op, val, clause[:values]))
258
278
  end
259
279
 
260
- def variation_for_user(rule, user, flag)
280
+ def variation_index_for_user(flag, rule, user)
261
281
  if !rule[:variation].nil? # fixed variation
262
- return { variation: rule[:variation], value: get_variation(flag, rule[:variation]) }
282
+ return rule[:variation]
263
283
  elsif !rule[:rollout].nil? # percentage rollout
264
284
  rollout = rule[:rollout]
265
285
  bucket_by = rollout[:bucketBy].nil? ? "key" : rollout[:bucketBy]
@@ -268,12 +288,12 @@ module LaunchDarkly
268
288
  rollout[:variations].each do |variate|
269
289
  sum += variate[:weight].to_f / 100000.0
270
290
  if bucket < sum
271
- return { variation: variate[:variation], value: get_variation(flag, variate[:variation]) }
291
+ return variate[:variation]
272
292
  end
273
293
  end
274
294
  nil
275
295
  else # the rule isn't well-formed
276
- raise EvaluationError, "Rule does not define a variation or rollout"
296
+ nil
277
297
  end
278
298
  end
279
299
 
@@ -350,5 +370,31 @@ module LaunchDarkly
350
370
  end
351
371
  return false
352
372
  end
373
+
374
+ private
375
+
376
+ def get_variation(flag, index, reason, logger)
377
+ if index < 0 || index >= flag[:variations].length
378
+ logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index")
379
+ return error_result('MALFORMED_FLAG')
380
+ end
381
+ EvaluationDetail.new(flag[:variations][index], index, reason)
382
+ end
383
+
384
+ def get_off_value(flag, reason, logger)
385
+ if flag[:offVariation].nil? # off variation unspecified - return default value
386
+ return EvaluationDetail.new(nil, nil, reason)
387
+ end
388
+ get_variation(flag, flag[:offVariation], reason, logger)
389
+ end
390
+
391
+ def get_value_for_variation_or_rollout(flag, vr, user, reason, logger)
392
+ index = variation_index_for_user(flag, vr, user)
393
+ if index.nil?
394
+ logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": variation/rollout object with no variation or rollout")
395
+ return error_result('MALFORMED_FLAG')
396
+ end
397
+ return get_variation(flag, index, reason, logger)
398
+ end
353
399
  end
354
400
  end
@@ -363,6 +363,7 @@ module LaunchDarkly
363
363
  else
364
364
  out[:userKey] = event[:user].nil? ? nil : event[:user][:key]
365
365
  end
366
+ out[:reason] = event[:reason] if !event[:reason].nil?
366
367
  out
367
368
  when "identify"
368
369
  {
@@ -15,12 +15,13 @@ module LaunchDarkly
15
15
  end
16
16
 
17
17
  # Used internally to build the state map.
18
- def add_flag(flag, value, variation)
18
+ def add_flag(flag, value, variation, reason = nil)
19
19
  key = flag[:key]
20
20
  @flag_values[key] = value
21
21
  meta = { version: flag[:version], trackEvents: flag[:trackEvents] }
22
22
  meta[:variation] = variation if !variation.nil?
23
23
  meta[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
24
+ meta[:reason] = reason if !reason.nil?
24
25
  @flag_metadata[key] = meta
25
26
  end
26
27
 
@@ -115,57 +115,54 @@ module LaunchDarkly
115
115
  # @param key [String] the unique feature key for the feature flag, as shown
116
116
  # on the LaunchDarkly dashboard
117
117
  # @param user [Hash] a hash containing parameters for the end user requesting the flag
118
- # @param default=false the default value of the flag
118
+ # @param default the default value of the flag
119
119
  #
120
120
  # @return the variation to show the user, or the
121
121
  # default value if there's an an error
122
122
  def variation(key, user, default)
123
- return default if @config.offline?
124
-
125
- if !initialized?
126
- if @store.initialized?
127
- @config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
128
- else
129
- @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
130
- @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
131
- return default
132
- end
133
- end
134
-
135
- sanitize_user(user) if !user.nil?
136
- feature = @store.get(FEATURES, key)
137
-
138
- if feature.nil?
139
- @config.logger.info { "[LDClient] Unknown feature flag #{key}. Returning default value" }
140
- @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
141
- return default
142
- end
143
-
144
- unless user
145
- @config.logger.error { "[LDClient] Must specify user" }
146
- @event_processor.add_event(make_feature_event(feature, user, nil, default, default))
147
- return default
148
- end
123
+ evaluate_internal(key, user, default, false).value
124
+ end
149
125
 
150
- begin
151
- res = evaluate(feature, user, @store, @config.logger)
152
- if !res[:events].nil?
153
- res[:events].each do |event|
154
- @event_processor.add_event(event)
155
- end
156
- end
157
- value = res[:value]
158
- if value.nil?
159
- @config.logger.debug { "[LDClient] Result value is null in toggle" }
160
- value = default
161
- end
162
- @event_processor.add_event(make_feature_event(feature, user, res[:variation], value, default))
163
- return value
164
- rescue => exn
165
- Util.log_exception(@config.logger, "Error evaluating feature flag", exn)
166
- @event_processor.add_event(make_feature_event(feature, user, nil, default, default))
167
- return default
168
- end
126
+ #
127
+ # Determines the variation of a feature flag for a user, like `variation`, but also
128
+ # provides additional information about how this value was calculated.
129
+ #
130
+ # The return value of `variation_detail` is an `EvaluationDetail` object, which has
131
+ # three properties:
132
+ #
133
+ # `value`: the value that was calculated for this user (same as the return value
134
+ # of `variation`)
135
+ #
136
+ # `variation_index`: the positional index of this value in the flag, e.g. 0 for the
137
+ # first variation - or `nil` if the default value was returned
138
+ #
139
+ # `reason`: a hash describing the main reason why this value was selected. Its `:kind`
140
+ # property will be one of the following:
141
+ #
142
+ # * `'OFF'`: the flag was off and therefore returned its configured off value
143
+ # * `'FALLTHROUGH'`: the flag was on but the user did not match any targets or rules
144
+ # * `'TARGET_MATCH'`: the user key was specifically targeted for this flag
145
+ # * `'RULE_MATCH'`: the user matched one of the flag's rules; the `:ruleIndex` and
146
+ # `:ruleId` properties indicate the positional index and unique identifier of the rule
147
+ # * `'PREREQUISITE_FAILED`': the flag was considered off because it had at least one
148
+ # prerequisite flag that either was off or did not return the desired variation; the
149
+ # `:prerequisiteKey` property indicates the key of the prerequisite that failed
150
+ # * `'ERROR'`: the flag could not be evaluated, e.g. because it does not exist or due
151
+ # to an unexpected error, and therefore returned the default value; the `:errorKind`
152
+ # property describes the nature of the error, such as `'FLAG_NOT_FOUND'`
153
+ #
154
+ # The `reason` will also be included in analytics events, if you are capturing
155
+ # detailed event data for this flag.
156
+ #
157
+ # @param key [String] the unique feature key for the feature flag, as shown
158
+ # on the LaunchDarkly dashboard
159
+ # @param user [Hash] a hash containing parameters for the end user requesting the flag
160
+ # @param default the default value of the flag
161
+ #
162
+ # @return an `EvaluationDetail` object describing the result
163
+ #
164
+ def variation_detail(key, user, default)
165
+ evaluate_internal(key, user, default, true)
169
166
  end
170
167
 
171
168
  #
@@ -213,6 +210,8 @@ module LaunchDarkly
213
210
  # @param options={} [Hash] Optional parameters to control how the state is generated
214
211
  # @option options [Boolean] :client_side_only (false) True if only flags marked for use with the
215
212
  # client-side SDK should be included in the state. By default, all flags are included.
213
+ # @option options [Boolean] :with_reasons (false) True if evaluation reasons should be included
214
+ # in the state (see `variation_detail`). By default, they are not included.
216
215
  # @return [FeatureFlagsState] a FeatureFlagsState object which can be serialized to JSON
217
216
  #
218
217
  def all_flags_state(user, options={})
@@ -234,16 +233,17 @@ module LaunchDarkly
234
233
 
235
234
  state = FeatureFlagsState.new(true)
236
235
  client_only = options[:client_side_only] || false
236
+ with_reasons = options[:with_reasons] || false
237
237
  features.each do |k, f|
238
238
  if client_only && !f[:clientSide]
239
239
  next
240
240
  end
241
241
  begin
242
242
  result = evaluate(f, user, @store, @config.logger)
243
- state.add_flag(f, result[:value], result[:variation])
243
+ state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil)
244
244
  rescue => exn
245
245
  Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
246
- state.add_flag(f, nil, nil)
246
+ state.add_flag(f, nil, nil, with_reasons ? { kind: 'ERROR', errorKind: 'EXCEPTION' } : nil)
247
247
  end
248
248
  end
249
249
 
@@ -261,27 +261,83 @@ module LaunchDarkly
261
261
  @store.stop
262
262
  end
263
263
 
264
+ private
265
+
266
+ # @return [EvaluationDetail]
267
+ def evaluate_internal(key, user, default, include_reasons_in_events)
268
+ if @config.offline?
269
+ return error_result('CLIENT_NOT_READY', default)
270
+ end
271
+
272
+ if !initialized?
273
+ if @store.initialized?
274
+ @config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
275
+ else
276
+ @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
277
+ @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
278
+ return error_result('CLIENT_NOT_READY', default)
279
+ end
280
+ end
281
+
282
+ sanitize_user(user) if !user.nil?
283
+ feature = @store.get(FEATURES, key)
284
+
285
+ if feature.nil?
286
+ @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
287
+ detail = error_result('FLAG_NOT_FOUND', default)
288
+ @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user,
289
+ reason: include_reasons_in_events ? detail.reason : nil)
290
+ return detail
291
+ end
292
+
293
+ unless user
294
+ @config.logger.error { "[LDClient] Must specify user" }
295
+ detail = error_result('USER_NOT_SPECIFIED', default)
296
+ @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events))
297
+ return detail
298
+ end
299
+
300
+ begin
301
+ res = evaluate(feature, user, @store, @config.logger)
302
+ if !res.events.nil?
303
+ res.events.each do |event|
304
+ @event_processor.add_event(event)
305
+ end
306
+ end
307
+ detail = res.detail
308
+ if detail.default_value?
309
+ detail = EvaluationDetail.new(default, nil, detail.reason)
310
+ end
311
+ @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events))
312
+ return detail
313
+ rescue => exn
314
+ Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
315
+ detail = error_result('EXCEPTION', default)
316
+ @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events))
317
+ return detail
318
+ end
319
+ end
320
+
264
321
  def sanitize_user(user)
265
322
  if user[:key]
266
323
  user[:key] = user[:key].to_s
267
324
  end
268
325
  end
269
326
 
270
- def make_feature_event(flag, user, variation, value, default)
327
+ def make_feature_event(flag, user, detail, default, with_reasons)
271
328
  {
272
329
  kind: "feature",
273
330
  key: flag[:key],
274
331
  user: user,
275
- variation: variation,
276
- value: value,
332
+ variation: detail.variation_index,
333
+ value: detail.value,
277
334
  default: default,
278
335
  version: flag[:version],
279
336
  trackEvents: flag[:trackEvents],
280
- debugEventsUntilDate: flag[:debugEventsUntilDate]
337
+ debugEventsUntilDate: flag[:debugEventsUntilDate],
338
+ reason: with_reasons ? detail.reason : nil
281
339
  }
282
340
  end
283
-
284
- private :evaluate, :sanitize_user, :make_feature_event
285
341
  end
286
342
 
287
343
  #