optimizely-sdk 3.4.0 → 3.5.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +144 -35
- data/lib/optimizely/audience.rb +2 -2
- data/lib/optimizely/config/datafile_project_config.rb +15 -3
- data/lib/optimizely/config_manager/async_scheduler.rb +6 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +20 -16
- data/lib/optimizely/event_dispatcher.rb +6 -13
- data/lib/optimizely/exceptions.rb +1 -9
- data/lib/optimizely/helpers/constants.rb +6 -3
- data/lib/optimizely/helpers/http_utils.rb +53 -0
- data/lib/optimizely/helpers/variable_type.rb +8 -1
- data/lib/optimizely/optimizely_factory.rb +54 -5
- data/lib/optimizely/version.rb +1 -1
- metadata +5 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 033cd0357752fc0d52193944412f2a3029924e3ffdc650848924fe9d9a9ce6ee
|
4
|
+
data.tar.gz: 4e7949391d0a41b832c21f8835158345966edf63949256c15436321683927678
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a434d80add6804f3722dc4d616d2533f263601a80a41d357ac1766d099f79eae6a6429eb945a198a0689ce6e2525fd6f44d2b308a1e7a0d470fe184d89e28b
|
7
|
+
data.tar.gz: 73735c47d0e2ed58a26de0b7a8fb25d8167f45e1f4535aeb90f4fe95fb0fd5733dbcf614efe4d4d8af4ee0b3d5080953224c2f2a8fee80380e521a97c1a310e8
|
data/lib/optimizely.rb
CHANGED
@@ -430,6 +430,32 @@ module Optimizely
|
|
430
430
|
variable_value
|
431
431
|
end
|
432
432
|
|
433
|
+
# Get the Json value of the specified variable in the feature flag in a Dict.
|
434
|
+
#
|
435
|
+
# @param feature_flag_key - String key of feature flag the variable belongs to
|
436
|
+
# @param variable_key - String key of variable for which we are getting the string value
|
437
|
+
# @param user_id - String user ID
|
438
|
+
# @param attributes - Hash representing visitor attributes and values which need to be recorded.
|
439
|
+
#
|
440
|
+
# @return [Dict] the Dict containing variable value.
|
441
|
+
# @return [nil] if the feature flag or variable are not found.
|
442
|
+
|
443
|
+
def get_feature_variable_json(feature_flag_key, variable_key, user_id, attributes = nil)
|
444
|
+
unless is_valid
|
445
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_json').message)
|
446
|
+
return nil
|
447
|
+
end
|
448
|
+
variable_value = get_feature_variable_for_type(
|
449
|
+
feature_flag_key,
|
450
|
+
variable_key,
|
451
|
+
Optimizely::Helpers::Constants::VARIABLE_TYPES['JSON'],
|
452
|
+
user_id,
|
453
|
+
attributes
|
454
|
+
)
|
455
|
+
|
456
|
+
variable_value
|
457
|
+
end
|
458
|
+
|
433
459
|
# Get the Boolean value of the specified variable in the feature flag.
|
434
460
|
#
|
435
461
|
# @param feature_flag_key - String key of feature flag the variable belongs to
|
@@ -484,6 +510,71 @@ module Optimizely
|
|
484
510
|
variable_value
|
485
511
|
end
|
486
512
|
|
513
|
+
# Get values of all the variables in the feature flag and returns them in a Dict
|
514
|
+
#
|
515
|
+
# @param feature_flag_key - String key of feature flag
|
516
|
+
# @param user_id - String user ID
|
517
|
+
# @param attributes - Hash representing visitor attributes and values which need to be recorded.
|
518
|
+
#
|
519
|
+
# @return [Dict] the Dict containing all the varible values
|
520
|
+
# @return [nil] if the feature flag is not found.
|
521
|
+
|
522
|
+
def get_all_feature_variables(feature_flag_key, user_id, attributes = nil)
|
523
|
+
unless is_valid
|
524
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_all_feature_variables').message)
|
525
|
+
return nil
|
526
|
+
end
|
527
|
+
|
528
|
+
return nil unless Optimizely::Helpers::Validator.inputs_valid?(
|
529
|
+
{
|
530
|
+
feature_flag_key: feature_flag_key,
|
531
|
+
user_id: user_id
|
532
|
+
},
|
533
|
+
@logger, Logger::ERROR
|
534
|
+
)
|
535
|
+
|
536
|
+
return nil unless user_inputs_valid?(attributes)
|
537
|
+
|
538
|
+
config = project_config
|
539
|
+
|
540
|
+
feature_flag = config.get_feature_flag_from_key(feature_flag_key)
|
541
|
+
unless feature_flag
|
542
|
+
@logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
|
543
|
+
return nil
|
544
|
+
end
|
545
|
+
|
546
|
+
decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
547
|
+
variation = decision ? decision['variation'] : nil
|
548
|
+
feature_enabled = variation ? variation['featureEnabled'] : false
|
549
|
+
all_variables = {}
|
550
|
+
|
551
|
+
feature_flag['variables'].each do |variable|
|
552
|
+
variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
553
|
+
all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
|
554
|
+
end
|
555
|
+
|
556
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
557
|
+
if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
558
|
+
source_info = {
|
559
|
+
experiment_key: decision.experiment['key'],
|
560
|
+
variation_key: variation['key']
|
561
|
+
}
|
562
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
563
|
+
end
|
564
|
+
|
565
|
+
@notification_center.send_notifications(
|
566
|
+
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
567
|
+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['ALL_FEATURE_VARIABLES'], user_id, (attributes || {}),
|
568
|
+
feature_key: feature_flag_key,
|
569
|
+
feature_enabled: feature_enabled,
|
570
|
+
source: source_string,
|
571
|
+
variable_values: all_variables,
|
572
|
+
source_info: source_info || {}
|
573
|
+
)
|
574
|
+
|
575
|
+
all_variables
|
576
|
+
end
|
577
|
+
|
487
578
|
# Get the Integer value of the specified variable in the feature flag.
|
488
579
|
#
|
489
580
|
# @param feature_flag_key - String key of feature flag the variable belongs to
|
@@ -649,8 +740,6 @@ module Optimizely
|
|
649
740
|
# Error message logged in DatafileProjectConfig- get_feature_flag_from_key
|
650
741
|
return nil if variable.nil?
|
651
742
|
|
652
|
-
feature_enabled = false
|
653
|
-
|
654
743
|
# If variable_type is nil, set it equal to variable['type']
|
655
744
|
variable_type ||= variable['type']
|
656
745
|
# Returns nil if type differs
|
@@ -658,43 +747,24 @@ module Optimizely
|
|
658
747
|
@logger.log(Logger::WARN,
|
659
748
|
"Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
|
660
749
|
return nil
|
661
|
-
else
|
662
|
-
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
663
|
-
decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
664
|
-
variable_value = variable['defaultValue']
|
665
|
-
if decision
|
666
|
-
variation = decision['variation']
|
667
|
-
if decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
668
|
-
source_info = {
|
669
|
-
experiment_key: decision.experiment['key'],
|
670
|
-
variation_key: variation['key']
|
671
|
-
}
|
672
|
-
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
673
|
-
end
|
674
|
-
feature_enabled = variation['featureEnabled']
|
675
|
-
if feature_enabled == true
|
676
|
-
variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
|
677
|
-
variable_id = variable['id']
|
678
|
-
if variation_variable_usages&.key?(variable_id)
|
679
|
-
variable_value = variation_variable_usages[variable_id]['value']
|
680
|
-
@logger.log(Logger::INFO,
|
681
|
-
"Got variable value '#{variable_value}' for variable '#{variable_key}' of feature flag '#{feature_flag_key}'.")
|
682
|
-
else
|
683
|
-
@logger.log(Logger::DEBUG,
|
684
|
-
"Variable '#{variable_key}' is not used in variation '#{variation['key']}'. Returning the default variable value '#{variable_value}'.")
|
685
|
-
end
|
686
|
-
else
|
687
|
-
@logger.log(Logger::DEBUG,
|
688
|
-
"Feature '#{feature_flag_key}' for variation '#{variation['key']}' is not enabled. Returning the default variable value '#{variable_value}'.")
|
689
|
-
end
|
690
|
-
else
|
691
|
-
@logger.log(Logger::INFO,
|
692
|
-
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
|
693
|
-
end
|
694
750
|
end
|
695
751
|
|
752
|
+
decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
753
|
+
variation = decision ? decision['variation'] : nil
|
754
|
+
feature_enabled = variation ? variation['featureEnabled'] : false
|
755
|
+
|
756
|
+
variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
696
757
|
variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
|
697
758
|
|
759
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
760
|
+
if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
761
|
+
source_info = {
|
762
|
+
experiment_key: decision.experiment['key'],
|
763
|
+
variation_key: variation['key']
|
764
|
+
}
|
765
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
766
|
+
end
|
767
|
+
|
698
768
|
@notification_center.send_notifications(
|
699
769
|
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
700
770
|
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
|
@@ -710,6 +780,45 @@ module Optimizely
|
|
710
780
|
variable_value
|
711
781
|
end
|
712
782
|
|
783
|
+
def get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
784
|
+
# Helper method to get the non type-casted value for a variable attached to a
|
785
|
+
# feature flag. Returns appropriate variable value depending on whether there
|
786
|
+
# was a matching variation, feature was enabled or not or varible was part of the
|
787
|
+
# available variation or not. Also logs the appropriate message explaining how it
|
788
|
+
# evaluated the value of the variable.
|
789
|
+
#
|
790
|
+
# feature_flag_key - String key of feature flag the variable belongs to
|
791
|
+
# feature_enabled - Boolean indicating if feature is enabled or not
|
792
|
+
# variation - varition returned by decision service
|
793
|
+
# user_id - String user ID
|
794
|
+
#
|
795
|
+
# Returns string value of the variable.
|
796
|
+
|
797
|
+
config = project_config
|
798
|
+
variable_value = variable['defaultValue']
|
799
|
+
if variation
|
800
|
+
if feature_enabled == true
|
801
|
+
variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
|
802
|
+
variable_id = variable['id']
|
803
|
+
if variation_variable_usages&.key?(variable_id)
|
804
|
+
variable_value = variation_variable_usages[variable_id]['value']
|
805
|
+
@logger.log(Logger::INFO,
|
806
|
+
"Got variable value '#{variable_value}' for variable '#{variable['key']}' of feature flag '#{feature_flag_key}'.")
|
807
|
+
else
|
808
|
+
@logger.log(Logger::DEBUG,
|
809
|
+
"Variable '#{variable['key']}' is not used in variation '#{variation['key']}'. Returning the default variable value '#{variable_value}'.")
|
810
|
+
end
|
811
|
+
else
|
812
|
+
@logger.log(Logger::DEBUG,
|
813
|
+
"Feature '#{feature_flag_key}' for variation '#{variation['key']}' is not enabled. Returning the default variable value '#{variable_value}'.")
|
814
|
+
end
|
815
|
+
else
|
816
|
+
@logger.log(Logger::INFO,
|
817
|
+
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
|
818
|
+
end
|
819
|
+
variable_value
|
820
|
+
end
|
821
|
+
|
713
822
|
def user_inputs_valid?(attributes = nil, event_tags = nil)
|
714
823
|
# Helper method to validate user inputs.
|
715
824
|
#
|
data/lib/optimizely/audience.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019, Optimizely and contributors
|
4
|
+
# Copyright 2016-2017, 2019-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -84,7 +84,7 @@ module Optimizely
|
|
84
84
|
result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_custom_attr)
|
85
85
|
result_str = result.nil? ? 'UNKNOWN' : result.to_s.upcase
|
86
86
|
logger.log(
|
87
|
-
Logger::
|
87
|
+
Logger::DEBUG,
|
88
88
|
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
|
89
89
|
)
|
90
90
|
result
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019, Optimizely and contributors
|
3
|
+
# Copyright 2019-2020, Optimizely and contributors
|
4
4
|
#
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
6
|
# you may not use this file except in compliance with the License.
|
@@ -82,6 +82,17 @@ module Optimizely
|
|
82
82
|
@revision = config['revision']
|
83
83
|
@rollouts = config.fetch('rollouts', [])
|
84
84
|
|
85
|
+
# Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
|
86
|
+
# Converting it to a first-class json type while creating Project Config
|
87
|
+
@feature_flags.each do |feature_flag|
|
88
|
+
feature_flag['variables'].each do |variable|
|
89
|
+
if variable['type'] == 'string' && variable['subType'] == 'json'
|
90
|
+
variable['type'] = 'json'
|
91
|
+
variable.delete('subType')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
85
96
|
# Utility maps for quick lookup
|
86
97
|
@attribute_key_map = generate_key_map(@attributes, 'key')
|
87
98
|
@event_key_map = generate_key_map(@events, 'key')
|
@@ -159,8 +170,9 @@ module Optimizely
|
|
159
170
|
config = new(datafile, logger, error_handler)
|
160
171
|
rescue StandardError => e
|
161
172
|
default_logger = SimpleLogger.new
|
162
|
-
|
163
|
-
|
173
|
+
error_to_handle = e.class == InvalidDatafileVersionError ? e : InvalidInputError.new('datafile')
|
174
|
+
error_msg = error_to_handle.message
|
175
|
+
|
164
176
|
default_logger.log(Logger::ERROR, error_msg)
|
165
177
|
error_handler.handle_error error_to_handle
|
166
178
|
return nil
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2019, Optimizely and contributors
|
4
|
+
# Copyright 2019-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -19,13 +19,14 @@ module Optimizely
|
|
19
19
|
class AsyncScheduler
|
20
20
|
attr_reader :running
|
21
21
|
|
22
|
-
def initialize(callback, interval, auto_update, logger = nil)
|
22
|
+
def initialize(callback, interval, auto_update, logger = nil, error_handler = nil)
|
23
23
|
# Sets up AsyncScheduler to execute a callback periodically.
|
24
24
|
#
|
25
25
|
# callback - Main function to be executed periodically.
|
26
26
|
# interval - How many seconds to wait between executions.
|
27
27
|
# auto_update - boolean indicates to run infinitely or only once.
|
28
28
|
# logger - Optional Provides a logger instance.
|
29
|
+
# error_handler - Optional Provides a handle_error method to handle exceptions.
|
29
30
|
|
30
31
|
@callback = callback
|
31
32
|
@interval = interval
|
@@ -33,6 +34,7 @@ module Optimizely
|
|
33
34
|
@running = false
|
34
35
|
@thread = nil
|
35
36
|
@logger = logger || NoOpLogger.new
|
37
|
+
@error_handler = error_handler || NoOpErrorHandler.new
|
36
38
|
end
|
37
39
|
|
38
40
|
def start!
|
@@ -54,6 +56,7 @@ module Optimizely
|
|
54
56
|
Logger::ERROR,
|
55
57
|
"Couldn't create a new thread for async scheduler. #{e.message}"
|
56
58
|
)
|
59
|
+
@error_handler.handle_error(e)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
@@ -80,6 +83,7 @@ module Optimizely
|
|
80
83
|
Logger::ERROR,
|
81
84
|
"Something went wrong when executing passed callback. #{e.message}"
|
82
85
|
)
|
86
|
+
@error_handler.handle_error(e)
|
83
87
|
stop!
|
84
88
|
end
|
85
89
|
break unless @auto_update
|
@@ -19,14 +19,16 @@ require_relative '../config/datafile_project_config'
|
|
19
19
|
require_relative '../error_handler'
|
20
20
|
require_relative '../exceptions'
|
21
21
|
require_relative '../helpers/constants'
|
22
|
+
require_relative '../helpers/http_utils'
|
22
23
|
require_relative '../logger'
|
23
24
|
require_relative '../notification_center'
|
24
25
|
require_relative '../project_config'
|
25
26
|
require_relative '../optimizely_config'
|
26
27
|
require_relative 'project_config_manager'
|
27
28
|
require_relative 'async_scheduler'
|
28
|
-
|
29
|
+
|
29
30
|
require 'json'
|
31
|
+
|
30
32
|
module Optimizely
|
31
33
|
class HTTPProjectConfigManager < ProjectConfigManager
|
32
34
|
# Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
|
@@ -49,6 +51,7 @@ module Optimizely
|
|
49
51
|
# error_handler - Provides a handle_error method to handle exceptions.
|
50
52
|
# skip_json_validation - Optional boolean param which allows skipping JSON schema
|
51
53
|
# validation upon object invocation. By default JSON schema validation will be performed.
|
54
|
+
# datafile_access_token - access token used to fetch private datafiles
|
52
55
|
def initialize(
|
53
56
|
sdk_key: nil,
|
54
57
|
url: nil,
|
@@ -61,10 +64,12 @@ module Optimizely
|
|
61
64
|
logger: nil,
|
62
65
|
error_handler: nil,
|
63
66
|
skip_json_validation: false,
|
64
|
-
notification_center: nil
|
67
|
+
notification_center: nil,
|
68
|
+
datafile_access_token: nil
|
65
69
|
)
|
66
70
|
@logger = logger || NoOpLogger.new
|
67
71
|
@error_handler = error_handler || NoOpErrorHandler.new
|
72
|
+
@access_token = datafile_access_token
|
68
73
|
@datafile_url = get_datafile_url(sdk_key, url, url_template)
|
69
74
|
@polling_interval = nil
|
70
75
|
polling_interval(polling_interval)
|
@@ -148,16 +153,13 @@ module Optimizely
|
|
148
153
|
"Fetching datafile from #{@datafile_url}"
|
149
154
|
)
|
150
155
|
begin
|
151
|
-
headers = {
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
headers[Helpers::Constants::HTTP_HEADERS['LAST_MODIFIED']] = @last_modified if @last_modified
|
156
|
+
headers = {}
|
157
|
+
headers['Content-Type'] = 'application/json'
|
158
|
+
headers['If-Modified-Since'] = @last_modified if @last_modified
|
159
|
+
headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?
|
156
160
|
|
157
|
-
response =
|
158
|
-
@datafile_url,
|
159
|
-
headers: headers,
|
160
|
-
timeout: Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
|
161
|
+
response = Helpers::HttpUtils.make_request(
|
162
|
+
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
|
161
163
|
)
|
162
164
|
rescue StandardError => e
|
163
165
|
@logger.log(
|
@@ -284,17 +286,19 @@ module Optimizely
|
|
284
286
|
# SDK key to determine URL from which to fetch the datafile.
|
285
287
|
# Returns String representing URL to fetch datafile from.
|
286
288
|
if sdk_key.nil? && url.nil?
|
287
|
-
|
288
|
-
@
|
289
|
+
error_msg = 'Must provide at least one of sdk_key or url.'
|
290
|
+
@logger.log(Logger::ERROR, error_msg)
|
291
|
+
@error_handler.handle_error(InvalidInputsError.new(error_msg))
|
289
292
|
end
|
290
293
|
|
291
294
|
unless url
|
292
|
-
url_template ||= Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE']
|
295
|
+
url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
|
293
296
|
begin
|
294
297
|
return (url_template % sdk_key)
|
295
298
|
rescue
|
296
|
-
|
297
|
-
@
|
299
|
+
error_msg = "Invalid url_template #{url_template} provided."
|
300
|
+
@logger.log(Logger::ERROR, error_msg)
|
301
|
+
@error_handler.handle_error(InvalidInputsError.new(error_msg))
|
298
302
|
end
|
299
303
|
end
|
300
304
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019
|
4
|
+
# Copyright 2016-2017, 2019-2020 Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -16,8 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
require_relative 'exceptions'
|
19
|
-
|
20
|
-
require 'httparty'
|
19
|
+
require_relative 'helpers/http_utils'
|
21
20
|
|
22
21
|
module Optimizely
|
23
22
|
class NoOpEventDispatcher
|
@@ -39,19 +38,13 @@ module Optimizely
|
|
39
38
|
#
|
40
39
|
# @param event - Event object
|
41
40
|
def dispatch_event(event)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
elsif event.http_verb == :post
|
46
|
-
response = HTTParty.post(event.url,
|
47
|
-
body: event.params.to_json,
|
48
|
-
headers: event.headers,
|
49
|
-
timeout: REQUEST_TIMEOUT)
|
50
|
-
end
|
41
|
+
response = Helpers::HttpUtils.make_request(
|
42
|
+
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT
|
43
|
+
)
|
51
44
|
|
52
45
|
error_msg = "Event failed to dispatch with response code: #{response.code}"
|
53
46
|
|
54
|
-
case response.code
|
47
|
+
case response.code.to_i
|
55
48
|
when 400...500
|
56
49
|
@logger.log(Logger::ERROR, error_msg)
|
57
50
|
@error_handler.handle_error(HTTPCallError.new("HTTP Client Error: #{response.code}"))
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -81,14 +81,6 @@ module Optimizely
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
class InvalidDatafileError < Error
|
85
|
-
# Raised when a public method fails due to an invalid datafile
|
86
|
-
|
87
|
-
def initialize(aborted_method)
|
88
|
-
super("Provided datafile is in an invalid format. Aborting #{aborted_method}.")
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
84
|
class InvalidDatafileVersionError < Error
|
93
85
|
# Raised when a datafile with an unsupported version is provided
|
94
86
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -304,7 +304,8 @@ module Optimizely
|
|
304
304
|
'BOOLEAN' => 'boolean',
|
305
305
|
'DOUBLE' => 'double',
|
306
306
|
'INTEGER' => 'integer',
|
307
|
-
'STRING' => 'string'
|
307
|
+
'STRING' => 'string',
|
308
|
+
'JSON' => 'json'
|
308
309
|
}.freeze
|
309
310
|
|
310
311
|
INPUT_VARIABLES = {
|
@@ -357,11 +358,13 @@ module Optimizely
|
|
357
358
|
'AB_TEST' => 'ab-test',
|
358
359
|
'FEATURE' => 'feature',
|
359
360
|
'FEATURE_TEST' => 'feature-test',
|
360
|
-
'FEATURE_VARIABLE' => 'feature-variable'
|
361
|
+
'FEATURE_VARIABLE' => 'feature-variable',
|
362
|
+
'ALL_FEATURE_VARIABLES' => 'all-feature-variables'
|
361
363
|
}.freeze
|
362
364
|
|
363
365
|
CONFIG_MANAGER = {
|
364
366
|
'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
|
367
|
+
'AUTHENTICATED_DATAFILE_URL_TEMPLATE' => 'https://config.optimizely.com/datafiles/auth/%s.json',
|
365
368
|
# Default time in seconds to block the 'config' method call until 'config' instance has been initialized.
|
366
369
|
'DEFAULT_BLOCKING_TIMEOUT' => 15,
|
367
370
|
# Default config update interval of 5 minutes
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2020, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'net/http'
|
20
|
+
|
21
|
+
module Optimizely
|
22
|
+
module Helpers
|
23
|
+
module HttpUtils
|
24
|
+
module_function
|
25
|
+
|
26
|
+
def make_request(url, http_method, request_body = nil, headers = {}, read_timeout = nil)
|
27
|
+
# makes http/https GET/POST request and returns response
|
28
|
+
|
29
|
+
uri = URI.parse(url)
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
31
|
+
|
32
|
+
http.read_timeout = read_timeout if read_timeout
|
33
|
+
http.use_ssl = uri.scheme == 'https'
|
34
|
+
|
35
|
+
if http_method == :get
|
36
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
37
|
+
elsif http_method == :post
|
38
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
39
|
+
request.body = request_body if request_body
|
40
|
+
else
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# set headers
|
45
|
+
headers&.each do |key, val|
|
46
|
+
request[key] = val
|
47
|
+
end
|
48
|
+
|
49
|
+
http.request(request)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2017, Optimizely and contributors
|
4
|
+
# Copyright 2017, 2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -48,6 +48,13 @@ module Optimizely
|
|
48
48
|
logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type "\
|
49
49
|
"'#{variable_type}': #{e.message}.")
|
50
50
|
end
|
51
|
+
when 'json'
|
52
|
+
begin
|
53
|
+
return_value = JSON.parse(value)
|
54
|
+
rescue => e
|
55
|
+
logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type "\
|
56
|
+
"'#{variable_type}': #{e.message}.")
|
57
|
+
end
|
51
58
|
else
|
52
59
|
# default case is string
|
53
60
|
return_value = value
|
@@ -17,16 +17,18 @@
|
|
17
17
|
#
|
18
18
|
|
19
19
|
require 'optimizely'
|
20
|
+
require 'optimizely/error_handler'
|
20
21
|
require 'optimizely/event_dispatcher'
|
21
22
|
require 'optimizely/event/batch_event_processor'
|
23
|
+
require 'optimizely/logger'
|
24
|
+
require 'optimizely/notification_center'
|
25
|
+
|
22
26
|
module Optimizely
|
23
27
|
class OptimizelyFactory
|
24
|
-
attr_reader :max_event_batch_size, :max_event_flush_interval
|
25
|
-
|
26
28
|
# Convenience method for setting the maximum number of events contained within a batch.
|
27
29
|
# @param batch_size Integer - Sets size of EventQueue.
|
28
30
|
# @param logger - Optional LoggerInterface Provides a log method to log messages.
|
29
|
-
def self.max_event_batch_size(batch_size, logger)
|
31
|
+
def self.max_event_batch_size(batch_size, logger = NoOpLogger.new)
|
30
32
|
unless batch_size.is_a? Integer
|
31
33
|
logger.log(
|
32
34
|
Logger::ERROR,
|
@@ -48,7 +50,7 @@ module Optimizely
|
|
48
50
|
# Convenience method for setting the maximum time interval in milliseconds between event dispatches.
|
49
51
|
# @param flush_interval Numeric - Time interval between event dispatches.
|
50
52
|
# @param logger - Optional LoggerInterface Provides a log method to log messages.
|
51
|
-
def self.max_event_flush_interval(flush_interval, logger)
|
53
|
+
def self.max_event_flush_interval(flush_interval, logger = NoOpLogger.new)
|
52
54
|
unless flush_interval.is_a? Numeric
|
53
55
|
logger.log(
|
54
56
|
Logger::ERROR,
|
@@ -67,12 +69,42 @@ module Optimizely
|
|
67
69
|
@max_event_flush_interval = flush_interval
|
68
70
|
end
|
69
71
|
|
72
|
+
# Convenience method for setting frequency at which datafile has to be polled and ProjectConfig updated.
|
73
|
+
#
|
74
|
+
# @param polling_interval Numeric - Time in seconds after which to update datafile.
|
75
|
+
def self.polling_interval(polling_interval)
|
76
|
+
@polling_interval = polling_interval
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convenience method for setting timeout to block the config call until config has been initialized.
|
80
|
+
#
|
81
|
+
# @param blocking_timeout Numeric - Time in seconds.
|
82
|
+
def self.blocking_timeout(blocking_timeout)
|
83
|
+
@blocking_timeout = blocking_timeout
|
84
|
+
end
|
85
|
+
|
70
86
|
# Returns a new optimizely instance.
|
71
87
|
#
|
72
88
|
# @params sdk_key - Required String uniquely identifying the fallback datafile corresponding to project.
|
73
89
|
# @param fallback datafile - Optional JSON string datafile.
|
74
90
|
def self.default_instance(sdk_key, datafile = nil)
|
75
|
-
|
91
|
+
error_handler = NoOpErrorHandler.new
|
92
|
+
logger = NoOpLogger.new
|
93
|
+
notification_center = NotificationCenter.new(logger, error_handler)
|
94
|
+
|
95
|
+
config_manager = Optimizely::HTTPProjectConfigManager.new(
|
96
|
+
sdk_key: sdk_key,
|
97
|
+
polling_interval: @polling_interval,
|
98
|
+
blocking_timeout: @blocking_timeout,
|
99
|
+
datafile: datafile,
|
100
|
+
logger: logger,
|
101
|
+
error_handler: error_handler,
|
102
|
+
notification_center: notification_center
|
103
|
+
)
|
104
|
+
|
105
|
+
Optimizely::Project.new(
|
106
|
+
datafile, nil, logger, error_handler, nil, nil, sdk_key, config_manager, notification_center
|
107
|
+
)
|
76
108
|
end
|
77
109
|
|
78
110
|
# Returns a new optimizely instance.
|
@@ -108,10 +140,27 @@ module Optimizely
|
|
108
140
|
config_manager = nil,
|
109
141
|
notification_center = nil
|
110
142
|
)
|
143
|
+
|
144
|
+
error_handler ||= NoOpErrorHandler.new
|
145
|
+
logger ||= NoOpLogger.new
|
146
|
+
notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(logger, error_handler)
|
147
|
+
|
111
148
|
event_processor = BatchEventProcessor.new(
|
112
149
|
event_dispatcher: event_dispatcher || EventDispatcher.new,
|
113
150
|
batch_size: @max_event_batch_size,
|
114
151
|
flush_interval: @max_event_flush_interval,
|
152
|
+
logger: logger,
|
153
|
+
notification_center: notification_center
|
154
|
+
)
|
155
|
+
|
156
|
+
config_manager ||= Optimizely::HTTPProjectConfigManager.new(
|
157
|
+
sdk_key: sdk_key,
|
158
|
+
polling_interval: @polling_interval,
|
159
|
+
blocking_timeout: @blocking_timeout,
|
160
|
+
datafile: datafile,
|
161
|
+
logger: logger,
|
162
|
+
error_handler: error_handler,
|
163
|
+
skip_json_validation: skip_json_validation,
|
115
164
|
notification_center: notification_center
|
116
165
|
)
|
117
166
|
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0.pre.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Optimizely
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -94,20 +94,6 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: httparty
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0.11'
|
104
|
-
type: :runtime
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0.11'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: json-schema
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +164,7 @@ files:
|
|
178
164
|
- lib/optimizely/helpers/date_time_utils.rb
|
179
165
|
- lib/optimizely/helpers/event_tag_utils.rb
|
180
166
|
- lib/optimizely/helpers/group.rb
|
167
|
+
- lib/optimizely/helpers/http_utils.rb
|
181
168
|
- lib/optimizely/helpers/validator.rb
|
182
169
|
- lib/optimizely/helpers/variable_type.rb
|
183
170
|
- lib/optimizely/logger.rb
|
@@ -203,9 +190,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
203
190
|
version: '0'
|
204
191
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
192
|
requirements:
|
206
|
-
- - "
|
193
|
+
- - ">"
|
207
194
|
- !ruby/object:Gem::Version
|
208
|
-
version:
|
195
|
+
version: 1.3.1
|
209
196
|
requirements: []
|
210
197
|
rubygems_version: 3.0.3
|
211
198
|
signing_key:
|