optimizely-sdk 3.3.2.rc1 → 3.6.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
2
  SHA256:
3
- metadata.gz: 4b941180fbc22ae8f59eff475967cb6f130c4845e2a47324c3e18252b4a35685
4
- data.tar.gz: 560e8ce82a288fb98c6d088264b0df6ff1cd4b0d3aba162d72ab1541a9fe76ee
3
+ metadata.gz: 4d8204dce32c85bea56820607c0268b363fb86e7506ce685a2675645b4ef581b
4
+ data.tar.gz: dce7cd63fde5e24e179a74f8f14c6a22ed389028faac38a373af105683b596f0
5
5
  SHA512:
6
- metadata.gz: 66dbbf2dcbfe72ba85201b552f5d8a1e10c33eb109c18e51a56e46582754fcc21ad86d7d0ce487e032e872e135c2671269451c788447c527d8a2b8498fd1421e
7
- data.tar.gz: 3ca3f728b5c3c20355e192f26a50913194cebf4fe3a446cddd94a72daecc9c0f50d74af7c36863cfcde5dd02e93baea2d14fb0137a6b0e5e3481d135ff323ae0
6
+ metadata.gz: 5cc88f9802b38a2eb29354507303c6dcdcf6a402f721d24689f1e42a52a79319568974cf3584c6bf94398ff5f1410aacf49a1adf7aa27ef9105f3e7b23d39e3d
7
+ data.tar.gz: a6f0a7f052bb851ce760c4c88e0fe0dea18c14b42d5bcf457ac9cfefe3eaa2027f1b847c2ab0c133233618502cfe9207b1e82a5b1d65d0d4a678b58f5ea0ff88
@@ -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
@@ -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,46 @@ 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 value is not defined. Returning the default variable value '#{variable_value}' for variable '#{variable['key']}'.")
810
+
811
+ end
812
+ else
813
+ @logger.log(Logger::DEBUG,
814
+ "Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'. Returning the default variable value '#{variable_value}'.")
815
+ end
816
+ else
817
+ @logger.log(Logger::INFO,
818
+ "User '#{user_id}' was not bucketed into experiment or rollout for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
819
+ end
820
+ variable_value
821
+ end
822
+
661
823
  def user_inputs_valid?(attributes = nil, event_tags = nil)
662
824
  # Helper method to validate user inputs.
663
825
  #
@@ -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.
@@ -24,23 +24,32 @@ module Optimizely
24
24
  module Audience
25
25
  module_function
26
26
 
27
- def user_in_experiment?(config, experiment, attributes, logger)
28
- # Determine for given experiment if user satisfies the audiences for the experiment.
27
+ def user_meets_audience_conditions?(config, experiment, attributes, logger, logging_hash = nil, logging_key = nil)
28
+ # Determine for given experiment/rollout rule if user satisfies the audience conditions.
29
29
  #
30
30
  # config - Representation of the Optimizely project config.
31
- # experiment - Experiment for which visitor is to be bucketed.
31
+ # experiment - Experiment/Rollout rule in which user is to be bucketed.
32
32
  # attributes - Hash representing user attributes which will be used in determining if
33
33
  # the audience conditions are met.
34
+ # logger - Provides a logger instance.
35
+ # logging_hash - Optional string representing logs hash inside Helpers::Constants.
36
+ # This defaults to 'EXPERIMENT_AUDIENCE_EVALUATION_LOGS'.
37
+ # logging_key - Optional string to be logged as an identifier of experiment under evaluation.
38
+ # This defaults to experiment['key'].
34
39
  #
35
40
  # Returns boolean representing if user satisfies audience conditions for the audiences or not.
41
+ logging_hash ||= 'EXPERIMENT_AUDIENCE_EVALUATION_LOGS'
42
+ logging_key ||= experiment['key']
43
+
44
+ logs_hash = Object.const_get "Optimizely::Helpers::Constants::#{logging_hash}"
36
45
 
37
46
  audience_conditions = experiment['audienceConditions'] || experiment['audienceIds']
38
47
 
39
48
  logger.log(
40
49
  Logger::DEBUG,
41
50
  format(
42
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCES_COMBINED'],
43
- experiment['key'],
51
+ logs_hash['EVALUATING_AUDIENCES_COMBINED'],
52
+ logging_key,
44
53
  audience_conditions
45
54
  )
46
55
  )
@@ -50,8 +59,8 @@ module Optimizely
50
59
  logger.log(
51
60
  Logger::INFO,
52
61
  format(
53
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
54
- experiment['key'],
62
+ logs_hash['AUDIENCE_EVALUATION_RESULT_COMBINED'],
63
+ logging_key,
55
64
  'TRUE'
56
65
  )
57
66
  )
@@ -74,7 +83,7 @@ module Optimizely
74
83
  logger.log(
75
84
  Logger::DEBUG,
76
85
  format(
77
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCE'],
86
+ logs_hash['EVALUATING_AUDIENCE'],
78
87
  audience_id,
79
88
  audience_conditions
80
89
  )
@@ -84,8 +93,8 @@ module Optimizely
84
93
  result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_custom_attr)
85
94
  result_str = result.nil? ? 'UNKNOWN' : result.to_s.upcase
86
95
  logger.log(
87
- Logger::INFO,
88
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
96
+ Logger::DEBUG,
97
+ format(logs_hash['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
89
98
  )
90
99
  result
91
100
  end
@@ -97,8 +106,8 @@ module Optimizely
97
106
  logger.log(
98
107
  Logger::INFO,
99
108
  format(
100
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
101
- experiment['key'],
109
+ logs_hash['AUDIENCE_EVALUATION_RESULT_COMBINED'],
110
+ logging_key,
102
111
  eval_result.to_s.upcase
103
112
  )
104
113
  )
@@ -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.
@@ -39,7 +39,7 @@ module Optimizely
39
39
  # Determines ID of variation to be shown for a given experiment key and user ID.
40
40
  #
41
41
  # project_config - Instance of ProjectConfig
42
- # experiment - Experiment for which visitor is to be bucketed.
42
+ # experiment - Experiment or Rollout rule for which visitor is to be bucketed.
43
43
  # bucketing_id - String A customer-assigned value used to generate the bucketing key
44
44
  # user_id - String ID for user.
45
45
  #
@@ -47,6 +47,7 @@ module Optimizely
47
47
  return nil if experiment.nil?
48
48
 
49
49
  # check if experiment is in a group; if so, check if user is bucketed into specified experiment
50
+ # this will not affect evaluation of rollout rules.
50
51
  experiment_id = experiment['id']
51
52
  experiment_key = experiment['key']
52
53
  group_id = experiment['groupId']
@@ -82,11 +83,6 @@ module Optimizely
82
83
  variation_id = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
83
84
  if variation_id && variation_id != ''
84
85
  variation = project_config.get_variation_from_id(experiment_key, variation_id)
85
- variation_key = variation ? variation['key'] : nil
86
- @logger.log(
87
- Logger::INFO,
88
- "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
89
- )
90
86
  return variation
91
87
  end
92
88
 
@@ -98,7 +94,6 @@ module Optimizely
98
94
  )
99
95
  end
100
96
 
101
- @logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
102
97
  nil
103
98
  end
104
99
 
@@ -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.
@@ -24,6 +24,7 @@ module Optimizely
24
24
  RUNNING_EXPERIMENT_STATUS = ['Running'].freeze
25
25
  RESERVED_ATTRIBUTE_PREFIX = '$opt_'
26
26
 
27
+ attr_reader :datafile
27
28
  attr_reader :account_id
28
29
  attr_reader :attributes
29
30
  attr_reader :audiences
@@ -62,6 +63,7 @@ module Optimizely
62
63
 
63
64
  config = JSON.parse(datafile)
64
65
 
66
+ @datafile = datafile
65
67
  @error_handler = error_handler
66
68
  @logger = logger
67
69
  @version = config['version']
@@ -82,6 +84,17 @@ module Optimizely
82
84
  @revision = config['revision']
83
85
  @rollouts = config.fetch('rollouts', [])
84
86
 
87
+ # Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
88
+ # Converting it to a first-class json type while creating Project Config
89
+ @feature_flags.each do |feature_flag|
90
+ feature_flag['variables'].each do |variable|
91
+ if variable['type'] == 'string' && variable['subType'] == 'json'
92
+ variable['type'] = 'json'
93
+ variable.delete('subType')
94
+ end
95
+ end
96
+ end
97
+
85
98
  # Utility maps for quick lookup
86
99
  @attribute_key_map = generate_key_map(@attributes, 'key')
87
100
  @event_key_map = generate_key_map(@events, 'key')
@@ -159,8 +172,9 @@ module Optimizely
159
172
  config = new(datafile, logger, error_handler)
160
173
  rescue StandardError => e
161
174
  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
175
+ error_to_handle = e.class == InvalidDatafileVersionError ? e : InvalidInputError.new('datafile')
176
+ error_msg = error_to_handle.message
177
+
164
178
  default_logger.log(Logger::ERROR, error_msg)
165
179
  error_handler.handle_error error_to_handle
166
180
  return nil