optimizely-sdk 3.3.2.rc1 → 3.6.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
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