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 +4 -4
- data/lib/kameleoon/client.rb +224 -153
- data/lib/kameleoon/configuration/feature_flag.rb +13 -24
- data/lib/kameleoon/configuration/rule.rb +17 -5
- data/lib/kameleoon/configuration/settings.rb +20 -0
- data/lib/kameleoon/cookie.rb +3 -3
- data/lib/kameleoon/data.rb +24 -18
- data/lib/kameleoon/hybrid/manager.rb +60 -0
- data/lib/kameleoon/real_time/real_time_configuration_service.rb +98 -0
- data/lib/kameleoon/real_time/real_time_event.rb +22 -0
- data/lib/kameleoon/real_time/sse_client.rb +111 -0
- data/lib/kameleoon/real_time/sse_message.rb +23 -0
- data/lib/kameleoon/real_time/sse_request.rb +59 -0
- data/lib/kameleoon/storage/cache.rb +84 -0
- data/lib/kameleoon/storage/cache_factory.rb +23 -0
- data/lib/kameleoon/targeting/condition.rb +4 -4
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +60 -25
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +1 -1
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +2 -2
- data/lib/kameleoon/version.rb +1 -1
- metadata +25 -3
- data/lib/kameleoon/configuration/feature_flag_v2.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba4f943be2e686ba2cca810ef8f456622e25733eda03808c7b3d81e2d72b1ee3
|
4
|
+
data.tar.gz: cef2edf8ef8e449f3150f73c5df9d76477342a52e49fbf90885b458fd6aa1b57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b4e8f09c36222e08e9b426e90db6fb05476aa0e9bcd53036de31a8d7819db7b95ea5960adc6cc059494eb5355d9d912dd73a507923904723cf9ca63887adb4a
|
7
|
+
data.tar.gz: 7f7f9d409053f873d9f7daa9b281b7c926d3b314fe5a2e7bd0814d3a7b5fdf4f8c5f61c6eed9f65a5fd4c3d11665a0efc5464164636e9affd0bac96f95d3798f
|
data/lib/kameleoon/client.rb
CHANGED
@@ -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/
|
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'] ||
|
40
|
-
@api_data_url =
|
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
|
-
|
121
|
-
if !
|
122
|
-
track_experiment(visitor_code, experiment_id,
|
123
|
-
|
124
|
-
|
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)
|
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
|
-
|
258
|
-
|
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 =
|
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
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
405
|
-
# stored on a remote Kameleoon server. Usually data will be stored on our remote
|
406
|
-
#
|
407
|
-
#
|
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.
|
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
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
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 &&
|
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
|
-
@
|
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
|
-
@
|
471
|
-
|
472
|
-
|
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
|
-
|
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
|
-
|
487
|
-
|
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
|
-
|
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
|
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(
|
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
|
-
@
|
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
|
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
|
-
|
568
|
-
end
|
639
|
+
return url if variation_id.nil?
|
640
|
+
|
569
641
|
url += "&variationId=#{variation_id}"
|
570
|
-
if none_variation
|
571
|
-
|
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 = @
|
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
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
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 =
|
736
|
-
|
737
|
-
|
738
|
-
|
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
|
-
|
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 =
|
750
|
-
#
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
#
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
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
|
-
[
|
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
|
773
|
-
def _send_tracking_request(visitor_code,
|
774
|
-
if !
|
775
|
-
|
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 '
|
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
|
9
|
-
class FeatureFlag
|
10
|
-
|
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(
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
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
|
27
|
-
|
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
|