Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/sdk-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ permissions:
jobs:
sdk-spec:
# Keep the ref in `uses:` and `spec-ref` in sync.
uses: Flagsmith/sdk-spec/.github/workflows/run-spec.yml@v0.7.1
uses: Flagsmith/sdk-spec/.github/workflows/run-spec.yml@v0.9.0
with:
spec-ref: v0.7.1
tags: core or events or local-evaluation
spec-ref: v0.9.0
tags: core events local-evaluation
language: ruby
24 changes: 21 additions & 3 deletions lib/flagsmith.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class Client # rubocop:disable Metrics/ClassLength
#
# flag = flagsmith.get_experiment_flag('foo', 'identifier', plan: 'premium')
# flagsmith.track_event('purchase', identifier: 'identifier', value: 42)
#
# Close the client before the application stops, so that its buffered events are sent:
#
# flagsmith.close

# Available Configs.
#
Expand Down Expand Up @@ -221,8 +225,7 @@ def get_value_for_identity(feature_name, user_id = nil, default: nil)

# Get a flag for an identity, taking the same traits as get_identity_flags, and record the
# identity's exposure to the variant it was served when it is in the feature's running experiment.
# Requires enable_events. Exposure needs remote evaluation: the environment document used by local
# evaluation and offline handlers carries no experiment data, so no exposure is recorded from it.
# Requires enable_events.
def get_experiment_flag(feature_name, identifier, **traits)
require_events!
flag = get_identity_flags(identifier, **traits)[feature_name]
Expand Down Expand Up @@ -260,6 +263,21 @@ def track_exposure_event(feature_name, identifier:, value: nil, traits: nil, met
metadata: metadata)
end

# Send the events tracked so far, and return once each of them has been delivered, dropped, or kept for the
# next flush, retries included. Requires enable_events.
def flush_events
require_events!
event_processor.flush
end

# Stop the client's background work, and send the buffered events, waiting for at most +timeout_seconds+.
# Events not sent by then are lost.
def close(timeout_seconds: Flagsmith::EventProcessor::CLOSE_TIMEOUT_SECONDS)
@environment_data_polling_manager&.stop
@realtime_client&.running = false
@event_processor&.stop(timeout_seconds)
end

def get_identity_segments(identifier, traits = {})
raise Flagsmith::ClientError, 'Local evaluation or offline handler is required to obtain identity segments.' unless environment

Expand All @@ -283,7 +301,6 @@ def require_events!
def exposure_skip_reason(flag)
return 'feature not found' unless flag.is_a?(Flagsmith::Flags::Flag)
return 'feature is disabled' unless flag.enabled?
return 'flags come from the environment document, which carries no experiment data' if environment
return 'identity is not in a running experiment' unless flag.experiment&.in_experiment?

'flag has no variant' if flag.variant.nil?
Expand All @@ -300,6 +317,7 @@ def environment_flags_from_document # rubocop:disable Metrics/MethodLength

Flagsmith::Flags::Collection.from_evaluation_result(
evaluation_result,
identity: false,
analytics_processor: analytics_processor,
default_flag_handler: default_flag_handler,
offline_handler: offline_handler
Expand Down
24 changes: 12 additions & 12 deletions lib/flagsmith/engine/features/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Engine
class FeatureState
include Flagsmith::Engine::Utils::HashFunc

attr_reader :feature, :enabled, :django_id, :uuid, :feature_segment
attr_reader :feature, :enabled, :django_id, :uuid, :feature_segment, :experiment
attr_accessor :multivariate_feature_state_values

def initialize(params = {})
Expand All @@ -19,6 +19,7 @@ def initialize(params = {})
@feature_state_value = params.fetch(:feature_state_value, nil)
@uuid = params.fetch(:uuid, SecureRandom.uuid)
@multivariate_feature_state_values = params.fetch(:multivariate_feature_state_values, [])
@experiment = params.fetch(:experiment, nil)
end

attr_writer :feature_state_value
Expand Down Expand Up @@ -66,14 +67,12 @@ def higher_segment_priority?(other)
class << self
def build(json)
multivariate_feature_state_values = build_multivariate_values(json[:multivariate_feature_state_values])
attributes = json.slice(:uuid, :enabled, :django_id, :feature_state_value)
.merge(feature: Flagsmith::Engine::Feature.build(json[:feature]))
.merge(multivariate_feature_state_values: multivariate_feature_state_values)
if json.key?(:feature_segment) && !json[:feature_segment].nil?
attributes = attributes.merge(
feature_segment: Flagsmith::Engine::Features::Segment.new(json[:feature_segment])
)
end
attributes = json.slice(:uuid, :enabled, :django_id, :feature_state_value).merge(
feature: Flagsmith::Engine::Feature.build(json[:feature]),
multivariate_feature_state_values: multivariate_feature_state_values,
experiment: json.dig(:metadata, :experiment)
)
attributes[:feature_segment] = Flagsmith::Engine::Features::Segment.new(json[:feature_segment]) unless json[:feature_segment].nil?
new(**attributes)
end

Expand Down Expand Up @@ -113,16 +112,17 @@ def build(json)
module Features
# MultivariateFeatureOptionModel
class MultivariateOption
attr_reader :value, :id
attr_reader :value, :id, :key

def initialize(value:, id: nil)
def initialize(value:, id: nil, key: nil)
@value = value
@id = id
@key = key
end

class << self
def build(json)
new(**json.slice(:id, :value))
new(**json.slice(:id, :value, :key))
end
end
end
Expand Down
12 changes: 10 additions & 2 deletions lib/flagsmith/engine/mappers/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,30 @@ def self.build_feature_hash(feature_state)
name: feature_state.feature.name,
enabled: feature_state.enabled,
value: feature_state.get_value,
metadata: { id: feature_state.feature.id }
metadata: build_feature_metadata(feature_state)
}
add_variants_to_feature(feature_hash, feature_state)
add_priority_to_feature(feature_hash, feature_state)
feature_hash
end

# The running experiment a feature state belongs to, if any, is passed through to its flag
def self.build_feature_metadata(feature_state)
metadata = { id: feature_state.feature.id }
metadata[:experiment] = feature_state.experiment if feature_state.experiment
metadata
end

def self.add_variants_to_feature(feature_hash, feature_state)
return unless feature_state.multivariate_feature_state_values&.any?

feature_hash[:variants] = feature_state.multivariate_feature_state_values.map do |mv|
{
value: mv.multivariate_feature_option.value,
key: mv.multivariate_feature_option.key,
weight: mv.percentage_allocation,
priority: mv.id || uuid_to_big_int(mv.mv_fs_value_uuid)
}
}.compact
end
end

Expand Down
Loading
Loading