Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31a145d
Update CHANGES.txt
chillaq Jun 16, 2025
3fd624e
Update CHANGES.txt
chillaq Jun 17, 2025
6969b26
Merge pull request #562 from splitio/release-8.6.0
chillaq Jun 17, 2025
116da66
Merge pull request #563 from splitio/development
chillaq Jun 17, 2025
8e03763
Updated engine and sync classes
chillaq Jul 2, 2025
f8a9d93
Removed properties if nil in sender
chillaq Jul 2, 2025
e18d900
Merge pull request #564 from splitio/FME-4369-imp-prop-engine
chillaq Jul 2, 2025
4bc21d5
Update client
chillaq Jul 3, 2025
35fd494
Updated integration tests
chillaq Jul 7, 2025
9997c3d
Merge pull request #565 from splitio/FME-4368-imp-prop-client
chillaq Jul 7, 2025
2d7d41d
Merge pull request #566 from splitio/FME-4370-imp-prop-integrations
chillaq Jul 7, 2025
d977a91
Created EvaluationOptions class
chillaq Jul 10, 2025
bbde652
upgraded jwt
chillaq Jul 10, 2025
d0dd013
Merge pull request #569 from splitio/upgrade-jwt
chillaq Jul 11, 2025
897c28f
polish
chillaq Jul 11, 2025
f122208
Merge pull request #568 from splitio/FME-4374-imp-prop-options-class
chillaq Jul 14, 2025
f4aeda4
fix rbs repository
chillaq Jul 23, 2025
db90929
added test
chillaq Jul 23, 2025
28bdff9
Merge pull request #570 from splitio/fix-rbs-repository
chillaq Jul 23, 2025
5a5c724
Added deprecate warn and updated version and changes
chillaq Jul 31, 2025
eeb835c
Merge pull request #571 from splitio/prepare-release-8.7.0
chillaq Jul 31, 2025
d8584ec
Fixed typo
chillaq Jul 31, 2025
7b6e192
Merge branch 'development' into feature/impressions-properties
chillaq Jul 31, 2025
3b0e2ec
Fixed deprecated param value
chillaq Aug 1, 2025
76ac3f9
polishing
chillaq Aug 1, 2025
42ee023
Merge pull request #572 from splitio/feature/impressions-properties
chillaq Aug 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
CHANGES

8.6.0 (Jun xx, 2025)
8.7.0 (Aug 1, 2025)
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.

8.6.0 (Jun 17, 2025)
- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.

Expand Down
1 change: 1 addition & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
require 'splitclient-rb/engine/models/segment_type'
require 'splitclient-rb/engine/models/treatment'
require 'splitclient-rb/engine/models/split_http_response'
require 'splitclient-rb/engine/models/evaluation_options'
require 'splitclient-rb/engine/auth_api_client'
require 'splitclient-rb/engine/back_off'
require 'splitclient-rb/engine/push_manager'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ def clear

def contains?(segment_names)
return false if rule_based_segment_names.empty?
return set(segment_names).subset?(rule_based_segment_names)

return segment_names.to_set.subset?(rule_based_segment_names.to_set)
end

private
Expand Down
34 changes: 24 additions & 10 deletions lib/splitclient-rb/cache/senders/impressions_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,28 @@ def feature_impressions(filtered_impressions, feature)

def current_impressions(feature_impressions)
feature_impressions.map do |impression|
{
k: impression[:i][:k],
t: impression[:i][:t],
m: impression[:i][:m],
b: impression[:i][:b],
r: impression[:i][:r],
c: impression[:i][:c],
pt: impression[:i][:pt]
}
if impression[:i][:properties].nil?
impression = {
k: impression[:i][:k],
t: impression[:i][:t],
m: impression[:i][:m],
b: impression[:i][:b],
r: impression[:i][:r],
c: impression[:i][:c],
pt: impression[:i][:pt]
}
else
impression = {
k: impression[:i][:k],
t: impression[:i][:t],
m: impression[:i][:m],
b: impression[:i][:b],
r: impression[:i][:r],
c: impression[:i][:c],
pt: impression[:i][:pt],
properties: impression[:i][:properties].to_json.to_s
}
end
end
end

Expand Down Expand Up @@ -73,7 +86,8 @@ def impression_hash(impression)
"#{impression[:i][:b]}:" \
"#{impression[:i][:c]}:" \
"#{impression[:i][:t]}:" \
"#{impression[:i][:pt]}"
"#{impression[:i][:pt]}" \
"#{impression[:i][:properties]}" \
end
end
end
Expand Down
96 changes: 63 additions & 33 deletions lib/splitclient-rb/clients/split_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,64 +35,67 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage
end

def get_treatment(
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
multiple = false, evaluator = nil
)
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple)
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')

result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_options)
return result.tap { |t| t.delete(:config) } if multiple
result[:treatment]
end

def get_treatment_with_config(
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
multiple = false, evaluator = nil
)
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple)
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
end

def get_treatments(key, split_names, attributes = {})
treatments = treatments(key, split_names, attributes)
def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
treatments = treatments(key, split_names, attributes, evaluation_options)

return treatments if treatments.nil?
keys = treatments.keys
treats = treatments.map { |_,t| t[:treatment] }
Hash[keys.zip(treats)]
end

def get_treatments_with_config(key, split_names, attributes = {})
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG)
def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
end

def get_treatments_by_flag_set(key, flag_set, attributes = {})
def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET)
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SET)
return treatments if treatments.nil?
keys = treatments.keys
treats = treatments.map { |_,t| t[:treatment] }
Hash[keys.zip(treats)]
end

def get_treatments_by_flag_sets(key, flag_sets, attributes = {})
def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS)
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SETS)
return treatments if treatments.nil?
keys = treatments.keys
treats = treatments.map { |_,t| t[:treatment] }
Hash[keys.zip(treats)]
end

def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {})
def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
end

def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {})
def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
end

def destroy
Expand Down Expand Up @@ -135,10 +138,7 @@ def track(key, traffic_type_name, event_type, value = nil, properties = nil)
if !properties.nil?
properties, size = validate_properties(properties)
properties_size += size
if (properties_size > EVENTS_SIZE_THRESHOLD)
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. Event not queued")
return false
end
return false unless check_properties_size(properties_size)
end

if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
Expand All @@ -163,6 +163,14 @@ def block_until_ready(time = nil)

private

def check_properties_size(properties_size, msg = "Event not queued")
if (properties_size > EVENTS_SIZE_THRESHOLD)
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}")
return false
end
return true
end

def keys_from_key(key)
case key
when Hash
Expand Down Expand Up @@ -206,7 +214,7 @@ def sanitize_split_names(calling_method, split_names)
end
end

def validate_properties(properties)
def validate_properties(properties, method = 'Event')
properties_count = 0
size = 0

Expand All @@ -225,11 +233,21 @@ def validate_properties(properties)
end
}

@config.logger.warn('Event has more than 300 properties. Some of them will be trimmed when processed') if properties_count > 300
@config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300

return fixed_properties, size
end

def validate_evaluation_options(evaluation_options)
if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions)
@config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil")
return nil, 0
end
evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment')
return evaluation_options, size
end

def valid_client
if @destroyed
@config.logger.error('Client has already been destroyed - no calls possible')
Expand All @@ -238,8 +256,7 @@ def valid_client
@config.valid_mode
end

def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments')
attributes = {} if attributes.nil?
def treatments(key, feature_flag_names, attributes = {}, evaluation_options = nil, calling_method = 'get_treatments')
sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)

if sanitized_feature_flag_names.nil?
Expand All @@ -255,6 +272,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
bucketing_key, matching_key = keys_from_key(key)
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
matching_key = matching_key ? matching_key.to_s : nil
attributes = parsed_attributes(attributes)

if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
to_return = Hash.new
Expand All @@ -269,7 +287,9 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
to_return = Hash.new
sanitized_feature_flag_names.each {|name|
to_return[name.to_sym] = control_treatment_with_config
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }), :disabled => false }
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil },
evaluation_options), :disabled => false }
}
@impressions_manager.track(impressions)
return to_return
Expand All @@ -291,7 +311,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
invalid_treatments[key] = control_treatment_with_config
next
end
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
treatments[key] =
{
treatment: treatments_labels_change_numbers[:treatment],
Expand All @@ -313,8 +333,12 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true,
calling_method = 'get_treatment', multiple = false)
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil,
calling_method = 'get_treatment', multiple = false, evaluation_options = nil)

log_deprecated_warning(calling_method, split_data, 'split_data')
log_deprecated_warning(calling_method, store_impressions, 'store_impressions')

impressions = []
bucketing_key, matching_key = keys_from_key(key)

Expand All @@ -332,13 +356,17 @@ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_i
end

feature_flag = @splits_repository.get_split(feature_flag_name)
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, evaluation_options)

@impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
treatments
end

def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
def log_deprecated_warning(calling_method, parameter, parameter_name)
@config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil?
end

def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil)
impressions_decorator = []
begin
start = Time.now
Expand All @@ -359,18 +387,20 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_
impressions_disabled = false
end

evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil?
evaluation_options.properties = nil unless evaluation_options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")

record_latency(calling_method, start)
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }), :disabled => impressions_disabled }
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }, evaluation_options), :disabled => impressions_disabled }
impressions_decorator << impression_decorator unless impression_decorator.nil?
rescue StandardError => e
@config.log_found_exception(__method__.to_s, e)
record_exception(calling_method)
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }), :disabled => false }
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
impressions_decorator << impression_decorator unless impression_decorator.nil?

return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator
end

return parsed_treatment(treatment_data, multiple), impressions_decorator
end

Expand Down
Loading