optimizely-sdk 3.3.1 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0eefcbf5e58e06f6e28c1437d3ed456810a4240f
4
- data.tar.gz: c50c1633875300966b654e1fc0b2d246a386a089
2
+ SHA256:
3
+ metadata.gz: c89ea6be1aa10ab05c2d47e74ebe054987e6fc0e08b85417a63b579d80f2fac4
4
+ data.tar.gz: 1f35f5d10017780db25f46e270d64010d0dc5e245bd4b183ae1da8ee1dfb060d
5
5
  SHA512:
6
- metadata.gz: 43c28f74959363c09232e85422cf7995a22163f4a8166300a87379bc05f29e49cae4d0af3cf8f3203b74bd02674d74b34be950b2cc307de0be3accf41371ae29
7
- data.tar.gz: 2d73a7d2a6909f5a1a90287faa5af11579de08a072d5522feb6a7aec54bc3af758a752c6a217642518d4e6bd7eed035b46e186eae8e8bcc9d42e0c98a5d7bc31
6
+ metadata.gz: d2fd93c8952496d3feb12ec4dd8fb8ced95f7123a94447b646b14e157cdaf82af65adfc193943a6efc7ff77069d4600d5006728c876f9ff0d06d9116ad2c58a1
7
+ data.tar.gz: a5ffc4c7db14a01e4bc7741eca6c636b6f41e755a7256fae64703e5241bd6ade20190c8f0b83bdc37d7cb18277439659ce09faa68388c7e74db2d6d3463d470a
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2016-2019, Optimizely and contributors
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.
@@ -33,6 +33,7 @@ require_relative 'optimizely/helpers/validator'
33
33
  require_relative 'optimizely/helpers/variable_type'
34
34
  require_relative 'optimizely/logger'
35
35
  require_relative 'optimizely/notification_center'
36
+ require_relative 'optimizely/optimizely_config'
36
37
 
37
38
  module Optimizely
38
39
  class Project
@@ -70,7 +71,7 @@ module Optimizely
70
71
  )
71
72
  @logger = logger || NoOpLogger.new
72
73
  @error_handler = error_handler || NoOpErrorHandler.new
73
- @event_dispatcher = event_dispatcher || EventDispatcher.new
74
+ @event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
74
75
  @user_profile_service = user_profile_service
75
76
 
76
77
  begin
@@ -429,6 +430,32 @@ module Optimizely
429
430
  variable_value
430
431
  end
431
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
+
432
459
  # Get the Boolean value of the specified variable in the feature flag.
433
460
  #
434
461
  # @param feature_flag_key - String key of feature flag the variable belongs to
@@ -483,6 +510,71 @@ module Optimizely
483
510
  variable_value
484
511
  end
485
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
+
486
578
  # Get the Integer value of the specified variable in the feature flag.
487
579
  #
488
580
  # @param feature_flag_key - String key of feature flag the variable belongs to
@@ -523,6 +615,57 @@ module Optimizely
523
615
  @event_processor.stop! if @event_processor.respond_to?(:stop!)
524
616
  end
525
617
 
618
+ def get_optimizely_config
619
+ # Get OptimizelyConfig object containing experiments and features data
620
+ # Returns Object
621
+ #
622
+ # OptimizelyConfig Object Schema
623
+ # {
624
+ # 'experimentsMap' => {
625
+ # 'my-fist-experiment' => {
626
+ # 'id' => '111111',
627
+ # 'key' => 'my-fist-experiment'
628
+ # 'variationsMap' => {
629
+ # 'variation_1' => {
630
+ # 'id' => '121212',
631
+ # 'key' => 'variation_1',
632
+ # 'variablesMap' => {
633
+ # 'age' => {
634
+ # 'id' => '222222',
635
+ # 'key' => 'age',
636
+ # 'type' => 'integer',
637
+ # 'value' => '0',
638
+ # }
639
+ # }
640
+ # }
641
+ # }
642
+ # }
643
+ # },
644
+ # 'featuresMap' => {
645
+ # 'awesome-feature' => {
646
+ # 'id' => '333333',
647
+ # 'key' => 'awesome-feature',
648
+ # 'experimentsMap' => Object,
649
+ # 'variablesMap' => Object,
650
+ # }
651
+ # },
652
+ # 'revision' => '13',
653
+ # }
654
+ #
655
+ unless is_valid
656
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_optimizely_config').message)
657
+ return nil
658
+ end
659
+
660
+ # config_manager might not contain optimizely_config if its supplied by the consumer
661
+ # Generating a new OptimizelyConfig object in this case as a fallback
662
+ if @config_manager.respond_to?(:optimizely_config)
663
+ @config_manager.optimizely_config
664
+ else
665
+ OptimizelyConfig.new(project_config).config
666
+ end
667
+ end
668
+
526
669
  private
527
670
 
528
671
  def get_variation_with_config(experiment_key, user_id, attributes, config)
@@ -597,8 +740,6 @@ module Optimizely
597
740
  # Error message logged in DatafileProjectConfig- get_feature_flag_from_key
598
741
  return nil if variable.nil?
599
742
 
600
- feature_enabled = false
601
-
602
743
  # If variable_type is nil, set it equal to variable['type']
603
744
  variable_type ||= variable['type']
604
745
  # Returns nil if type differs
@@ -606,43 +747,24 @@ module Optimizely
606
747
  @logger.log(Logger::WARN,
607
748
  "Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
608
749
  return nil
609
- else
610
- source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
611
- decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
612
- variable_value = variable['defaultValue']
613
- if decision
614
- variation = decision['variation']
615
- if decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
616
- source_info = {
617
- experiment_key: decision.experiment['key'],
618
- variation_key: variation['key']
619
- }
620
- source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
621
- end
622
- feature_enabled = variation['featureEnabled']
623
- if feature_enabled == true
624
- variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
625
- variable_id = variable['id']
626
- if variation_variable_usages&.key?(variable_id)
627
- variable_value = variation_variable_usages[variable_id]['value']
628
- @logger.log(Logger::INFO,
629
- "Got variable value '#{variable_value}' for variable '#{variable_key}' of feature flag '#{feature_flag_key}'.")
630
- else
631
- @logger.log(Logger::DEBUG,
632
- "Variable '#{variable_key}' is not used in variation '#{variation['key']}'. Returning the default variable value '#{variable_value}'.")
633
- end
634
- else
635
- @logger.log(Logger::DEBUG,
636
- "Feature '#{feature_flag_key}' for variation '#{variation['key']}' is not enabled. Returning the default variable value '#{variable_value}'.")
637
- end
638
- else
639
- @logger.log(Logger::INFO,
640
- "User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
641
- end
642
750
  end
643
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)
644
757
  variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
645
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
+
646
768
  @notification_center.send_notifications(
647
769
  NotificationCenter::NOTIFICATION_TYPES[:DECISION],
648
770
  Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
@@ -658,6 +780,45 @@ module Optimizely
658
780
  variable_value
659
781
  end
660
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
+
661
822
  def user_inputs_valid?(attributes = nil, event_tags = nil)
662
823
  # Helper method to validate user inputs.
663
824
  #
@@ -701,7 +862,7 @@ module Optimizely
701
862
 
702
863
  return if Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
703
864
 
704
- @event_dispatcher = EventDispatcher.new
865
+ @event_dispatcher = EventDispatcher.new(logger: @logger, error_handler: @error_handler)
705
866
  raise InvalidInputError, 'event_dispatcher'
706
867
  end
707
868
 
@@ -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::INFO,
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
- error_msg = e.class == InvalidDatafileVersionError ? e.message : InvalidInputError.new('datafile').message
163
- error_to_handle = e.class == InvalidDatafileVersionError ? InvalidDatafileVersionError : InvalidInputError
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
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020, Optimizely and contributors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ #
18
+
19
+ module Optimizely
20
+ class ProxyConfig
21
+ attr_reader :host, :port, :username, :password
22
+
23
+ def initialize(host, port = nil, username = nil, password = nil)
24
+ # host - DNS name or IP address of proxy
25
+ # port - port to use to acess the proxy
26
+ # username - username if authorization is required
27
+ # password - password if authorization is required
28
+ @host = host
29
+ @port = port
30
+ @username = username
31
+ @password = password
32
+ end
33
+ end
34
+ end
@@ -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
 
@@ -75,11 +78,12 @@ module Optimizely
75
78
  loop do
76
79
  begin
77
80
  callback.call
78
- rescue
81
+ rescue StandardError => e
79
82
  @logger.log(
80
83
  Logger::ERROR,
81
- 'Something went wrong when running passed function.'
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