kameleoon-client-ruby 2.0.0 → 2.1.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
  SHA256:
3
- metadata.gz: f3e0c8012bf2bcd5f9369eb5783106ed1a9075d0c5c8e0ae96f0f794bafdc3d9
4
- data.tar.gz: 7b6b7695d09d04469a3981ab5c39157956c39697bedd38b5f53408c95a232496
3
+ metadata.gz: ba4f943be2e686ba2cca810ef8f456622e25733eda03808c7b3d81e2d72b1ee3
4
+ data.tar.gz: cef2edf8ef8e449f3150f73c5df9d76477342a52e49fbf90885b458fd6aa1b57
5
5
  SHA512:
6
- metadata.gz: de92a19e0202c6eebfb1477749298ee1948220250eb28b4b786c06558efa1acb86bf6e8afd2c4008bf78197536864fb300ca28d4b8d540cb3d1d2a5eefb146c3
7
- data.tar.gz: 9867186ac652c09e7d46b669afbe20df5718027c4e4139629f3282562675b4b40ccc289a7289b812089fdd1dadfca9ac30740b7b1ac5f699e10dc2179f761267
6
+ metadata.gz: 5b4e8f09c36222e08e9b426e90db6fb05476aa0e9bcd53036de31a8d7819db7b95ea5960adc6cc059494eb5355d9d912dd73a507923904723cf9ca63887adb4a
7
+ data.tar.gz: 7f7f9d409053f873d9f7daa9b281b7c926d3b314fe5a2e7bd0814d3a7b5fdf4f8c5f61c6eed9f65a5fd4c3d11665a0efc5464164636e9affd0bac96f95d3798f
@@ -6,8 +6,11 @@ require 'kameleoon/exceptions'
6
6
  require 'kameleoon/cookie'
7
7
  require 'kameleoon/configuration/feature_flag'
8
8
  require 'kameleoon/configuration/variation'
9
- require 'kameleoon/configuration/feature_flag_v2'
9
+ require 'kameleoon/configuration/settings'
10
+ require 'kameleoon/real_time/real_time_configuration_service'
10
11
  require 'kameleoon/storage/variation_storage'
12
+ require 'kameleoon/hybrid/manager'
13
+ require 'kameleoon/storage/cache_factory'
11
14
  require 'rufus/scheduler'
12
15
  require 'yaml'
13
16
  require 'json'
@@ -36,19 +39,29 @@ module Kameleoon
36
39
  @default_timeout = config['default_timeout'] || default_timeout # in ms
37
40
  refresh_interval = config['actions_configuration_refresh_interval']
38
41
  @interval = refresh_interval.nil? ? interval : "#{refresh_interval}m"
39
- @tracking_url = config['tracking_url'] || "https://api-ssx.kameleoon.com"
40
- @api_data_url = "https://api-data.kameleoon.com"
42
+ @tracking_url = config['tracking_url'] || API_SSX_URL
43
+ @api_data_url = 'https://api-data.kameleoon.com'
44
+ @events_url = 'https://events.kameleoon.com:8110/'
45
+ @real_time_configuration_service = nil
46
+ @update_configuration_handler = nil
47
+ @fetch_configuration_update_job = nil
41
48
  @client_id = client_id || config['client_id']
42
49
  @client_secret = client_secret || config['client_secret']
43
50
  @data_maximum_size = config['visitor_data_maximum_size'] || 500 # mb
44
51
  @environment = config['environment'] || DEFAULT_ENVIRONMENT
52
+ @settings = Kameleoon::Configuration::Settings.new
45
53
  @verbose_mode = config['verbose_mode'] || false
46
54
  @experiments = []
47
55
  @feature_flags = []
48
- @feature_flags_v2 = []
49
56
  @data = {}
50
57
  @user_agents = {}
51
58
  @variation_storage = Kameleoon::Storage::VariationStorage.new
59
+ @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(
60
+ CACHE_EXPIRATION_TIMEOUT,
61
+ CACHE_EXPIRATION_TIMEOUT * 3,
62
+ Kameleoon::Storage::CacheFactoryImpl.new,
63
+ method(:log)
64
+ )
52
65
  end
53
66
 
54
67
  ##
@@ -112,16 +125,17 @@ module Kameleoon
112
125
  check_visitor_code(visitor_code)
113
126
  experiment = @experiments.find { |exp| exp.id.to_s == experiment_id.to_s }
114
127
  if experiment.nil?
115
- raise Exception::ExperimentConfigurationNotFound.new(experiment_id)
128
+ raise Exception::ExperimentConfigurationNotFound.new(experiment_id),
129
+ "Experiment #{experiment_id} is not found"
116
130
  end
117
131
  check_site_code_enable(experiment)
118
132
  if check_targeting(visitor_code, experiment_id, experiment)
119
- saved_variation = get_valid_saved_variation(visitor_code, experiment)
120
- variation_key = saved_variation || variation_for_experiment(visitor_code, experiment)
121
- if !variation_key.nil?
122
- track_experiment(visitor_code, experiment_id, variation_key)
123
- @variation_storage.update_variation(visitor_code, experiment_id, variation_key) if saved_variation.nil?
124
- variation_key
133
+ # saved_variation = get_valid_saved_variation(visitor_code, experiment)
134
+ variation_id = calculate_variation_for_experiment(visitor_code, experiment)
135
+ if !variation_id.nil?
136
+ track_experiment(visitor_code, experiment_id, variation_id)
137
+ save_variation(visitor_code, experiment_id, variation_id)
138
+ variation_id
125
139
  else
126
140
  track_experiment(visitor_code, experiment_id, REFERENCE, true)
127
141
  raise Exception::NotAllocated.new(visitor_code),
@@ -148,9 +162,7 @@ module Kameleoon
148
162
  #
149
163
  def add_data(visitor_code, *args)
150
164
  check_visitor_code(visitor_code)
151
- while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20) do
152
- @data.shift
153
- end
165
+ @data.shift while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20)
154
166
  args.each do |data_element|
155
167
  if data_element.is_a?(UserAgent)
156
168
  add_user_agent_data(visitor_code, data_element)
@@ -252,28 +264,11 @@ module Kameleoon
252
264
  # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
253
265
  # associated targeting segment conditions were not fulfilled. He should see the reference variation
254
266
  # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
255
-
267
+ #
268
+ # DEPRECATED. Please use `is_feature_active` instead.
256
269
  def activate_feature(visitor_code, feature_key)
257
- check_visitor_code(visitor_code)
258
- feature_flag = find_feature_flag(feature_key)
259
- check_site_code_enable(feature_flag)
260
- unless check_targeting(visitor_code, feature_flag.id.to_i, feature_flag)
261
- raise Exception::NotTargeted.new(visitor_code),
262
- "Visitor '#{visitor_code}' is not targeted for FF with key '#{feature_key}'"
263
- end
264
-
265
- if feature_flag.is_scheduled_active(Time.now.to_i)
266
- threshold = obtain_hash_double(visitor_code, {}, feature_flag.id)
267
- if threshold >= 1 - feature_flag.exposition_rate
268
- track_experiment(visitor_code, feature_flag.id, feature_flag.variations.first['id'])
269
- true
270
- else
271
- track_experiment(visitor_code, feature_flag.id, REFERENCE, none_variation: true)
272
- false
273
- end
274
- else
275
- false
276
- end
270
+ warn '[DEPRECATION] `activate_feature` is deprecated. Please use `feature_active?` instead.'
271
+ feature_active?(visitor_code, feature_key)
277
272
  end
278
273
 
279
274
  ##
@@ -356,7 +351,7 @@ module Kameleoon
356
351
  # @raise [Kameleoon::Exception::VariationConfigurationNotFound]
357
352
  #
358
353
  def get_feature_all_variables(feature_key, variation_key)
359
- feature_flag = find_feature_flag_v2(feature_key)
354
+ feature_flag = find_feature_flag(feature_key)
360
355
  variation = feature_flag.get_variation_key(variation_key)
361
356
  if variation.nil?
362
357
  raise Exception::VariationConfigurationNotFound.new(variation_key),
@@ -381,54 +376,47 @@ module Kameleoon
381
376
  # DEPRECATED. Please use `get_feature_variable` instead.
382
377
  def obtain_feature_variable(feature_key, variable_key)
383
378
  warn '[DEPRECATION] `obtain_feature_variable` is deprecated. Please use `get_feature_variable` instead.'
384
- feature_flag = find_feature_flag(feature_key)
385
- custom_json = JSON.parse(feature_flag.variations.first['customJson'])[variable_key.to_s]
386
- if custom_json.nil?
387
- raise Exception::FeatureVariableNotFound.new('Feature variable not found')
388
- end
389
- case custom_json['type']
390
- when 'Boolean'
391
- custom_json['value']
392
- when 'String'
393
- custom_json['value']
394
- when 'Number'
395
- custom_json['value'].to_i
396
- when 'JSON'
397
- JSON.parse(custom_json['value'])
398
- else
399
- raise TypeError.new('Unknown type for feature variable')
400
- end
379
+ all_variables = get_feature_all_variables(
380
+ feature_key,
381
+ Configuration::VariationType::VARIATION_OFF
382
+ )
383
+ all_variables[variable_key]
401
384
  end
402
385
 
403
386
  ##
404
- # The retrieved_data_from_remote_source method allows you to retrieve data (according to a key passed as argument)
405
- # stored on a remote Kameleoon server. Usually data will be stored on our remote servers via the use of our Data API.
406
- # This method, along with the availability of our highly scalable servers for this purpose, provides a convenient way
407
- # to quickly store massive amounts of data that can be later retrieved for each of your visitors / users.
387
+ # The get_remote_date method allows you to retrieve data (according to a key passed as argument)
388
+ # stored on a remote Kameleoon server. Usually data will be stored on our remote
389
+ # servers via the use of our Data API.
390
+ # This method, along with the availability of our highly scalable servers for this purpose, provides a convenient
391
+ # way to quickly store massive amounts of data that can be later retrieved for each of your visitors / users.
408
392
  #
409
393
  # @param [String] key Key you want to retrieve data. This field is mandatory.
410
- # @param [Int] timeout Timeout for request. Equals default_timeout in a config file. This field is optional.
394
+ # @param [Int] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
395
+ # This field is optional.
411
396
  #
412
397
  # @return [Hash] Hash object of the json object.
413
- #
414
- #
415
- def retrieve_data_from_remote_source(key, timeout = @default_timeout)
398
+ def get_remote_date(key, timeout = @default_timeout)
416
399
  connexion_options = { connect_timeout: (timeout.to_f / 1000.0) }
417
400
  path = get_api_data_request_url(key)
418
401
  log "Retrieve API Data connexion: #{connexion_options.inspect}"
419
402
  response = get_sync(@api_data_url + path, connexion_options)
420
- if successful_sync?(response)
421
- JSON.parse(response.body) unless response.nil?
422
- else
423
- return nil
424
- end
403
+ return nil unless successful_sync?(response)
404
+
405
+ JSON.parse(response.body) unless response.nil?
406
+ end
407
+
408
+ ##
409
+ # DEPRECATED. Please use `get_feature_variable` instead.
410
+ def retrieve_data_from_remote_source(key, timeout = @default_timeout)
411
+ warn '[DEPRECATION] `retrieve_data_from_remote_source` is deprecated. Please use `get_remote_date` instead.'
412
+ get_remote_date(key, timeout)
425
413
  end
426
414
 
427
415
  ##
428
416
  # Returns a list of all experiment ids
429
417
  #
430
418
  # @return [Array] array of all experiment ids
431
- def get_experiment_list
419
+ def get_experiment_list # rubocop:disable Naming/AccessorMethodName
432
420
  @experiments.map { |it| it.id.to_i }
433
421
  end
434
422
 
@@ -443,7 +431,7 @@ module Kameleoon
443
431
  list_ids = []
444
432
  @experiments.each do |experiment|
445
433
  next unless check_targeting(visitor_code, experiment.id.to_i, experiment)
446
- next if only_allocated && variation_for_experiment(visitor_code, experiment).nil?
434
+ next if only_allocated && calculate_variation_for_experiment(visitor_code, experiment).nil?
447
435
 
448
436
  list_ids.push(experiment.id.to_i)
449
437
  end
@@ -454,8 +442,8 @@ module Kameleoon
454
442
  # Returns a list of all feature flag keys
455
443
  #
456
444
  # @return [Array] array of all feature flag keys
457
- def get_feature_list
458
- @feature_flags_v2.map(&:feature_key)
445
+ def get_feature_list # rubocop:disable Naming/AccessorMethodName
446
+ @feature_flags.map(&:feature_key)
459
447
  end
460
448
 
461
449
  ##
@@ -467,40 +455,114 @@ module Kameleoon
467
455
  def get_active_feature_list_for_visitor(visitor_code)
468
456
  check_visitor_code(visitor_code)
469
457
  list_keys = []
470
- @feature_flags_v2.each do |feature_flag|
471
- variation_key, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
472
- list_keys.push(feature_flag.feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
458
+ @feature_flags.each do |feature_flag|
459
+ variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
460
+ variation_key = _get_variation_key(variation, rule, feature_flag)
461
+ if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
462
+ list_keys.push(feature_flag.feature_key)
463
+ end
473
464
  end
474
465
  list_keys
475
466
  end
476
467
 
468
+ ##
469
+ # The `on_update_configuration()` method allows you to handle the event when configuration
470
+ # has updated data. It takes one input parameter: callable **handler**. The handler
471
+ # that will be called when the configuration is updated using a real-time configuration event.
472
+ #
473
+ # @param handler [Callable | NilClass] The handler that will be called when the configuration
474
+ # is updated using a real-time configuration event.
475
+ def on_update_configuration(handler)
476
+ @update_configuration_handler = handler
477
+ end
478
+
479
+ ##
480
+ # The `get_engine_tracking_code` returns the JavasScript code to be inserted in your page
481
+ # to send automatically the exposure events to the analytics solution you are using.
482
+ #
483
+ # @param [String] visitor_code The user's unique identifier. This field is mandatory.
484
+ #
485
+ # @return [String] JavasScript code to be inserted in your page to send automatically
486
+ # the exposure events to the analytics solution you are using.
487
+ def get_engine_tracking_code(visitor_code)
488
+ @hybrid_manager.get_engine_tracking_code(visitor_code)
489
+ end
490
+
477
491
  private
478
492
 
479
493
  API_SSX_URL = 'https://api-ssx.kameleoon.com'
480
494
  REFERENCE = 0
481
495
  DEFAULT_ENVIRONMENT = 'production'
482
- attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :feature_flags_v2, :scheduler, :data,
496
+ CACHE_EXPIRATION_TIMEOUT = 5
497
+ attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
483
498
  :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
484
499
 
485
500
  def fetch_configuration
486
- @scheduler = Rufus::Scheduler.singleton
487
- @scheduler.every @interval do
488
- log('Scheduled job to fetch configuration is starting.')
489
- fetch_configuration_job
490
- end
491
- @scheduler.schedule '0s' do
492
- log('Start-up, fetching is starting')
501
+ Rufus::Scheduler.singleton.in '0s' do
502
+ log('Initial configuration fetch is started.')
493
503
  fetch_configuration_job
494
504
  end
495
505
  end
496
506
 
497
- def fetch_configuration_job
507
+ def fetch_configuration_job(time_stamp = nil)
498
508
  EM.synchrony do
499
- obtain_configuration(@site_code, @environment)
509
+ begin
510
+ ok = obtain_configuration(@site_code, @environment, time_stamp)
511
+ if !ok && @settings.real_time_update
512
+ @settings.real_time_update = false
513
+ log('Switching to polling mode due to failed fetch')
514
+ end
515
+ rescue StandardError => e
516
+ log("Error occurred during configuration fetching: #{e}")
517
+ end
518
+ manage_configuration_update(@settings.real_time_update)
500
519
  EM.stop
501
520
  end
502
521
  end
503
522
 
523
+ def start_configuration_update_job_if_needed
524
+ return unless @fetch_configuration_update_job.nil?
525
+
526
+ @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @interval do
527
+ log('Scheduled job to fetch configuration is started.')
528
+ fetch_configuration_job
529
+ end
530
+ end
531
+
532
+ def stop_configuration_update_job_if_needed
533
+ return if @fetch_configuration_update_job.nil?
534
+
535
+ @fetch_configuration_update_job.unschedule
536
+ @fetch_configuration_update_job = nil
537
+ log('Scheduled job to fetch configuration is stopped.')
538
+ end
539
+
540
+ def start_real_time_configuration_service_if_needed
541
+ return unless @real_time_configuration_service.nil?
542
+
543
+ events_url = "#{@events_url}sse?siteCode=#{@site_code}"
544
+ fetch_func = proc { |real_time_event| fetch_configuration_job(real_time_event.time_stamp) }
545
+ @real_time_configuration_service =
546
+ Kameleoon::RealTime::RealTimeConfigurationService.new(events_url, fetch_func, method(:log))
547
+ end
548
+
549
+ def stop_real_time_configuration_service_if_needed
550
+ return if @real_time_configuration_service.nil?
551
+
552
+ @real_time_configuration_service.close
553
+ @real_time_configuration_service = nil
554
+ end
555
+
556
+ def manage_configuration_update(is_real_time_update)
557
+ if is_real_time_update
558
+ stop_configuration_update_job_if_needed
559
+ start_real_time_configuration_service_if_needed
560
+ else
561
+ stop_real_time_configuration_service_if_needed
562
+ start_configuration_update_job_if_needed
563
+ end
564
+ end
565
+
504
566
  # def hash_headers
505
567
  # if @access_token.nil?
506
568
  # CredentialsNotFound.new
@@ -515,26 +577,37 @@ module Kameleoon
515
577
  # { 'field' => field, 'operator' => operator, 'parameters' => parameters }
516
578
  # end
517
579
 
518
- def obtain_configuration(site_code, environment = @environment)
519
- log "Fetching configuration from Client-Config service"
580
+ def obtain_configuration(site_code, environment = @environment, time_stamp = nil)
581
+ log 'Fetching configuration from Client-Config service'
520
582
  request_path = "mobile?siteCode=#{site_code}"
521
583
  request_path += "&environment=#{environment}" unless environment.nil?
584
+ request_path += "&ts=#{time_stamp}" unless time_stamp.nil?
522
585
  request = request_configuration(request_path)
523
- return unless request
586
+ return false unless request
524
587
 
525
588
  configuration = JSON.parse(request.response)
526
589
  @experiments = Kameleoon::Configuration::Experiment.create_from_array(configuration['experiments']) ||
527
590
  @experiments
528
- @feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(configuration['featureFlags']) ||
529
- @feature_flags
530
- feature_flags_v2_new = Kameleoon::Configuration::FeatureFlagV2.create_from_array(
591
+ @feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(
531
592
  configuration['featureFlagConfigurations']
532
593
  )
533
- @feature_flags_v2 = feature_flags_v2_new || @feature_flags_v2
594
+ @settings.update(configuration['configuration'])
595
+ call_update_handler_if_needed(!time_stamp.nil?)
534
596
  log "Feature flags are fetched: #{request.inspect}"
597
+ true
598
+ end
599
+
600
+ ##
601
+ # Call the handler when configuraiton was updated with new time stamp.
602
+ #
603
+ # @param need_call [Bool] Indicates if we need to call handler or not.
604
+ def call_update_handler_if_needed(need_call)
605
+ return if !need_call || @update_configuration_handler.nil?
606
+
607
+ @update_configuration_handler.call
535
608
  end
536
609
 
537
- def variation_for_experiment(visitor_code, experiment)
610
+ def calculate_variation_for_experiment(visitor_code, experiment)
538
611
  threshold = obtain_hash_double(visitor_code, experiment.respool_time, experiment.id)
539
612
  experiment.deviations.each do |key, value|
540
613
  threshold -= value
@@ -563,13 +636,11 @@ module Kameleoon
563
636
  def get_experiment_register_url(visitor_code, experiment_id, variation_id = nil, none_variation = false)
564
637
  url = "/experimentTracking?#{URI.encode_www_form(get_common_ssx_parameters(visitor_code))}"
565
638
  url += "&experimentId=#{experiment_id}"
566
- if variation_id.nil?
567
- return url
568
- end
639
+ return url if variation_id.nil?
640
+
569
641
  url += "&variationId=#{variation_id}"
570
- if none_variation
571
- url += '&noneVariation=true'
572
- end
642
+ url += '&noneVariation=true' if none_variation
643
+
573
644
  url
574
645
  end
575
646
 
@@ -586,26 +657,8 @@ module Kameleoon
586
657
  end
587
658
 
588
659
  def find_feature_flag(feature_key)
589
- case feature_key
590
- when String
591
- feature_flag = @feature_flags.select { |ff| ff.identification_key == feature_key }.first
592
- when Integer
593
- feature_flag = @feature_flags.select { |ff| ff.id.to_i == feature_key }.first
594
- print "\nPassing `feature_key` with type of `int` to `activate_feature` or `obtain_feature_variable` "\
595
- "is deprecated, it will be removed in next releases. This is necessary to support multi-environment feature\n"
596
- else
597
- raise TypeError.new('Feature key should be a String or an Integer.'),
598
- 'Feature key should be a String or an Integer.'
599
- end
600
- error_message = "Feature #{feature_key} not found"
601
- raise Exception::FeatureConfigurationNotFound.new(feature_key), error_message if feature_flag.nil?
602
-
603
- feature_flag
604
- end
605
-
606
- def find_feature_flag_v2(feature_key)
607
660
  if feature_key.is_a?(String)
608
- feature_flag = @feature_flags_v2.select { |ff| ff.feature_key == feature_key }.first
661
+ feature_flag = @feature_flags.select { |ff| ff.feature_key == feature_key }.first
609
662
  else
610
663
  raise TypeError.new('Feature key should be a String or an Integer.'),
611
664
  'Feature key should be a String or an Integer.'
@@ -686,7 +739,7 @@ module Kameleoon
686
739
  end
687
740
 
688
741
  def log(text)
689
- print "Kameleoon Log: #{text}\n" if @verbose_mode
742
+ print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
690
743
  end
691
744
 
692
745
  def add_user_agent_data(visitor_code, user_agent)
@@ -698,15 +751,16 @@ module Kameleoon
698
751
  headers['User-Agent'] = user_agent.value unless user_agent.nil?
699
752
  end
700
753
 
701
- def get_valid_saved_variation(visitor_code, experiment)
702
- variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
703
- unless variation_id.nil?
704
- return @variation_storage.variation_valid?(visitor_code,
705
- experiment.id.to_i,
706
- experiment.respool_time[variation_id])
707
- end
708
- nil
709
- end
754
+ # Uncomment when using storage
755
+ # def get_valid_saved_variation(visitor_code, experiment)
756
+ # variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
757
+ # unless variation_id.nil?
758
+ # return @variation_storage.variation_valid?(visitor_code,
759
+ # experiment.id.to_i,
760
+ # experiment.respool_time[variation_id])
761
+ # end
762
+ # nil
763
+ # end
710
764
 
711
765
  def check_targeting(visitor_code, campaign_id, exp_ff_rule)
712
766
  segment = exp_ff_rule.targeting_segment
@@ -732,10 +786,13 @@ module Kameleoon
732
786
  # helper method for getting variation key for feature flag
733
787
  def _get_feature_variation_key(visitor_code, feature_key)
734
788
  check_visitor_code(visitor_code)
735
- feature_flag = find_feature_flag_v2(feature_key)
736
- variation_key, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
737
- if !rule.nil? && rule.type == Kameleoon::Configuration::RuleType::EXPERIMENTATION
738
- _send_tracking_request(visitor_code, variation_key, rule)
789
+ feature_flag = find_feature_flag(feature_key)
790
+ variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
791
+ variation_key = _get_variation_key(variation, rule, feature_flag)
792
+ unless rule.nil?
793
+ save_variation(visitor_code, rule.experiment_id, variation.variation_id) unless variation.nil?
794
+ variation_id = variation.variation_id unless variation.nil?
795
+ _send_tracking_request(visitor_code, rule.experiment_id, variation_id)
739
796
  end
740
797
  [feature_flag, variation_key]
741
798
  end
@@ -744,35 +801,49 @@ module Kameleoon
744
801
  # helper method for calculate variation key for feature flag
745
802
  def _calculate_variation_key_for_feature(visitor_code, feature_flag)
746
803
  # no rules -> return default_variation_key
747
- unless feature_flag.rules.empty?
804
+ feature_flag.rules.each do |rule|
805
+ # check if visitor is targeted for rule, else next rule
806
+ next unless check_targeting(visitor_code, feature_flag.id, rule)
807
+
748
808
  # uses for rule exposition
749
- hash_rule = obtain_hash_double_v2(visitor_code, feature_flag.id)
750
- # uses for variation's expositions
751
- hash_variation = obtain_hash_double_v2(visitor_code, feature_flag.id, 'variation')
752
- feature_flag.rules.each do |rule|
753
- # check if visitor is targeted for rule, else next rule
754
- next unless check_targeting(visitor_code, feature_flag.id, rule)
755
-
756
- # check main expostion for rule with hashRule
757
- if hash_rule < rule.exposition
758
- # get variation key with new hashVariation
759
- variation_key = rule.get_variation_key(hash_variation)
760
- # variation_key can be nil for experiment rules only, for targeted rule will be always exist
761
- return [variation_key, rule] unless variation_key.nil?
762
- # if visitor is targeted for targeted rule then break cycle -> return default
763
- elsif rule.type == Kameleoon::Configuration::RuleType::TARGETED_DELIVERY
764
- break
765
- end
809
+ hash_rule = obtain_hash_double_rule(visitor_code, rule.id, rule.respool_time)
810
+ # check main expostion for rule with hashRule
811
+ if hash_rule <= rule.exposition
812
+ # uses for variation's expositions
813
+ hash_variation = obtain_hash_double_rule(visitor_code, rule.experiment_id, rule.respool_time)
814
+ # get variation key with new hashVariation
815
+ variation = rule.get_variation(hash_variation)
816
+ # variation_key can be nil for experiment rules only, for targeted rule will be always exist
817
+ return [variation, rule] unless variation.nil?
818
+ # if visitor is targeted for targeted rule then break cycle -> return default
819
+ elsif rule.targeted_delivery_type?
820
+ break
766
821
  end
767
822
  end
768
- [feature_flag.default_variation_key, nil]
823
+ [nil, nil]
824
+ end
825
+
826
+ def save_variation(visitor_code, experiment_id, variation_id)
827
+ return if experiment_id.nil? || variation_id.nil?
828
+
829
+ @variation_storage.update_variation(visitor_code, experiment_id, variation_id)
830
+ @hybrid_manager.add_variation(visitor_code, experiment_id, variation_id)
831
+ end
832
+
833
+ def _get_variation_key(var_by_exp, rule, feature_flag)
834
+ return var_by_exp.variation_key unless var_by_exp.nil?
835
+
836
+ return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experiment_type?
837
+
838
+ feature_flag.default_variation_key
769
839
  end
770
840
 
771
841
  ##
772
- # helper method for sending tracking requests for new FF v2
773
- def _send_tracking_request(visitor_code, variation_key, rule)
774
- if !rule.experiment_id.nil?
775
- track_experiment(visitor_code, rule.experiment_id, rule.get_variation_id_by_key(variation_key))
842
+ # helper method for sending tracking requests for new FF
843
+ def _send_tracking_request(visitor_code, experiment_id, variation_id)
844
+ if !experiment_id.nil?
845
+ variation_reference_id = variation_id || 0
846
+ track_experiment(visitor_code, experiment_id, variation_reference_id)
776
847
  else
777
848
  log 'An attempt to send a request with null experimentId was blocked'
778
849
  end
@@ -1,40 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'experiment'
3
+ require_relative 'rule'
4
+ require_relative 'variation'
4
5
 
5
6
  module Kameleoon
6
7
  # Module which contains all internal data of SDK
7
8
  module Configuration
8
- # Class for manage all experiments and old feature flags
9
- class FeatureFlag < Experiment
10
- STATUS_ACTIVE = 'ACTIVE'
11
- FEATURE_STATUS_DEACTIVATED = 'DEACTIVATED'
12
- attr_accessor :identification_key, :feature_status, :schedules, :exposition_rate
9
+ # Class for manage all feature flags with rules
10
+ class FeatureFlag
11
+ attr_accessor :id, :feature_key, :variations, :default_variation_key, :rules
13
12
 
14
13
  def self.create_from_array(array)
15
14
  array&.map { |it| FeatureFlag.new(it) }
16
15
  end
17
16
 
18
- def initialize(feature_flag_hash)
19
- super(feature_flag_hash)
20
- @feature_status = feature_flag_hash['featureStatus']
21
- @identification_key = feature_flag_hash['identificationKey'] if feature_flag_hash['identificationKey']
22
- @exposition_rate = feature_flag_hash['expositionRate']
23
- @schedules = feature_flag_hash['schedules'] unless feature_flag_hash['schedules'].nil?
17
+ def initialize(hash)
18
+ @id = hash['id']
19
+ @feature_key = hash['featureKey']
20
+ @default_variation_key = hash['defaultVariationKey']
21
+ @variations = Variation.create_from_array(hash['variations'])
22
+ @rules = Rule.create_from_array(hash['rules'])
24
23
  end
25
24
 
26
- def is_scheduled_active(date)
27
- current_status = @status == STATUS_ACTIVE
28
- if @feature_status == FEATURE_STATUS_DEACTIVATED || @schedules.empty?
29
- return current_status
30
- end
31
- @schedules.each do |schedule|
32
- if (schedule['dateStart'].nil? || Time.parse(schedule['dateStart']).to_i < date) &&
33
- (schedule['dateEnd'].nil? || Time.parse(schedule['dateEnd']).to_i > date)
34
- return true
35
- end
36
- end
37
- false
25
+ def get_variation_key(key)
26
+ variations.select { |v| v.key == key }.first
38
27
  end
39
28
  end
40
29
  end