optimizely-sdk 3.1.1 → 3.2.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.
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