Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a67d321
fix: main-mappers-and-tests
Zaimwa9 Oct 23, 2025
dc19da6
fix: added-metadata
Zaimwa9 Oct 23, 2025
d536c81
fix: renaming
Zaimwa9 Oct 23, 2025
79b3176
fix: comments
Zaimwa9 Oct 23, 2025
45b926e
fix: remoevd-redundant-test
Zaimwa9 Oct 23, 2025
9ea952d
feat: reviewed-engine-test-todos
Zaimwa9 Oct 23, 2025
b9d14a8
Update lib/flagsmith/engine/evaluation/mappers.rb
Zaimwa9 Oct 24, 2025
cb3558b
feat: use-overrides-key-hash
Zaimwa9 Oct 24, 2025
58bfe8b
feat: removed-identifiers-join
Zaimwa9 Oct 24, 2025
3d7fa00
feat: flagsmith-id-in-snake-case
Zaimwa9 Oct 24, 2025
075a8a4
feat: run-lint
Zaimwa9 Oct 28, 2025
73b841c
feat: renamed-module-evaluation
Zaimwa9 Oct 28, 2025
51894ad
feat: fixed-priority-0-being-skipped
Zaimwa9 Oct 28, 2025
e1eabd2
feat: reverted-to-hash
Zaimwa9 Oct 30, 2025
0386836
feat: linter
Zaimwa9 Nov 10, 2025
d86ba90
feat: split-functions-and-module-for-linting
Zaimwa9 Nov 10, 2025
f8ea862
feat: get-rid-of-extra-map-nested-rule-function
Zaimwa9 Nov 10, 2025
b2c3d30
feat: renaming-identity-methods
Zaimwa9 Nov 10, 2025
7de46c2
feat: fixed-forgotten-func-renaming
Zaimwa9 Nov 10, 2025
10b256c
Update lib/flagsmith/engine/evaluation/mappers/environment.rb
Zaimwa9 Nov 10, 2025
6c8dc0b
feat: added-name-env-model-and-fixture
Zaimwa9 Nov 10, 2025
1a908fe
feat: moved-mappers-to-engine-namespace
Zaimwa9 Nov 10, 2025
0c9650a
feat: removed-feature-key
Zaimwa9 Nov 11, 2025
4b106a7
feat: linter
Zaimwa9 Nov 11, 2025
f1026a2
feat: get evaluation get result (#88)
Zaimwa9 Nov 12, 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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "spec/engine-test-data"]
path = spec/engine-test-data
url = [email protected]:Flagsmith/engine-test-data.git
branch = v1.0.0
branch = main
2 changes: 2 additions & 0 deletions lib/flagsmith/engine/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
require_relative 'segments/evaluator'
require_relative 'segments/models'
require_relative 'utils/hash_func'
require_relative 'mappers'
require_relative 'evaluation/core'

module Flagsmith
module Engine
Expand Down
9 changes: 6 additions & 3 deletions lib/flagsmith/engine/environments/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ module Flagsmith
module Engine
# EnvironmentModel
class Environment
attr_reader :id, :api_key
attr_reader :id, :api_key, :name
attr_accessor :project, :feature_states, :amplitude_config, :segment_config,
:mixpanel_config, :heap_config, :identity_overrides

def initialize(id:, api_key:, project:, feature_states: [], identity_overrides: [])
# rubocop:disable Metrics/ParameterLists
def initialize(id:, api_key:, project:, name: nil, feature_states: [], identity_overrides: [])
@id = id
@api_key = api_key
@name = name
@project = project
@feature_states = feature_states
@identity_overrides = identity_overrides
end
# rubocop:enable Metrics/ParameterLists

class << self
# rubocop:disable Metrics/MethodLength
Expand All @@ -28,7 +31,7 @@ def build(json)
Flagsmith::Engine::Identity.build(io)
end

new(**json.slice(:id, :api_key).merge(
new(**json.slice(:id, :api_key, :name).merge(
project: project,
feature_states: feature_states,
identity_overrides: identity_overrides
Expand Down
22 changes: 22 additions & 0 deletions lib/flagsmith/engine/evaluation/core.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Flagsmith
module Engine
module Evaluation
# Core evaluation logic for feature flags
module Core
# Get evaluation result from evaluation context
#
# @param evaluation_context [Hash] The evaluation context
# @return [Hash] Evaluation result with flags and segments
def self.get_evaluation_result(_evaluation_context)
# TODO: Implement core evaluation logic
{
flags: {},
segments: []
}
end
end
end
end
end
66 changes: 66 additions & 0 deletions lib/flagsmith/engine/mappers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require_relative 'mappers/environment'
require_relative 'mappers/identity'
require_relative 'mappers/segments'

module Flagsmith
module Engine
# Mappers for converting between models and evaluation contexts
module Mappers
STRONGEST_PRIORITY = Float::INFINITY
WEAKEST_PRIORITY = -Float::INFINITY

# @param environment [Flagsmith::Engine::Environment] The environment model
# @param identity [Flagsmith::Engine::Identity, nil] Optional identity model
# @param override_traits [Array<Flagsmith::Engine::Identities::Trait>, nil] Optional override traits
# @return [Hash] Evaluation context with environment, features, segments, and optionally identity
def self.get_evaluation_context(environment, identity = nil, override_traits = nil)
context = map_environment_model_to_evaluation_context(environment)
context[:identity] = map_identity_model_to_identity_context(identity, override_traits) if identity
context
end

# Maps environment model to evaluation context
#
# @param environment [Flagsmith::Engine::Environment] The environment model
# @return [Hash] Context with :environment, :features, and :segments keys
def self.map_environment_model_to_evaluation_context(environment)
context = {
environment: Environment.build_environment_context(environment),
features: Environment.build_features_context(environment.feature_states),
segments: Segments.build_segments_context(environment.project.segments)
}

context[:segments].merge!(Identity.map_overrides_to_segments(environment.identity_overrides)) if environment.identity_overrides&.any?

context
end

# Maps identity model to identity context
#
# @param identity [Flagsmith::Engine::Identity] The identity model
# @param override_traits [Array<Flagsmith::Engine::Identities::Trait>, nil] Optional override traits
# @return [Hash] Identity context with :identifier, :key, and :traits
def self.map_identity_model_to_identity_context(identity, override_traits = nil)
Identity.build_environment_context(identity, override_traits)
end

# Maps segment rule model to rule hash
#
# @param rule [Flagsmith::Engine::Segments::Rule] The segment rule model
# @return [Hash] Mapped rule with :type, :conditions, and :rules
def self.map_segment_rule_model_to_rule(rule)
Segments.map_rule(rule)
end

# Maps identity overrides to segments
#
# @param identity_overrides [Array<Flagsmith::Engine::Identity>] Array of identity override models
# @return [Hash] Segments hash for identity overrides
def self.map_identity_overrides_to_segments(identity_overrides)
Identity.map_overrides_to_segments(identity_overrides)
end
end
end
end
60 changes: 60 additions & 0 deletions lib/flagsmith/engine/mappers/environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module Flagsmith
module Engine
module Mappers
# Handles environment and feature mapping
module Environment
def self.build_environment_context(environment)
{
key: environment.api_key,
name: environment.name
}
end

def self.build_features_context(feature_states)
features = {}
feature_states.each do |feature_state|
features[feature_state.feature.name] = build_feature_hash(feature_state)
end
features
end

def self.build_feature_hash(feature_state) # rubocop:disable Metrics/MethodLength
feature_hash = {
key: feature_state.django_id&.to_s || feature_state.uuid,
feature_key: feature_state.feature.id.to_s,
name: feature_state.feature.name,
enabled: feature_state.enabled,
value: feature_state.get_value,
metadata: { flagsmith_id: feature_state.feature.id }
}
add_variants_to_feature(feature_hash, feature_state)
add_priority_to_feature(feature_hash, feature_state)
feature_hash
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,
weight: mv.percentage_allocation,
priority: mv.id || uuid_to_big_int(mv.mv_fs_value_uuid)
}
end
end

def self.uuid_to_big_int(uuid)
uuid.gsub('-', '').to_i(16)
end

def self.add_priority_to_feature(feature_hash, feature_state)
priority = feature_state.feature_segment&.priority
feature_hash[:priority] = priority unless priority.nil?
end
end
end
end
end
98 changes: 98 additions & 0 deletions lib/flagsmith/engine/mappers/identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

module Flagsmith
module Engine
module Mappers
# Handles identity and override mapping
module Identity
def self.build_environment_context(identity, override_traits = nil)
traits = override_traits || identity.identity_traits

{
identifier: identity.identifier,
key: identity.django_id&.to_s || identity.composite_key,
traits: build_traits_hash(traits)
}
end

def self.build_traits_hash(traits)
traits_hash = {}
traits.each do |trait|
traits_hash[trait.trait_key] = trait.trait_value
end
traits_hash
end

def self.map_overrides_to_segments(identity_overrides)
features_to_identifiers = group_by_overrides(identity_overrides)
build_identity_override_segments(features_to_identifiers)
end

def self.group_by_overrides(identity_overrides)
features_to_identifiers = {}

identity_overrides.each do |identity|
next if identity.identity_features.nil? || identity.identity_features.none?

overrides_key = build_overrides_key(identity.identity_features)
overrides_hash = overrides_key.hash

features_to_identifiers[overrides_hash] ||= { identifiers: [], overrides: overrides_key }
features_to_identifiers[overrides_hash][:identifiers] << identity.identifier
end

features_to_identifiers
end

def self.build_overrides_key(identity_features) # rubocop:disable Metrics/MethodLength
sorted_features = identity_features.to_a.sort_by { |fs| fs.feature.name }
sorted_features.map do |feature_state|
{
feature_key: feature_state.feature.id.to_s,
name: feature_state.feature.name,
enabled: feature_state.enabled,
value: feature_state.get_value,
priority: Mappers::STRONGEST_PRIORITY,
metadata: { flagsmith_id: feature_state.feature.id }
}
end
end

def self.build_identity_override_segments(features_to_identifiers)
segments = {}

features_to_identifiers.each do |overrides_hash, data|
segment_key = "identity_override_#{overrides_hash}"
segments[segment_key] = build_identity_override_segment(segment_key, data)
end

segments
end

def self.build_identity_override_segment(segment_key, data)
{
key: segment_key,
name: 'identity_override',
rules: [build_identity_override_rule(data[:identifiers])],
metadata: { source: 'identity_override' },
overrides: data[:overrides]
}
end

def self.build_identity_override_rule(identifiers) # rubocop:disable Metrics/MethodLength
{
type: 'ALL',
conditions: [
{
property: '$.identity.identifier',
operator: 'IN',
value: identifiers
}
],
rules: []
}
end
end
end
end
end
66 changes: 66 additions & 0 deletions lib/flagsmith/engine/mappers/segments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

module Flagsmith
module Engine
module Mappers
# Handles segment and rule mapping
module Segments
def self.build_segments_context(project_segments)
segments = {}
project_segments.each do |segment|
segments[segment.id.to_s] = build_segment_hash(segment)
end
segments
end

def self.build_segment_hash(segment)
{
key: segment.id.to_s,
name: segment.name,
rules: segment.rules.map { |rule| map_rule(rule) },
overrides: build_overrides(segment.feature_states),
metadata: {
source: 'API',
flagsmith_id: segment.id
}
}
end

def self.build_overrides(feature_states) # rubocop:disable Metrics/MethodLength
feature_states.map do |feature_state|
override_hash = {
key: feature_state.django_id&.to_s || feature_state.uuid,
feature_key: feature_state.feature.id.to_s,
name: feature_state.feature.name,
enabled: feature_state.enabled,
value: feature_state.get_value,
metadata: { flagsmith_id: feature_state.feature.id }
}
add_priority_to_override(override_hash, feature_state)
override_hash
end
end

def self.add_priority_to_override(override_hash, feature_state)
return unless feature_state.feature_segment&.priority

override_hash[:priority] = feature_state.feature_segment.priority
end

def self.map_rule(rule)
{
type: rule.type,
conditions: map_conditions(rule.conditions),
rules: (rule.rules || []).map { |nested_rule| map_rule(nested_rule) }
}
end

def self.map_conditions(conditions)
(conditions || []).map do |condition|
{ property: condition.property, operator: condition.operator, value: condition.value }
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion spec/engine-test-data
Submodule engine-test-data updated 190 files
Loading