-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcore.rb
More file actions
147 lines (117 loc) · 5.09 KB
/
core.rb
File metadata and controls
147 lines (117 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# frozen_string_literal: true
require 'semantic'
require 'securerandom'
require_relative 'environments/models'
require_relative 'features/models'
require_relative 'features/constants'
require_relative 'identities/models'
require_relative 'organisations/models'
require_relative 'projects/models'
require_relative 'segments/evaluator'
require_relative 'segments/models'
require_relative 'utils/hash_func'
require_relative 'mappers'
module Flagsmith
# Core evaluation logic for feature flags
module Engine
extend self
include Flagsmith::Engine::Utils::HashFunc
include Flagsmith::Engine::Features::TargetingReasons
include Flagsmith::Engine::Segments::Evaluator
# Get evaluation result from evaluation context
#
# @param evaluation_context [Hash] The evaluation context
# @return [Hash] Evaluation result with flags and segments
def get_evaluation_result(evaluation_context)
evaluation_context = get_enriched_context(evaluation_context)
segments, segment_overrides = evaluate_segments(evaluation_context)
flags = evaluate_features(evaluation_context, segment_overrides)
{
flags: flags,
segments: segments
}
end
# Returns { segments: EvaluationResultSegments; segmentOverrides: Record<string, SegmentOverride>; }
def evaluate_segments(evaluation_context)
return [], {} if evaluation_context[:segments].nil?
identity_segments = get_segments_from_context(evaluation_context)
segments = identity_segments.map do |segment|
{ name: segment[:name], metadata: segment[:metadata] }.compact
end
segment_overrides = process_segment_overrides(identity_segments)
[segments, segment_overrides]
end
# Returns Record<string: override.name, SegmentOverride>
def process_segment_overrides(identity_segments) # rubocop:disable Metrics/MethodLength
segment_overrides = {}
identity_segments.each do |segment|
Array(segment[:overrides]).each do |override|
next unless should_apply_override(override, segment_overrides)
segment_overrides[override[:name]] = {
feature: override,
segment_name: segment[:name]
}
end
end
segment_overrides
end
def evaluate_features(evaluation_context, segment_overrides)
identity_key = get_identity_key(evaluation_context)
(evaluation_context[:features] || {}).each_with_object({}) do |(_, feature), flags|
segment_override = segment_overrides[feature[:name]]
final_feature = segment_override ? segment_override[:feature] : feature
flag_result = build_flag_result(final_feature, identity_key, segment_override)
flags[final_feature[:name].to_sym] = flag_result
end
end
# Returns {value: any; reason?: string}
def evaluate_feature_value(feature, identity_key = nil)
return get_multivariate_feature_value(feature, identity_key) if feature[:variants]&.any? && identity_key
{ value: feature[:value], reason: nil }
end
# Returns {value: any; reason?: string}
def get_multivariate_feature_value(feature, identity_key)
percentage_value = hashed_percentage_for_object_ids([feature[:key], identity_key])
sorted_variants = (feature[:variants] || []).sort_by { |v| v[:priority] || WEAKEST_PRIORITY }
variant = find_matching_variant(sorted_variants, percentage_value)
variant || { value: feature[:value], reason: nil }
end
def find_matching_variant(sorted_variants, percentage_value)
start_percentage = 0
sorted_variants.each do |variant|
limit = start_percentage + variant[:weight]
return { value: variant[:value], reason: "#{TARGETING_REASON_SPLIT}; weight=#{variant[:weight]}" } if start_percentage <= percentage_value && percentage_value < limit
start_percentage = limit
end
nil
end
# returns boolean
def should_apply_override(override, existing_overrides)
current_override = existing_overrides[override[:name]]
!current_override || stronger_priority?(override[:priority], current_override[:feature][:priority])
end
private
def build_flag_result(feature, identity_key, segment_override)
evaluated = evaluate_feature_value(feature, identity_key)
flag_result = {
name: feature[:name],
enabled: feature[:enabled],
value: evaluated[:value],
reason: evaluated[:reason] || (segment_override ? "#{TARGETING_REASON_TARGETING_MATCH}; segment=#{segment_override[:segment_name]}" : TARGETING_REASON_DEFAULT)
}
flag_result[:metadata] = feature[:metadata] if feature[:metadata]
flag_result
end
# Extract identity key from evaluation context
#
# @param evaluation_context [Hash] The evaluation context
# @return [String, nil] The identity key or nil if no identity
def get_identity_key(evaluation_context)
return nil unless evaluation_context[:identity]
evaluation_context[:identity][:key]
end
def stronger_priority?(priority_a, priority_b)
(priority_a || WEAKEST_PRIORITY) < (priority_b || WEAKEST_PRIORITY)
end
end
end