diff --git a/.github/workflows/test-harness.yml b/.github/workflows/test-harness.yml index d1957ea..9eb3c9e 100644 --- a/.github/workflows/test-harness.yml +++ b/.github/workflows/test-harness.yml @@ -14,3 +14,4 @@ jobs: with: sdks-to-test: ruby sdk-github-sha: ${{github.event.pull_request.head.sha}} + sdk-capabilities: '{ "Ruby": ["clientCustomData", "v2Config", "allVariables", "allFeatures", "evalReason", "eventsEvalReason", "variablesFeatureId"]}' diff --git a/lib/devcycle-ruby-server-sdk.rb b/lib/devcycle-ruby-server-sdk.rb index a344fdb..8495b05 100644 --- a/lib/devcycle-ruby-server-sdk.rb +++ b/lib/devcycle-ruby-server-sdk.rb @@ -29,6 +29,7 @@ require 'devcycle-ruby-server-sdk/eval_hooks_runner' require 'devcycle-ruby-server-sdk/models/eval_hook' require 'devcycle-ruby-server-sdk/models/eval_hook_context' +require 'devcycle-ruby-server-sdk/eval_reasons' # APIs require 'devcycle-ruby-server-sdk/api/client' diff --git a/lib/devcycle-ruby-server-sdk/api/client.rb b/lib/devcycle-ruby-server-sdk/api/client.rb index 797be03..5261730 100644 --- a/lib/devcycle-ruby-server-sdk/api/client.rb +++ b/lib/devcycle-ruby-server-sdk/api/client.rb @@ -195,6 +195,7 @@ def variable(user, key, default, opts = {}) value = default type = determine_variable_type(default) defaulted = true + eval = { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED } if local_bucketing_initialized? && @local_bucketing.has_config type_code = variable_type_code_from_type(type) variable_pb = variable_for_user_pb(user, key, type_code) @@ -202,9 +203,11 @@ def variable(user, key, default, opts = {}) value = get_variable_value(variable_pb) defaulted = false end + eval = get_eval_reason(variable_pb) else + eval = { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::MISSING_CONFIG } @logger.warn("Local bucketing not initialized, returning default value for variable #{key}") - variable_event = Event.new({ type: DevCycle::EventTypes[:agg_variable_defaulted], target: key }) + variable_event = Event.new({ type: DevCycle::EventTypes[:agg_variable_defaulted], target: key, metaData: { evalReason: DevCycle::EVAL_REASONS::DEFAULT }}) bucketed_config = BucketedUserConfig.new({}, {}, {}, {}, {}, {}, []) @event_queue.queue_aggregate_event(variable_event, bucketed_config) end @@ -214,7 +217,8 @@ def variable(user, key, default, opts = {}) value: value, type: type, defaultValue: default, - isDefaulted: defaulted + isDefaulted: defaulted, + eval: eval }) end @@ -574,6 +578,18 @@ def get_variable_value(variable_pb) end end + def get_eval_reason(variable_pb) + if variable_pb.nil? + { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED} + else + if variable_pb.eval.nil? + { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED } + else + { reason: variable_pb.eval.reason, details: variable_pb.eval.details, target_id: variable_pb.eval.target_id } + end + end + end + # Adds an eval hook to the client # @param [EvalHook] eval_hook The eval hook to add # @return [void] diff --git a/lib/devcycle-ruby-server-sdk/eval_reasons.rb b/lib/devcycle-ruby-server-sdk/eval_reasons.rb new file mode 100644 index 0000000..01f58de --- /dev/null +++ b/lib/devcycle-ruby-server-sdk/eval_reasons.rb @@ -0,0 +1,13 @@ +module DevCycle + # Evaluation reasons for successful evaluations + module EVAL_REASONS + DEFAULT = 'DEFAULT' + ERROR = 'ERROR' + end + + # Default reason details + module DEFAULT_REASON_DETAILS + MISSING_CONFIG = 'Missing Config' + USER_NOT_TARGETED = 'User Not Targeted' + end +end diff --git a/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm b/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm index f3c33d6..80994ca 100644 Binary files a/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm and b/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm differ diff --git a/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto b/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto index f774a6a..c61b062 100644 --- a/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto +++ b/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto @@ -66,4 +66,12 @@ message SDKVariable_PB { double doubleValue = 5; string stringValue = 6; NullableString evalReason = 7; + NullableString _feature = 8; + EvalReason_PB eval = 9; } + +message EvalReason_PB { + string reason = 1; + string details = 2; + string target_id = 3; +} \ No newline at end of file diff --git a/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb b/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb index 33f3587..8fd2d43 100644 --- a/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb +++ b/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb @@ -1,69 +1,34 @@ +# frozen_string_literal: true # Generated by the protocol buffer compiler. DO NOT EDIT! # source: variableForUserParams.proto require 'google/protobuf' -Google::Protobuf::DescriptorPool.generated_pool.build do - add_file("variableForUserParams.proto", :syntax => :proto3) do - add_message "proto.NullableString" do - optional :value, :string, 1 - optional :isNull, :bool, 2 - end - add_message "proto.NullableDouble" do - optional :value, :double, 1 - optional :isNull, :bool, 2 - end - add_message "proto.CustomDataValue" do - optional :type, :enum, 1, "proto.CustomDataType" - optional :boolValue, :bool, 2 - optional :doubleValue, :double, 3 - optional :stringValue, :string, 4 - end - add_message "proto.NullableCustomData" do - map :value, :string, :message, 1, "proto.CustomDataValue" - optional :isNull, :bool, 2 - end - add_message "proto.VariableForUserParams_PB" do - optional :sdkKey, :string, 1 - optional :variableKey, :string, 2 - optional :variableType, :enum, 3, "proto.VariableType_PB" - optional :user, :message, 4, "proto.DVCUser_PB" - optional :shouldTrackEvent, :bool, 5 - end - add_message "proto.DVCUser_PB" do - optional :user_id, :string, 1 - optional :email, :message, 2, "proto.NullableString" - optional :name, :message, 3, "proto.NullableString" - optional :language, :message, 4, "proto.NullableString" - optional :country, :message, 5, "proto.NullableString" - optional :appBuild, :message, 6, "proto.NullableDouble" - optional :appVersion, :message, 7, "proto.NullableString" - optional :deviceModel, :message, 8, "proto.NullableString" - optional :customData, :message, 9, "proto.NullableCustomData" - optional :privateCustomData, :message, 10, "proto.NullableCustomData" - end - add_message "proto.SDKVariable_PB" do - optional :_id, :string, 1 - optional :type, :enum, 2, "proto.VariableType_PB" - optional :key, :string, 3 - optional :boolValue, :bool, 4 - optional :doubleValue, :double, 5 - optional :stringValue, :string, 6 - optional :evalReason, :message, 7, "proto.NullableString" - end - add_enum "proto.VariableType_PB" do - value :Boolean, 0 - value :Number, 1 - value :String, 2 - value :JSON, 3 - end - add_enum "proto.CustomDataType" do - value :Bool, 0 - value :Num, 1 - value :Str, 2 - value :Null, 3 + +descriptor_data = "\n\x1bvariableForUserParams.proto\x12\x05proto\"/\n\x0eNullableString\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"/\n\x0eNullableDouble\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"s\n\x0f\x43ustomDataValue\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.proto.CustomDataType\x12\x11\n\tboolValue\x18\x02 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x03 \x01(\x01\x12\x13\n\x0bstringValue\x18\x04 \x01(\t\"\x9f\x01\n\x12NullableCustomData\x12\x33\n\x05value\x18\x01 \x03(\x0b\x32$.proto.NullableCustomData.ValueEntry\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\x1a\x44\n\nValueEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.proto.CustomDataValue:\x02\x38\x01\"\xa8\x01\n\x18VariableForUserParams_PB\x12\x0e\n\x06sdkKey\x18\x01 \x01(\t\x12\x13\n\x0bvariableKey\x18\x02 \x01(\t\x12,\n\x0cvariableType\x18\x03 \x01(\x0e\x32\x16.proto.VariableType_PB\x12\x1f\n\x04user\x18\x04 \x01(\x0b\x32\x11.proto.DVCUser_PB\x12\x18\n\x10shouldTrackEvent\x18\x05 \x01(\x08\"\x9e\x03\n\nDVCUser_PB\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05\x65mail\x18\x02 \x01(\x0b\x32\x15.proto.NullableString\x12#\n\x04name\x18\x03 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08language\x18\x04 \x01(\x0b\x32\x15.proto.NullableString\x12&\n\x07\x63ountry\x18\x05 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08\x61ppBuild\x18\x06 \x01(\x0b\x32\x15.proto.NullableDouble\x12)\n\nappVersion\x18\x07 \x01(\x0b\x32\x15.proto.NullableString\x12*\n\x0b\x64\x65viceModel\x18\x08 \x01(\x0b\x32\x15.proto.NullableString\x12-\n\ncustomData\x18\t \x01(\x0b\x32\x19.proto.NullableCustomData\x12\x34\n\x11privateCustomData\x18\n \x01(\x0b\x32\x19.proto.NullableCustomData\"\x85\x02\n\x0eSDKVariable_PB\x12\x0b\n\x03_id\x18\x01 \x01(\t\x12$\n\x04type\x18\x02 \x01(\x0e\x32\x16.proto.VariableType_PB\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x11\n\tboolValue\x18\x04 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x05 \x01(\x01\x12\x13\n\x0bstringValue\x18\x06 \x01(\t\x12)\n\nevalReason\x18\x07 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08_feature\x18\x08 \x01(\x0b\x32\x15.proto.NullableString\x12\"\n\x04\x65val\x18\t \x01(\x0b\x32\x14.proto.EvalReason_PB\"C\n\rEvalReason_PB\x12\x0e\n\x06reason\x18\x01 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x02 \x01(\t\x12\x11\n\ttarget_id\x18\x03 \x01(\t*@\n\x0fVariableType_PB\x12\x0b\n\x07\x42oolean\x10\x00\x12\n\n\x06Number\x10\x01\x12\n\n\x06String\x10\x02\x12\x08\n\x04JSON\x10\x03*6\n\x0e\x43ustomDataType\x12\x08\n\x04\x42ool\x10\x00\x12\x07\n\x03Num\x10\x01\x12\x07\n\x03Str\x10\x02\x12\x08\n\x04Null\x10\x03\x62\x06proto3" + +pool = Google::Protobuf::DescriptorPool.generated_pool + +begin + pool.add_serialized_file(descriptor_data) +rescue TypeError => e + # Compatibility code: will be removed in the next major version. + require 'google/protobuf/descriptor_pb' + parsed = Google::Protobuf::FileDescriptorProto.decode(descriptor_data) + parsed.clear_dependency + serialized = parsed.class.encode(parsed) + file = pool.add_serialized_file(serialized) + warn "Warning: Protobuf detected an import path issue while loading generated file #{__FILE__}" + imports = [ + ] + imports.each do |type_name, expected_filename| + import_file = pool.lookup(type_name).file_descriptor + if import_file.name != expected_filename + warn "- #{file.name} imports #{expected_filename}, but that import was loaded as #{import_file.name}" end end + warn "Each proto file must use a consistent fully-qualified name." + warn "This will become an error in the next major version." end module Proto @@ -74,6 +39,7 @@ module Proto VariableForUserParams_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.VariableForUserParams_PB").msgclass DVCUser_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.DVCUser_PB").msgclass SDKVariable_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.SDKVariable_PB").msgclass + EvalReason_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.EvalReason_PB").msgclass VariableType_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.VariableType_PB").enummodule CustomDataType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.CustomDataType").enummodule end diff --git a/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh b/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh index aae4eee..83a2434 100755 --- a/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh +++ b/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh @@ -1,5 +1,5 @@ #!/bin/bash -BUCKETING_LIB_VERSION="1.35.1" +BUCKETING_LIB_VERSION="1.41.0" WAT_DOWNLOAD=0 rm bucketing-lib.release.wasm -wget "https://unpkg.com/@devcycle/bucketing-assembly-script@$BUCKETING_LIB_VERSION/build/bucketing-lib.release.wasm" \ No newline at end of file +wget "https://unpkg.com/@devcycle/bucketing-assembly-script@$BUCKETING_LIB_VERSION/build/bucketing-lib.release.wasm" diff --git a/lib/devcycle-ruby-server-sdk/models/variable.rb b/lib/devcycle-ruby-server-sdk/models/variable.rb index 3fbd4b3..675c3c8 100644 --- a/lib/devcycle-ruby-server-sdk/models/variable.rb +++ b/lib/devcycle-ruby-server-sdk/models/variable.rb @@ -29,6 +29,9 @@ class Variable attr_accessor :defaultValue + # Variable evaluation details + attr_accessor :eval + class EnumAttributeValidator attr_reader :datatype attr_reader :allowable_values @@ -58,7 +61,8 @@ def self.attribute_map :'type' => :'type', :'value' => :'value', :'defaultValue' => :'defaultValue', - :'isDefaulted' => :'isDefaulted' + :'isDefaulted' => :'isDefaulted', + :'eval' => :'eval' } end @@ -120,6 +124,10 @@ def initialize(attributes = {}) if attributes.key?(:'defaultValue') self.defaultValue = attributes[:'defaultValue'] end + + if attributes.key?(:'eval') + self.eval = attributes[:'eval'] + end end # Show invalid properties with the reasons. Usually used together with valid? diff --git a/spec/api/devcycle_api_spec.rb b/spec/api/devcycle_api_spec.rb index b0a0ce6..437c210 100644 --- a/spec/api/devcycle_api_spec.rb +++ b/spec/api/devcycle_api_spec.rb @@ -21,16 +21,18 @@ sdk_key = ENV["DEVCYCLE_SERVER_SDK_KEY"] if sdk_key.nil? puts("SDK KEY NOT SET - SKIPPING INIT") - return + @skip_tests = true + else + # run before each test + options = DevCycle::Options.new(enable_cloud_bucketing: true) + @api_instance = DevCycle::Client.new(sdk_key, options) + + @user = DevCycle::User.new({ + user_id: 'test-user', + appVersion: '1.2.3' + }) + @skip_tests = false end - # run before each test - options = DevCycle::Options.new(enable_cloud_bucketing: true) - @api_instance = DevCycle::Client.new(sdk_key, options) - - @user = DevCycle::User.new({ - user_id: 'test-user', - appVersion: '1.2.3' - }) end after do