mixpanel-ruby 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +31 -2
- data/lib/mixpanel-ruby/flags/flags_provider.rb +12 -8
- data/lib/mixpanel-ruby/flags/local_flags_provider.rb +19 -22
- data/lib/mixpanel-ruby/version.rb +1 -1
- data/openfeature-provider/Gemfile +7 -0
- data/openfeature-provider/README.md +286 -0
- data/openfeature-provider/RELEASE.md +52 -0
- data/openfeature-provider/lib/mixpanel/openfeature/provider.rb +170 -0
- data/openfeature-provider/lib/mixpanel/openfeature.rb +3 -0
- data/openfeature-provider/mixpanel-ruby-openfeature.gemspec +23 -0
- data/openfeature-provider/spec/mixpanel_openfeature_provider_spec.rb +606 -0
- data/openfeature-provider/spec/spec_helper.rb +23 -0
- metadata +11 -3
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open_feature/sdk'
|
|
4
|
+
|
|
5
|
+
module Mixpanel
|
|
6
|
+
module OpenFeature
|
|
7
|
+
class Provider
|
|
8
|
+
attr_reader :metadata, :mixpanel
|
|
9
|
+
|
|
10
|
+
def self.from_local(token, config, error_handler: nil)
|
|
11
|
+
tracker = ::Mixpanel::Tracker.new(token, error_handler, local_flags_config: config)
|
|
12
|
+
flags_provider = tracker.local_flags
|
|
13
|
+
flags_provider.start_polling_for_definitions!
|
|
14
|
+
provider = new(flags_provider)
|
|
15
|
+
provider.instance_variable_set(:@mixpanel, tracker)
|
|
16
|
+
provider
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_remote(token, config, error_handler: nil)
|
|
20
|
+
tracker = ::Mixpanel::Tracker.new(token, error_handler, remote_flags_config: config)
|
|
21
|
+
flags_provider = tracker.remote_flags
|
|
22
|
+
provider = new(flags_provider)
|
|
23
|
+
provider.instance_variable_set(:@mixpanel, tracker)
|
|
24
|
+
provider
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(flags_provider)
|
|
28
|
+
@flags_provider = flags_provider
|
|
29
|
+
@metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: 'mixpanel-provider').freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def shutdown
|
|
33
|
+
@flags_provider.shutdown if @flags_provider.respond_to?(:shutdown)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
37
|
+
resolve(flag_key, default_value, :boolean, evaluation_context)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
|
41
|
+
resolve(flag_key, default_value, :string, evaluation_context)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
|
|
45
|
+
resolve(flag_key, default_value, :number, evaluation_context)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
|
49
|
+
resolve(flag_key, default_value, :integer, evaluation_context)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
|
53
|
+
resolve(flag_key, default_value, :float, evaluation_context)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
|
|
57
|
+
resolve(flag_key, default_value, :object, evaluation_context)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def resolve(flag_key, default_value, expected_type, evaluation_context)
|
|
63
|
+
unless flags_ready?
|
|
64
|
+
return error_result(default_value, ::OpenFeature::SDK::Provider::ErrorCode::PROVIDER_NOT_READY)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context = build_context(evaluation_context)
|
|
68
|
+
fallback = ::Mixpanel::Flags::SelectedVariant.new(variant_value: default_value)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
result = @flags_provider.get_variant(flag_key, fallback, context, report_exposure: true)
|
|
72
|
+
rescue StandardError
|
|
73
|
+
return error_result(default_value, ::OpenFeature::SDK::Provider::ErrorCode::GENERAL)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if result.equal?(fallback)
|
|
77
|
+
return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
78
|
+
value: default_value,
|
|
79
|
+
error_code: ::OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
|
|
80
|
+
reason: ::OpenFeature::SDK::Provider::Reason::DEFAULT
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
value = result.variant_value
|
|
85
|
+
|
|
86
|
+
if value.nil? && expected_type != :object
|
|
87
|
+
return error_result(default_value, ::OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
coerced = coerce_value(value, expected_type)
|
|
91
|
+
if coerced.nil? && !value.nil?
|
|
92
|
+
return error_result(default_value, ::OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
96
|
+
value: coerced.nil? ? value : coerced,
|
|
97
|
+
variant: result.variant_key,
|
|
98
|
+
reason: ::OpenFeature::SDK::Provider::Reason::TARGETING_MATCH
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def coerce_value(value, expected_type)
|
|
103
|
+
case expected_type
|
|
104
|
+
when :boolean
|
|
105
|
+
value == true || value == false ? value : nil
|
|
106
|
+
when :string
|
|
107
|
+
value.is_a?(String) ? value : nil
|
|
108
|
+
when :integer
|
|
109
|
+
if value.is_a?(Integer)
|
|
110
|
+
value
|
|
111
|
+
elsif value.is_a?(Float) && value.finite? && value == value.floor
|
|
112
|
+
value.to_i
|
|
113
|
+
end
|
|
114
|
+
when :float
|
|
115
|
+
if value.is_a?(Float)
|
|
116
|
+
value
|
|
117
|
+
elsif value.is_a?(Integer)
|
|
118
|
+
value.to_f
|
|
119
|
+
end
|
|
120
|
+
when :number
|
|
121
|
+
value.is_a?(Numeric) ? value : nil
|
|
122
|
+
when :object
|
|
123
|
+
value
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def build_context(evaluation_context)
|
|
128
|
+
return {} if evaluation_context.nil?
|
|
129
|
+
|
|
130
|
+
ctx = {}
|
|
131
|
+
if evaluation_context.respond_to?(:fields)
|
|
132
|
+
evaluation_context.fields.each { |k, v| ctx[k] = unwrap_value(v) }
|
|
133
|
+
end
|
|
134
|
+
if evaluation_context.respond_to?(:targeting_key) && evaluation_context.targeting_key
|
|
135
|
+
ctx['targetingKey'] = unwrap_value(evaluation_context.targeting_key)
|
|
136
|
+
end
|
|
137
|
+
ctx
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def unwrap_value(value)
|
|
141
|
+
case value
|
|
142
|
+
when Float
|
|
143
|
+
value.finite? && value == value.floor ? value.to_i : value
|
|
144
|
+
when Array
|
|
145
|
+
value.map { |v| unwrap_value(v) }
|
|
146
|
+
when Hash
|
|
147
|
+
value.transform_values { |v| unwrap_value(v) }
|
|
148
|
+
else
|
|
149
|
+
value
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def flags_ready?
|
|
154
|
+
if @flags_provider.respond_to?(:are_flags_ready)
|
|
155
|
+
@flags_provider.are_flags_ready
|
|
156
|
+
else
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def error_result(default_value, error_code)
|
|
162
|
+
::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
163
|
+
value: default_value,
|
|
164
|
+
error_code: error_code,
|
|
165
|
+
reason: ::OpenFeature::SDK::Provider::Reason::ERROR
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'mixpanel-ruby-openfeature'
|
|
5
|
+
spec.version = '0.1.0'
|
|
6
|
+
spec.authors = ['Mixpanel']
|
|
7
|
+
spec.email = 'support@mixpanel.com'
|
|
8
|
+
spec.summary = 'OpenFeature provider for Mixpanel feature flags'
|
|
9
|
+
spec.description = 'An OpenFeature provider that wraps the Mixpanel Ruby SDK feature flags'
|
|
10
|
+
spec.homepage = 'https://mixpanel.com'
|
|
11
|
+
spec.license = 'Apache-2.0'
|
|
12
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
13
|
+
|
|
14
|
+
spec.files = Dir['lib/**/*.rb']
|
|
15
|
+
spec.require_paths = ['lib']
|
|
16
|
+
|
|
17
|
+
spec.add_runtime_dependency 'openfeature-sdk', '~> 0.5'
|
|
18
|
+
spec.add_runtime_dependency 'mixpanel-ruby', '~> 3.1'
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
21
|
+
spec.add_development_dependency 'simplecov'
|
|
22
|
+
spec.add_development_dependency 'simplecov-cobertura'
|
|
23
|
+
end
|