optimizely-sdk 3.1.1 → 3.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: 59701c9db3bbf50b8be0a1126be80e0e870bc242
4
- data.tar.gz: 44be0cbee1131b89553f49381c7e382c842a2af9
3
+ metadata.gz: bb7a6c83f167e9c4e5166a6a4d8d4a9d4a0da2d4
4
+ data.tar.gz: 0a6b87960154256a3073c319be67779a395df7e4
5
5
  SHA512:
6
- metadata.gz: aaa90e6631800a4c9a31d372f1eaa1917e2f1b9dbcc0203eb36d8a5e8e1db0b68fc53fc744e4d4f759c13104cd21af9e77967303388e40f1f92d1ea02ac3ea6a
7
- data.tar.gz: e71aa8c67ed0cb0fbe0da318bd08335988c535e43f9462a6448061002669f78c431724131e747dbad8ad3ebd01b9e915ea00fc7b2c623919d757ea56400d0ca2
6
+ metadata.gz: 1b47592ea9b7a94640a3d3d2f876eea4578b726bfc7a644a4ef2427493b510d00e7ebd7f2475cb9fca69c36167afb5691a58465ce639e863caf960e900821e8f
7
+ data.tar.gz: d4165dbdb6a9982d9fcd6582c34acbab71a9e41a31ec44037a8aead5462f6daad1142b97ee61ab116e5433836bd283c6356c1658885a7c06513c5246f3297091
@@ -16,6 +16,9 @@
16
16
  # limitations under the License.
17
17
  #
18
18
  require_relative 'optimizely/audience'
19
+ require_relative 'optimizely/config/datafile_project_config'
20
+ require_relative 'optimizely/config_manager/http_project_config_manager'
21
+ require_relative 'optimizely/config_manager/static_project_config_manager'
19
22
  require_relative 'optimizely/decision_service'
20
23
  require_relative 'optimizely/error_handler'
21
24
  require_relative 'optimizely/event_builder'
@@ -27,13 +30,12 @@ require_relative 'optimizely/helpers/validator'
27
30
  require_relative 'optimizely/helpers/variable_type'
28
31
  require_relative 'optimizely/logger'
29
32
  require_relative 'optimizely/notification_center'
30
- require_relative 'optimizely/project_config'
31
33
 
32
34
  module Optimizely
33
35
  class Project
34
36
  attr_reader :notification_center
35
37
  # @api no-doc
36
- attr_reader :is_valid, :config, :decision_service, :error_handler,
38
+ attr_reader :config_manager, :decision_service, :error_handler,
37
39
  :event_builder, :event_dispatcher, :logger
38
40
 
39
41
  # Constructor for Projects.
@@ -45,38 +47,52 @@ module Optimizely
45
47
  # By default all exceptions will be suppressed.
46
48
  # @param user_profile_service - Optional component which provides methods to store and retreive user profiles.
47
49
  # @param skip_json_validation - Optional boolean param to skip JSON schema validation of the provided datafile.
48
-
49
- def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false, user_profile_service = nil)
50
- @is_valid = true
50
+ # @params sdk_key - Optional string uniquely identifying the datafile corresponding to project and environment combination.
51
+ # Must provide at least one of datafile or sdk_key.
52
+ # @param config_manager - Optional Responds to get_config.
53
+ # @param notification_center - Optional Instance of NotificationCenter.
54
+
55
+ def initialize(
56
+ datafile = nil,
57
+ event_dispatcher = nil,
58
+ logger = nil,
59
+ error_handler = nil,
60
+ skip_json_validation = false,
61
+ user_profile_service = nil,
62
+ sdk_key = nil,
63
+ config_manager = nil,
64
+ notification_center = nil
65
+ )
51
66
  @logger = logger || NoOpLogger.new
52
67
  @error_handler = error_handler || NoOpErrorHandler.new
53
68
  @event_dispatcher = event_dispatcher || EventDispatcher.new
54
69
  @user_profile_service = user_profile_service
55
70
 
56
71
  begin
57
- validate_instantiation_options(datafile, skip_json_validation)
72
+ validate_instantiation_options
58
73
  rescue InvalidInputError => e
59
- @is_valid = false
60
74
  @logger = SimpleLogger.new
61
75
  @logger.log(Logger::ERROR, e.message)
62
- return
63
- end
64
-
65
- begin
66
- @config = ProjectConfig.new(datafile, @logger, @error_handler)
67
- rescue StandardError => e
68
- @is_valid = false
69
- @logger = SimpleLogger.new
70
- error_msg = e.class == InvalidDatafileVersionError ? e.message : InvalidInputError.new('datafile').message
71
- error_to_handle = e.class == InvalidDatafileVersionError ? InvalidDatafileVersionError : InvalidInputError
72
- @logger.log(Logger::ERROR, error_msg)
73
- @error_handler.handle_error error_to_handle
74
- return
75
76
  end
76
77
 
77
- @decision_service = DecisionService.new(@config, @user_profile_service)
78
- @event_builder = EventBuilder.new(@config, @logger)
79
- @notification_center = NotificationCenter.new(@logger, @error_handler)
78
+ @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
79
+
80
+ @config_manager = if config_manager.respond_to?(:get_config)
81
+ config_manager
82
+ elsif sdk_key
83
+ HTTPProjectConfigManager.new(
84
+ sdk_key: sdk_key,
85
+ datafile: datafile,
86
+ logger: @logger,
87
+ error_handler: @error_handler,
88
+ skip_json_validation: skip_json_validation,
89
+ notification_center: @notification_center
90
+ )
91
+ else
92
+ StaticProjectConfigManager.new(datafile, @logger, @error_handler, skip_json_validation)
93
+ end
94
+ @decision_service = DecisionService.new(@logger, @user_profile_service)
95
+ @event_builder = EventBuilder.new(@logger)
80
96
  end
81
97
 
82
98
  # Buckets visitor and sends impression event to Optimizely.
@@ -89,8 +105,8 @@ module Optimizely
89
105
  # @return [nil] if experiment is not Running, if user is not in experiment, or if datafile is invalid.
90
106
 
91
107
  def activate(experiment_key, user_id, attributes = nil)
92
- unless @is_valid
93
- @logger.log(Logger::ERROR, InvalidDatafileError.new('activate').message)
108
+ unless is_valid
109
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('activate').message)
94
110
  return nil
95
111
  end
96
112
 
@@ -101,7 +117,9 @@ module Optimizely
101
117
  }, @logger, Logger::ERROR
102
118
  )
103
119
 
104
- variation_key = get_variation(experiment_key, user_id, attributes)
120
+ config = project_config
121
+
122
+ variation_key = get_variation_with_config(experiment_key, user_id, attributes, config)
105
123
 
106
124
  if variation_key.nil?
107
125
  @logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
@@ -109,8 +127,8 @@ module Optimizely
109
127
  end
110
128
 
111
129
  # Create and dispatch impression event
112
- experiment = @config.get_experiment_from_key(experiment_key)
113
- send_impression(experiment, variation_key, user_id, attributes)
130
+ experiment = config.get_experiment_from_key(experiment_key)
131
+ send_impression(config, experiment, variation_key, user_id, attributes)
114
132
 
115
133
  variation_key
116
134
  end
@@ -125,8 +143,8 @@ module Optimizely
125
143
  # @return [nil] if experiment is not Running, if user is not in experiment, or if datafile is invalid.
126
144
 
127
145
  def get_variation(experiment_key, user_id, attributes = nil)
128
- unless @is_valid
129
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_variation').message)
146
+ unless is_valid
147
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_variation').message)
130
148
  return nil
131
149
  end
132
150
 
@@ -137,30 +155,9 @@ module Optimizely
137
155
  }, @logger, Logger::ERROR
138
156
  )
139
157
 
140
- experiment = @config.get_experiment_from_key(experiment_key)
141
- return nil if experiment.nil?
142
-
143
- unless user_inputs_valid?(attributes)
144
- @logger.log(Logger::INFO, "Not activating user '#{user_id}.")
145
- return nil
146
- end
147
-
148
- variation_id = @decision_service.get_variation(experiment_key, user_id, attributes)
149
- variation = @config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
150
- variation_key = variation['key'] if variation
151
- decision_notification_type = if @config.feature_experiment?(experiment['id'])
152
- Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_TEST']
153
- else
154
- Helpers::Constants::DECISION_NOTIFICATION_TYPES['AB_TEST']
155
- end
156
- @notification_center.send_notifications(
157
- NotificationCenter::NOTIFICATION_TYPES[:DECISION],
158
- decision_notification_type, user_id, (attributes || {}),
159
- experiment_key: experiment_key,
160
- variation_key: variation_key
161
- )
158
+ config = project_config
162
159
 
163
- variation_key
160
+ get_variation_with_config(experiment_key, user_id, attributes, config)
164
161
  end
165
162
 
166
163
  # Force a user into a variation for a given experiment.
@@ -173,7 +170,18 @@ module Optimizely
173
170
  # @return [Boolean] indicates if the set completed successfully.
174
171
 
175
172
  def set_forced_variation(experiment_key, user_id, variation_key)
176
- @config.set_forced_variation(experiment_key, user_id, variation_key)
173
+ unless is_valid
174
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('set_forced_variation').message)
175
+ return nil
176
+ end
177
+
178
+ input_values = {experiment_key: experiment_key, user_id: user_id}
179
+ input_values[:variation_key] = variation_key unless variation_key.nil?
180
+ return false unless Optimizely::Helpers::Validator.inputs_valid?(input_values, @logger, Logger::ERROR)
181
+
182
+ config = project_config
183
+
184
+ @decision_service.set_forced_variation(config, experiment_key, user_id, variation_key)
177
185
  end
178
186
 
179
187
  # Gets the forced variation for a given user and experiment.
@@ -184,8 +192,22 @@ module Optimizely
184
192
  # @return [String] The forced variation key.
185
193
 
186
194
  def get_forced_variation(experiment_key, user_id)
195
+ unless is_valid
196
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_forced_variation').message)
197
+ return nil
198
+ end
199
+
200
+ return nil unless Optimizely::Helpers::Validator.inputs_valid?(
201
+ {
202
+ experiment_key: experiment_key,
203
+ user_id: user_id
204
+ }, @logger, Logger::ERROR
205
+ )
206
+
207
+ config = project_config
208
+
187
209
  forced_variation_key = nil
188
- forced_variation = @config.get_forced_variation(experiment_key, user_id)
210
+ forced_variation = @decision_service.get_forced_variation(config, experiment_key, user_id)
189
211
  forced_variation_key = forced_variation['key'] if forced_variation
190
212
 
191
213
  forced_variation_key
@@ -199,8 +221,8 @@ module Optimizely
199
221
  # @param event_tags - Hash representing metadata associated with the event.
200
222
 
201
223
  def track(event_key, user_id, attributes = nil, event_tags = nil)
202
- unless @is_valid
203
- @logger.log(Logger::ERROR, InvalidDatafileError.new('track').message)
224
+ unless is_valid
225
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('track').message)
204
226
  return nil
205
227
  end
206
228
 
@@ -213,14 +235,16 @@ module Optimizely
213
235
 
214
236
  return nil unless user_inputs_valid?(attributes, event_tags)
215
237
 
216
- event = @config.get_event_from_key(event_key)
238
+ config = project_config
239
+
240
+ event = config.get_event_from_key(event_key)
217
241
  unless event
218
- @config.logger.log(Logger::INFO, "Not tracking user '#{user_id}' for event '#{event_key}'.")
242
+ @logger.log(Logger::INFO, "Not tracking user '#{user_id}' for event '#{event_key}'.")
219
243
  return nil
220
244
  end
221
245
 
222
- conversion_event = @event_builder.create_conversion_event(event, user_id, attributes, event_tags)
223
- @config.logger.log(Logger::INFO, "Tracking event '#{event_key}' for user '#{user_id}'.")
246
+ conversion_event = @event_builder.create_conversion_event(config, event, user_id, attributes, event_tags)
247
+ @logger.log(Logger::INFO, "Tracking event '#{event_key}' for user '#{user_id}'.")
224
248
  @logger.log(Logger::INFO,
225
249
  "Dispatching conversion event to URL #{conversion_event.url} with params #{conversion_event.params}.")
226
250
  begin
@@ -248,8 +272,8 @@ module Optimizely
248
272
  # @return [False] if the feature is not found.
249
273
 
250
274
  def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
251
- unless @is_valid
252
- @logger.log(Logger::ERROR, InvalidDatafileError.new('is_feature_enabled').message)
275
+ unless is_valid
276
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('is_feature_enabled').message)
253
277
  return false
254
278
  end
255
279
 
@@ -262,13 +286,15 @@ module Optimizely
262
286
 
263
287
  return false unless user_inputs_valid?(attributes)
264
288
 
265
- feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
289
+ config = project_config
290
+
291
+ feature_flag = config.get_feature_flag_from_key(feature_flag_key)
266
292
  unless feature_flag
267
293
  @logger.log(Logger::ERROR, "No feature flag was found for key '#{feature_flag_key}'.")
268
294
  return false
269
295
  end
270
296
 
271
- decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
297
+ decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
272
298
 
273
299
  feature_enabled = false
274
300
  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
@@ -282,7 +308,7 @@ module Optimizely
282
308
  variation_key: variation['key']
283
309
  }
284
310
  # Send event if Decision came from an experiment.
285
- send_impression(decision.experiment, variation['key'], user_id, attributes)
311
+ send_impression(config, decision.experiment, variation['key'], user_id, attributes)
286
312
  else
287
313
  @logger.log(Logger::DEBUG,
288
314
  "The user '#{user_id}' is not being experimented on in feature '#{feature_flag_key}'.")
@@ -318,9 +344,8 @@ module Optimizely
318
344
 
319
345
  def get_enabled_features(user_id, attributes = nil)
320
346
  enabled_features = []
321
-
322
- unless @is_valid
323
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_enabled_features').message)
347
+ unless is_valid
348
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_enabled_features').message)
324
349
  return enabled_features
325
350
  end
326
351
 
@@ -332,7 +357,9 @@ module Optimizely
332
357
 
333
358
  return enabled_features unless user_inputs_valid?(attributes)
334
359
 
335
- @config.feature_flags.each do |feature|
360
+ config = project_config
361
+
362
+ config.feature_flags.each do |feature|
336
363
  enabled_features.push(feature['key']) if is_feature_enabled(
337
364
  feature['key'],
338
365
  user_id,
@@ -353,11 +380,10 @@ module Optimizely
353
380
  # @return [nil] if the feature flag or variable are not found.
354
381
 
355
382
  def get_feature_variable_string(feature_flag_key, variable_key, user_id, attributes = nil)
356
- unless @is_valid
357
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_string').message)
383
+ unless is_valid
384
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_string').message)
358
385
  return nil
359
386
  end
360
-
361
387
  variable_value = get_feature_variable_for_type(
362
388
  feature_flag_key,
363
389
  variable_key,
@@ -380,8 +406,8 @@ module Optimizely
380
406
  # @return [nil] if the feature flag or variable are not found.
381
407
 
382
408
  def get_feature_variable_boolean(feature_flag_key, variable_key, user_id, attributes = nil)
383
- unless @is_valid
384
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_boolean').message)
409
+ unless is_valid
410
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_boolean').message)
385
411
  return nil
386
412
  end
387
413
 
@@ -407,8 +433,8 @@ module Optimizely
407
433
  # @return [nil] if the feature flag or variable are not found.
408
434
 
409
435
  def get_feature_variable_double(feature_flag_key, variable_key, user_id, attributes = nil)
410
- unless @is_valid
411
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_double').message)
436
+ unless is_valid
437
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_double').message)
412
438
  return nil
413
439
  end
414
440
 
@@ -434,10 +460,11 @@ module Optimizely
434
460
  # @return [nil] if the feature flag or variable are not found.
435
461
 
436
462
  def get_feature_variable_integer(feature_flag_key, variable_key, user_id, attributes = nil)
437
- unless @is_valid
438
- @logger.log(Logger::ERROR, InvalidDatafileError.new('get_feature_variable_integer').message)
463
+ unless is_valid
464
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_integer').message)
439
465
  return nil
440
466
  end
467
+
441
468
  variable_value = get_feature_variable_for_type(
442
469
  feature_flag_key,
443
470
  variable_key,
@@ -449,8 +476,46 @@ module Optimizely
449
476
  variable_value
450
477
  end
451
478
 
479
+ def is_valid
480
+ config = project_config
481
+ config.is_a?(Optimizely::ProjectConfig)
482
+ end
483
+
452
484
  private
453
485
 
486
+ def get_variation_with_config(experiment_key, user_id, attributes, config)
487
+ # Gets variation where visitor will be bucketed.
488
+ #
489
+ # experiment_key - Experiment for which visitor variation needs to be determined.
490
+ # user_id - String ID for user.
491
+ # attributes - Hash representing user attributes.
492
+ # config - Instance of DatfileProjectConfig
493
+ #
494
+ # Returns [variation key] where visitor will be bucketed.
495
+ # Returns [nil] if experiment is not Running, if user is not in experiment, or if datafile is invalid.
496
+ experiment = config.get_experiment_from_key(experiment_key)
497
+ return nil if experiment.nil?
498
+
499
+ return nil unless user_inputs_valid?(attributes)
500
+
501
+ variation_id = @decision_service.get_variation(config, experiment_key, user_id, attributes)
502
+ variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
503
+ variation_key = variation['key'] if variation
504
+ decision_notification_type = if config.feature_experiment?(experiment['id'])
505
+ Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_TEST']
506
+ else
507
+ Helpers::Constants::DECISION_NOTIFICATION_TYPES['AB_TEST']
508
+ end
509
+ @notification_center.send_notifications(
510
+ NotificationCenter::NOTIFICATION_TYPES[:DECISION],
511
+ decision_notification_type, user_id, (attributes || {}),
512
+ experiment_key: experiment_key,
513
+ variation_key: variation_key
514
+ )
515
+
516
+ variation_key
517
+ end
518
+
454
519
  def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type, user_id, attributes = nil)
455
520
  # Get the variable value for the given feature variable and cast it to the specified type
456
521
  # The default value is returned if the feature flag is not enabled for the user.
@@ -477,15 +542,17 @@ module Optimizely
477
542
 
478
543
  return nil unless user_inputs_valid?(attributes)
479
544
 
480
- feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
545
+ config = project_config
546
+
547
+ feature_flag = config.get_feature_flag_from_key(feature_flag_key)
481
548
  unless feature_flag
482
549
  @logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
483
550
  return nil
484
551
  end
485
552
 
486
- variable = @config.get_feature_variable(feature_flag, variable_key)
553
+ variable = config.get_feature_variable(feature_flag, variable_key)
487
554
 
488
- # Error message logged in ProjectConfig- get_feature_flag_from_key
555
+ # Error message logged in DatafileProjectConfig- get_feature_flag_from_key
489
556
  return nil if variable.nil?
490
557
 
491
558
  feature_enabled = false
@@ -496,7 +563,7 @@ module Optimizely
496
563
  return nil
497
564
  else
498
565
  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
499
- decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
566
+ decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
500
567
  variable_value = variable['defaultValue']
501
568
  if decision
502
569
  variation = decision['variation']
@@ -509,7 +576,7 @@ module Optimizely
509
576
  end
510
577
  feature_enabled = variation['featureEnabled']
511
578
  if feature_enabled == true
512
- variation_variable_usages = @config.variation_id_to_variable_usage_map[variation['id']]
579
+ variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
513
580
  variable_id = variable['id']
514
581
  if variation_variable_usages&.key?(variable_id)
515
582
  variable_value = variation_variable_usages[variable_id]['value']
@@ -579,20 +646,24 @@ module Optimizely
579
646
  true
580
647
  end
581
648
 
582
- def validate_instantiation_options(datafile, skip_json_validation)
583
- unless skip_json_validation
584
- raise InvalidInputError, 'datafile' unless Helpers::Validator.datafile_valid?(datafile)
649
+ def validate_instantiation_options
650
+ raise InvalidInputError, 'logger' unless Helpers::Validator.logger_valid?(@logger)
651
+
652
+ unless Helpers::Validator.error_handler_valid?(@error_handler)
653
+ @error_handler = NoOpErrorHandler.new
654
+ raise InvalidInputError, 'error_handler'
585
655
  end
586
656
 
587
- raise InvalidInputError, 'logger' unless Helpers::Validator.logger_valid?(@logger)
588
- raise InvalidInputError, 'error_handler' unless Helpers::Validator.error_handler_valid?(@error_handler)
589
- raise InvalidInputError, 'event_dispatcher' unless Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
657
+ return if Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
658
+
659
+ @event_dispatcher = EventDispatcher.new
660
+ raise InvalidInputError, 'event_dispatcher'
590
661
  end
591
662
 
592
- def send_impression(experiment, variation_key, user_id, attributes = nil)
663
+ def send_impression(config, experiment, variation_key, user_id, attributes = nil)
593
664
  experiment_key = experiment['key']
594
- variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
595
- impression_event = @event_builder.create_impression_event(experiment, variation_id, user_id, attributes)
665
+ variation_id = config.get_variation_id_from_key(experiment_key, variation_key)
666
+ impression_event = @event_builder.create_impression_event(config, experiment, variation_id, user_id, attributes)
596
667
  @logger.log(Logger::INFO,
597
668
  "Dispatching impression event to URL #{impression_event.url} with params #{impression_event.params}.")
598
669
  begin
@@ -600,11 +671,15 @@ module Optimizely
600
671
  rescue => e
601
672
  @logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
602
673
  end
603
- variation = @config.get_variation_from_id(experiment_key, variation_id)
674
+ variation = config.get_variation_from_id(experiment_key, variation_id)
604
675
  @notification_center.send_notifications(
605
676
  NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
606
677
  experiment, user_id, attributes, variation, impression_event
607
678
  )
608
679
  end
680
+
681
+ def project_config
682
+ @config_manager.config
683
+ end
609
684
  end
610
685
  end