optimizely-sdk 3.3.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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