optimizely-sdk 3.4.0 → 3.5.0.pre.beta
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/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:
|