From 876489cb26aa817220b8b9f1729b19b23c22502c Mon Sep 17 00:00:00 2001 From: hermeswaldemarin Date: Tue, 14 Nov 2023 22:37:05 +0000 Subject: [PATCH] Adding Custom Field Support --- lib/context.rb | 83 +++++++++++++++++++ lib/json/experiment.rb | 13 ++- lib/json_expr/expr_evaluator.rb | 6 +- spec/context_spec.rb | 38 +++++++++ .../default_context_data_deserializer_spec.rb | 8 ++ spec/fixtures/resources/context.json | 31 ++++++- 6 files changed, 170 insertions(+), 9 deletions(-) diff --git a/lib/context.rb b/lib/context.rb index a2028c6..964065d 100644 --- a/lib/context.rb +++ b/lib/context.rb @@ -21,6 +21,7 @@ def self.create(clock, config, data_future, data_provider, def initialize(clock, config, data_future, data_provider, event_handler, event_logger, variable_parser, audience_matcher) @index = [] + @context_custom_fields = {} @achievements = [] @assignment_cache = {} @assignments = {} @@ -203,6 +204,52 @@ def variable_value(key, default_value) default_value end + def custom_field_keys + check_ready?(true) + keys = [] + + @data.experiments.each do |experiment| + custom_field_values = experiment.custom_field_values + if custom_field_values != nil + custom_field_values.each do |custom_field| + keys.append(custom_field.name) + end + end + end + + return keys.sort.uniq + end + + def custom_field_value(experimentName, key) + check_ready?(true) + + experiment_custom_fields = @context_custom_fields[experimentName] + + if experiment_custom_fields != nil + field = experiment_custom_fields[key] + if field != nil + return field.value + end + end + + return nil + end + + def custom_field_type(experimentName, key) + check_ready?(true) + + experiment_custom_fields = @context_custom_fields[experimentName] + + if experiment_custom_fields != nil + field = experiment_custom_fields[key] + if field != nil + return field.type + end + end + + return nil + end + def peek_variable_value(key, default_value) check_ready?(true) @@ -451,8 +498,11 @@ def assign_data(data) @data = data @index = {} @index_variables = {} + if data && !data.experiments.nil? && !data.experiments.empty? data.experiments.each do |experiment| + @experimentCustomFieldValues = {} + experiment_variables = ExperimentVariables.new experiment_variables.data = experiment experiment_variables.variables ||= [] @@ -467,7 +517,36 @@ def assign_data(data) end end + if !experiment.custom_field_values.nil? + experiment.custom_field_values.each do |custom_field_value| + value = ContextCustomFieldValues.new + value.type = custom_field_value.type + + if !custom_field_value.value.nil? + custom_value = custom_field_value.value + + if custom_field_value.type.start_with?("json") + value.value = @variable_parser.parse(self, experiment.name, custom_field_value.name, custom_value) + + elsif custom_field_value.type.start_with?("boolean") + value.value = custom_value.to_bool + + elsif custom_field_value.type.start_with?("number") + value.value = custom_value.to_i + + else + value.value = custom_field_value.value + end + + @experimentCustomFieldValues[custom_field_value.name] = value + + end + + end + end + @index[experiment.name] = experiment_variables + @context_custom_fields[experiment.name] = @experimentCustomFieldValues end end end @@ -544,5 +623,9 @@ class ExperimentVariables attr_accessor :data, :variables end +class ContextCustomFieldValues + attr_accessor :type, :value +end + class IllegalStateException < StandardError end diff --git a/lib/json/experiment.rb b/lib/json/experiment.rb index 35d43d5..42e70e3 100644 --- a/lib/json/experiment.rb +++ b/lib/json/experiment.rb @@ -3,11 +3,12 @@ require_relative "../string" require_relative "experiment_application" require_relative "experiment_variant" +require_relative "custom_field_value" class Experiment attr_accessor :id, :name, :unit_type, :iteration, :seed_hi, :seed_lo, :split, :traffic_seed_hi, :traffic_seed_lo, :traffic_split, :full_on_variant, - :applications, :variants, :audience_strict, :audience + :applications, :variants, :audience_strict, :audience, :custom_field_values def initialize(args = {}) args.each do |name, value| @@ -15,6 +16,10 @@ def initialize(args = {}) @applications = assign_to_klass(ExperimentApplication, value) elsif name == :variants @variants = assign_to_klass(ExperimentVariant, value) + elsif name == :customFieldValues + if value != nil + @custom_field_values = assign_to_klass(CustomFieldValue, value) + end else self.instance_variable_set("@#{name.to_s.underscore}", value) end @@ -42,7 +47,7 @@ def ==(o) @unit_type == that.unit_type && @split == that.split && @traffic_split == that.traffic_split && @applications == that.applications && @variants == that.variants && @audience_strict == that.audience_strict && - @audience == that.audience + @audience == that.audience && @custom_field_values == that.custom_field_values end def hash_code @@ -57,7 +62,8 @@ def hash_code traffic_seed_lo: @traffic_seed_lo, full_on_variant: @full_on_variant, audience_strict: @audience_strict, - audience: @audience + audience: @audience, + custom_field_values: @custom_field_values } end @@ -78,6 +84,7 @@ def to_s ", variants=#{@variants.join}" + ", audienceStrict=#{@audience_strict}" + ", audience='#{@audience}'" + + ", custom_field_values='#{@custom_field_values}'" + "}" end end diff --git a/lib/json_expr/expr_evaluator.rb b/lib/json_expr/expr_evaluator.rb index 0aaa087..788ea92 100644 --- a/lib/json_expr/expr_evaluator.rb +++ b/lib/json_expr/expr_evaluator.rb @@ -32,7 +32,7 @@ def evaluate(expr) def boolean_convert(x) if x.is_a?(TrueClass) || x.is_a?(FalseClass) return x - elsif x.is_a?(Numeric) || !(x =~ NUMERIC_REGEX).nil? + elsif x.is_a?(Numeric) || !(x.to_s =~ NUMERIC_REGEX).nil? return !x.to_f.zero? elsif x.is_a?(String) return x != "false" && x != "0" && x != "" @@ -44,7 +44,7 @@ def boolean_convert(x) def number_convert(x) return if x.nil? || x.to_s.empty? - if x.is_a?(Numeric) || !(x =~ NUMERIC_REGEX).nil? + if x.is_a?(Numeric) || !(x.to_s =~ NUMERIC_REGEX).nil? return x.to_f elsif x.is_a?(TrueClass) || x.is_a?(FalseClass) return x ? 1.0 : 0.0 @@ -57,7 +57,7 @@ def string_convert(x) return x elsif x.is_a?(TrueClass) || x.is_a?(FalseClass) return x.to_s - elsif x.is_a?(Numeric) || !(x =~ NUMERIC_REGEX).nil? + elsif x.is_a?(Numeric) || !(x.to_s =~ NUMERIC_REGEX).nil? return x == x.to_i ? x.to_i.to_s : x.to_s end nil diff --git a/spec/context_spec.rb b/spec/context_spec.rb index 8542af6..7fa2bcb 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -698,6 +698,44 @@ def faraday_response(content) expect(variable_experiments).to eq(context.variable_keys) end + it "getCustomFieldKeys" do + context = create_context(data_future_ready) + + expect(["country", "languages", "overrides"]).to eq(context.custom_field_keys) + end + + it "getCustomFieldValues" do + context = create_context(data_future_ready) + + expect(context.custom_field_value("not_found", "not_found")).to be_nil + expect(context.custom_field_value("exp_test_ab", key: "not_found")).to be_nil + expect(context.custom_field_value("exp_test_ab", "country")).to eq("US,PT,ES,DE,FR") + expect(context.custom_field_type("exp_test_ab", "country")).to eq("string") + + data = {"123": 1, "456": 0} + expect(context.custom_field_value("exp_test_ab", "overrides")).to eq(data) + expect(context.custom_field_type("exp_test_ab", "overrides")).to eq("json") + + expect(context.custom_field_value("exp_test_ab", "languages")).to be_nil + expect(context.custom_field_type("exp_test_ab", "languages")).to be_nil + + expect(context.custom_field_value("exp_test_abc", "overrides")).to be_nil + expect(context.custom_field_type("exp_test_abc", "overrides")).to be_nil + + expect(context.custom_field_value("exp_test_abc", "languages")).to eq("en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX") + expect(context.custom_field_type("exp_test_abc", "languages")).to eq("string") + + expect(context.custom_field_value("exp_test_no_custom_fields", "country")).to be_nil + expect(context.custom_field_type("exp_test_no_custom_fields", "country")).to be_nil + + expect(context.custom_field_type("exp_test_no_custom_fields", "overrides")).to be_nil + expect(context.custom_field_value("exp_test_no_custom_fields", "overrides")).to be_nil + + expect(context.custom_field_type("exp_test_no_custom_fields", "languages")).to be_nil + expect(context.custom_field_value("exp_test_no_custom_fields", "languages")).to be_nil + + end + it "peek_treatmentReturnsOverrideVariant" do context = create_ready_context diff --git a/spec/default_context_data_deserializer_spec.rb b/spec/default_context_data_deserializer_spec.rb index 775d6f2..de605b9 100644 --- a/spec/default_context_data_deserializer_spec.rb +++ b/spec/default_context_data_deserializer_spec.rb @@ -32,6 +32,10 @@ ExperimentVariant.new("A", nil), ExperimentVariant.new("B", "{\"banner.border\":1,\"banner.size\":\"large\"}") ] + experiment0.custom_field_values = [ + CustomFieldValue.new("country", "US,PT,ES,DE,FR", "string"), + CustomFieldValue.new("overrides", "{\"123\":1,\"456\":0}", "json"), + ] experiment0.audience_strict = false experiment0.audience = nil @@ -53,6 +57,10 @@ ExperimentVariant.new("B", "{\"button.color\":\"blue\"}"), ExperimentVariant.new("C", "{\"button.color\":\"red\"}") ] + experiment1.custom_field_values = [ + CustomFieldValue.new("country", "US,PT,ES,DE,FR", "string"), + CustomFieldValue.new("languages", "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", "string"), + ] experiment1.audience_strict = false experiment1.audience = "" diff --git a/spec/fixtures/resources/context.json b/spec/fixtures/resources/context.json index 6d5c4fa..036ed53 100644 --- a/spec/fixtures/resources/context.json +++ b/spec/fixtures/resources/context.json @@ -33,7 +33,19 @@ "config":"{\"banner.border\":1,\"banner.size\":\"large\"}" } ], - "audience": null + "audience": null, + "customFieldValues": [ + { + "name": "country", + "value": "US,PT,ES,DE,FR", + "type": "string" + }, + { + "name": "overrides", + "value": "{\"123\":1,\"456\":0}", + "type": "json" + } + ] }, { "id":2, @@ -73,7 +85,19 @@ "config":"{\"button.color\":\"red\"}" } ], - "audience": "" + "audience": "", + "customFieldValues": [ + { + "name": "country", + "value": "US,PT,ES,DE,FR", + "type": "string" + }, + { + "name": "languages", + "value": "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", + "type": "string" + } + ] }, { "id":3, @@ -113,7 +137,8 @@ "config":"{\"card.width\":\"75%\"}" } ], - "audience": "{}" + "audience": "{}", + "customFieldValues": null }, { "id":4,