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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d3a5c579488b2cbe1c3b2c6e489070a0f438dca91b8e5f91d863709c517c978
|
|
4
|
+
data.tar.gz: '089600f43e3c29ef4f83d129dd1f8f58682ae041b1ffe776fad3ff3036140a5e'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81511ab17dfe9e72ffeb81a97752d82a581297dc002aa722ee50e2d12a5faa835f7dc198ea1be5b2b62cec7cb117fd8906177b427d8e8e9eddfab21f24c5d930
|
|
7
|
+
data.tar.gz: 97bac85d2cb9edd8dc4655f3657a42ff4a1c56fcb0f6fd983da8bb5b44baec1c684a63afd16f5612a02c545e53dd84d07853416f8aab79a0ddd00ac88dfbe2dd
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
- '3.4'
|
|
23
23
|
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@v6
|
|
25
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
26
26
|
- name: Set up Ruby
|
|
27
27
|
uses: ruby/setup-ruby@d697be2f83c6234b20877c3b5eac7a7f342f0d0c # v1.269.0
|
|
28
28
|
with:
|
|
@@ -31,7 +31,36 @@ jobs:
|
|
|
31
31
|
- name: Run tests
|
|
32
32
|
run: bundle exec rake
|
|
33
33
|
- name: Upload coverage reports to Codecov
|
|
34
|
-
uses: codecov/codecov-action@v5
|
|
34
|
+
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
|
|
35
35
|
with:
|
|
36
36
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
37
37
|
slug: mixpanel/mixpanel-ruby
|
|
38
|
+
|
|
39
|
+
test-openfeature:
|
|
40
|
+
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
strategy:
|
|
43
|
+
matrix:
|
|
44
|
+
ruby-version:
|
|
45
|
+
- '3.1'
|
|
46
|
+
- '3.2'
|
|
47
|
+
- '3.3'
|
|
48
|
+
- '3.4'
|
|
49
|
+
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
52
|
+
- name: Set up Ruby
|
|
53
|
+
uses: ruby/setup-ruby@d697be2f83c6234b20877c3b5eac7a7f342f0d0c # v1.269.0
|
|
54
|
+
with:
|
|
55
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
56
|
+
- name: Install dependencies
|
|
57
|
+
run: cd openfeature-provider && bundle install
|
|
58
|
+
- name: Run OpenFeature provider tests
|
|
59
|
+
run: cd openfeature-provider && bundle exec rspec
|
|
60
|
+
- name: Upload coverage reports to Codecov
|
|
61
|
+
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
|
|
62
|
+
with:
|
|
63
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
64
|
+
slug: mixpanel/mixpanel-ruby
|
|
65
|
+
flags: openfeature
|
|
66
|
+
directory: openfeature-provider
|
|
@@ -56,25 +56,29 @@ module Mixpanel
|
|
|
56
56
|
request.basic_auth(@provider_config[:token], '')
|
|
57
57
|
|
|
58
58
|
request['Content-Type'] = 'application/json'
|
|
59
|
-
request['traceparent'] = Utils.generate_traceparent
|
|
59
|
+
request['traceparent'] = Utils.generate_traceparent
|
|
60
60
|
|
|
61
61
|
begin
|
|
62
62
|
response = http.request(request)
|
|
63
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
64
|
+
raise ConnectionError.new("Request timeout: #{e.message}")
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
raise ConnectionError.new("Network error: #{e.message}")
|
|
67
|
+
end
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
unless response.code == '200'
|
|
70
|
+
raise ServerError.new("HTTP #{response.code}: #{response.body}")
|
|
71
|
+
end
|
|
67
72
|
|
|
73
|
+
begin
|
|
68
74
|
JSON.parse(response.body)
|
|
69
|
-
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
70
|
-
raise ConnectionError.new("Request timeout: #{e.message}")
|
|
71
75
|
rescue JSON::ParserError => e
|
|
72
76
|
raise ServerError.new("Invalid JSON response: #{e.message}")
|
|
73
|
-
rescue StandardError => e
|
|
74
|
-
raise ConnectionError.new("Network error: #{e.message}")
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
def shutdown; end
|
|
81
|
+
|
|
78
82
|
# Track exposure event to Mixpanel
|
|
79
83
|
# @param flag_key [String] Feature flag key
|
|
80
84
|
# @param selected_variant [SelectedVariant] The selected variant
|
|
@@ -64,6 +64,10 @@ module Mixpanel
|
|
|
64
64
|
@polling_thread = nil
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
def shutdown
|
|
68
|
+
stop_polling_for_definitions!
|
|
69
|
+
end
|
|
70
|
+
|
|
67
71
|
# Check if flag is enabled (for boolean flags)
|
|
68
72
|
# @param flag_key [String] Feature flag key
|
|
69
73
|
# @param context [Hash] Evaluation context (must include 'distinct_id')
|
|
@@ -107,24 +111,17 @@ module Mixpanel
|
|
|
107
111
|
|
|
108
112
|
context_value = context[context_key] || context[context_key.to_sym]
|
|
109
113
|
|
|
110
|
-
selected_variant =
|
|
114
|
+
selected_variant = get_variant_override_for_test_user(flag, context)
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
if test_variant
|
|
114
|
-
selected_variant = test_variant
|
|
115
|
-
else
|
|
116
|
+
unless selected_variant
|
|
116
117
|
rollout = get_assigned_rollout(flag, context_value, context)
|
|
117
|
-
if rollout
|
|
118
|
-
selected_variant = get_assigned_variant(flag, context_value, flag_key, rollout)
|
|
119
|
-
end
|
|
118
|
+
selected_variant = get_assigned_variant(flag, context_value, flag_key, rollout) if rollout
|
|
120
119
|
end
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
track_exposure_event(flag_key, selected_variant, context) if report_exposure
|
|
124
|
-
return selected_variant
|
|
125
|
-
end
|
|
121
|
+
return fallback_variant unless selected_variant
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
track_exposure_event(flag_key, selected_variant, context) if report_exposure
|
|
124
|
+
selected_variant
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
# Get all variants for user context
|
|
@@ -147,11 +144,9 @@ module Mixpanel
|
|
|
147
144
|
def fetch_flag_definitions
|
|
148
145
|
response = call_flags_endpoint
|
|
149
146
|
|
|
150
|
-
new_definitions = {}
|
|
151
|
-
|
|
152
|
-
new_definitions[flag_data['key']] = flag_data
|
|
147
|
+
new_definitions = (response['flags'] || []).each_with_object({}) do |flag_data, definitions|
|
|
148
|
+
definitions[flag_data['key']] = flag_data
|
|
153
149
|
end
|
|
154
|
-
|
|
155
150
|
@flag_definitions = new_definitions
|
|
156
151
|
|
|
157
152
|
response
|
|
@@ -175,9 +170,10 @@ module Mixpanel
|
|
|
175
170
|
end
|
|
176
171
|
|
|
177
172
|
def get_matching_variant(variant_key, flag)
|
|
178
|
-
|
|
173
|
+
variants = flag.dig('ruleset', 'variants')
|
|
174
|
+
return nil unless variants
|
|
179
175
|
|
|
180
|
-
|
|
176
|
+
variants.each do |v|
|
|
181
177
|
if variant_key.downcase == v['key'].downcase
|
|
182
178
|
return SelectedVariant.new(
|
|
183
179
|
variant_key: v['key'],
|
|
@@ -191,9 +187,10 @@ module Mixpanel
|
|
|
191
187
|
end
|
|
192
188
|
|
|
193
189
|
def get_assigned_rollout(flag, context_value, context)
|
|
194
|
-
|
|
190
|
+
rollouts = flag.dig('ruleset', 'rollout')
|
|
191
|
+
return nil unless rollouts
|
|
195
192
|
|
|
196
|
-
|
|
193
|
+
rollouts.each_with_index do |rollout, index|
|
|
197
194
|
salt = if flag['hash_salt']
|
|
198
195
|
"#{flag['key']}#{flag['hash_salt']}#{index}"
|
|
199
196
|
else
|
|
@@ -224,7 +221,7 @@ module Mixpanel
|
|
|
224
221
|
salt = "#{flag_key}#{stored_salt}variant"
|
|
225
222
|
variant_hash = Utils.normalized_hash(context_value.to_s, salt)
|
|
226
223
|
|
|
227
|
-
variants = flag['ruleset']['variants'].map
|
|
224
|
+
variants = flag['ruleset']['variants'].map(&:dup)
|
|
228
225
|
if rollout['variant_splits']
|
|
229
226
|
variants.each do |v|
|
|
230
227
|
v['split'] = rollout['variant_splits'][v['key']] if rollout['variant_splits'].key?(v['key'])
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# mixpanel-ruby-openfeature
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/mixpanel-ruby-openfeature)
|
|
4
|
+
[](https://openfeature.dev/)
|
|
5
|
+
[](https://github.com/mixpanel/mixpanel-ruby/blob/master/LICENSE)
|
|
6
|
+
|
|
7
|
+
An [OpenFeature](https://openfeature.dev/) provider that wraps Mixpanel's feature flags for use with the OpenFeature Ruby SDK. This allows you to use Mixpanel's feature flagging capabilities through OpenFeature's standardized, vendor-agnostic API.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This gem provides a bridge between Mixpanel's native feature flags implementation and the OpenFeature specification. By using this provider, you can:
|
|
12
|
+
|
|
13
|
+
- Leverage Mixpanel's powerful feature flag and experimentation platform
|
|
14
|
+
- Use OpenFeature's standardized API for flag evaluation
|
|
15
|
+
- Easily switch between feature flag providers without changing your application code
|
|
16
|
+
- Integrate with OpenFeature's ecosystem of tools and frameworks
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Add these gems to your `Gemfile`:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem 'mixpanel-ruby-openfeature'
|
|
24
|
+
gem 'openfeature-sdk'
|
|
25
|
+
gem 'mixpanel-ruby'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or install directly:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
gem install mixpanel-ruby-openfeature
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### Local Evaluation (Recommended)
|
|
43
|
+
|
|
44
|
+
Local evaluation downloads flag definitions and evaluates them locally, providing fast, synchronous flag checks with no per-evaluation network requests.
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require 'mixpanel-ruby'
|
|
48
|
+
require 'mixpanel/openfeature'
|
|
49
|
+
|
|
50
|
+
# 1. Create the provider with local evaluation
|
|
51
|
+
provider = Mixpanel::OpenFeature::Provider.from_local(
|
|
52
|
+
'YOUR_PROJECT_TOKEN',
|
|
53
|
+
{ poll_interval: 300 } # poll for updated definitions every 300 seconds
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 2. Register the provider with OpenFeature
|
|
57
|
+
OpenFeature::SDK.configure do |config|
|
|
58
|
+
config.set_provider(provider)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 3. Get a client and evaluate flags
|
|
62
|
+
client = OpenFeature::SDK.build_client
|
|
63
|
+
|
|
64
|
+
show_new_feature = client.fetch_boolean_value(flag_key: 'new-feature-flag', default_value: false)
|
|
65
|
+
|
|
66
|
+
if show_new_feature
|
|
67
|
+
puts 'New feature is enabled!'
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Remote Evaluation
|
|
72
|
+
|
|
73
|
+
Remote evaluation sends each flag check to Mixpanel's servers, which is useful when you need server-side targeting or cannot download flag definitions locally.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
require 'mixpanel-ruby'
|
|
77
|
+
require 'mixpanel/openfeature'
|
|
78
|
+
|
|
79
|
+
# 1. Create the provider with remote evaluation
|
|
80
|
+
provider = Mixpanel::OpenFeature::Provider.from_remote(
|
|
81
|
+
'YOUR_PROJECT_TOKEN',
|
|
82
|
+
{} # remote config options
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 2. Register the provider with OpenFeature
|
|
86
|
+
OpenFeature::SDK.configure do |config|
|
|
87
|
+
config.set_provider(provider)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# 3. Evaluate flags the same way
|
|
91
|
+
client = OpenFeature::SDK.build_client
|
|
92
|
+
value = client.fetch_string_value(flag_key: 'button-color-test', default_value: 'blue')
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Manual Initialization
|
|
96
|
+
|
|
97
|
+
If you already have a Mixpanel `Tracker` instance, you can pass its flags provider directly:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
tracker = Mixpanel::Tracker.new('YOUR_PROJECT_TOKEN', nil, local_flags_config: { poll_interval: 300 })
|
|
101
|
+
flags_provider = tracker.local_flags
|
|
102
|
+
flags_provider.start_polling_for_definitions!
|
|
103
|
+
|
|
104
|
+
provider = Mixpanel::OpenFeature::Provider.new(flags_provider)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Usage Examples
|
|
108
|
+
|
|
109
|
+
### Basic Boolean Flag
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
client = OpenFeature::SDK.build_client
|
|
113
|
+
|
|
114
|
+
is_feature_enabled = client.fetch_boolean_value(flag_key: 'my-feature', default_value: false)
|
|
115
|
+
|
|
116
|
+
if is_feature_enabled
|
|
117
|
+
# Show the new feature
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Mixpanel Flag Types and OpenFeature Evaluation Methods
|
|
122
|
+
|
|
123
|
+
Mixpanel feature flags support three flag types. Use the corresponding OpenFeature evaluation method based on your flag's variant values:
|
|
124
|
+
|
|
125
|
+
| Mixpanel Flag Type | Variant Values | OpenFeature Method |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| Feature Gate | `true` / `false` | `fetch_boolean_value` |
|
|
128
|
+
| Experiment | boolean, string, number, or JSON object | `fetch_boolean_value`, `fetch_string_value`, `fetch_number_value`, or `fetch_object_value` |
|
|
129
|
+
| Dynamic Config | JSON object | `fetch_object_value` |
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
client = OpenFeature::SDK.build_client
|
|
133
|
+
|
|
134
|
+
# Feature Gate - boolean variants
|
|
135
|
+
is_feature_on = client.fetch_boolean_value(flag_key: 'new-checkout', default_value: false)
|
|
136
|
+
|
|
137
|
+
# Experiment with string variants
|
|
138
|
+
button_color = client.fetch_string_value(flag_key: 'button-color-test', default_value: 'blue')
|
|
139
|
+
|
|
140
|
+
# Experiment with number variants
|
|
141
|
+
max_items = client.fetch_number_value(flag_key: 'max-items', default_value: 10)
|
|
142
|
+
|
|
143
|
+
# Dynamic Config - JSON object variants
|
|
144
|
+
feature_config = client.fetch_object_value(
|
|
145
|
+
flag_key: 'homepage-layout',
|
|
146
|
+
default_value: { 'layout' => 'grid', 'items_per_row' => 3 }
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Getting Full Resolution Details
|
|
151
|
+
|
|
152
|
+
If you need additional metadata about the flag evaluation:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
client = OpenFeature::SDK.build_client
|
|
156
|
+
|
|
157
|
+
details = client.fetch_boolean_details(flag_key: 'my-feature', default_value: false)
|
|
158
|
+
|
|
159
|
+
puts details.value # The resolved value
|
|
160
|
+
puts details.variant # The variant key from Mixpanel
|
|
161
|
+
puts details.reason # Why this value was returned
|
|
162
|
+
puts details.error_code # Error code if evaluation failed
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Passing Evaluation Context
|
|
166
|
+
|
|
167
|
+
You can pass evaluation context to provide additional properties for flag evaluation:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
context = OpenFeature::SDK::EvaluationContext.new(
|
|
171
|
+
targeting_key: 'user-123',
|
|
172
|
+
email: 'user@example.com',
|
|
173
|
+
plan: 'premium'
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
value = client.fetch_boolean_value(
|
|
177
|
+
flag_key: 'premium-feature',
|
|
178
|
+
default_value: false,
|
|
179
|
+
evaluation_context: context
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Accessing the Mixpanel Tracker
|
|
184
|
+
|
|
185
|
+
When using the `from_local` or `from_remote` class methods, you can access the underlying Mixpanel tracker for tracking events:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
provider = Mixpanel::OpenFeature::Provider.from_local('YOUR_PROJECT_TOKEN', {})
|
|
189
|
+
|
|
190
|
+
# Access the tracker for event tracking
|
|
191
|
+
provider.mixpanel.track('user-123', 'Page View', { 'page' => '/home' })
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Cleanup
|
|
195
|
+
|
|
196
|
+
When you are done using the provider, shut it down to stop any background polling:
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
provider.shutdown
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Error Handling
|
|
203
|
+
|
|
204
|
+
The provider uses OpenFeature's standard error codes to indicate issues during flag evaluation:
|
|
205
|
+
|
|
206
|
+
### PROVIDER_NOT_READY
|
|
207
|
+
|
|
208
|
+
Returned when flags are evaluated before the provider has finished initializing (e.g., before flag definitions have been fetched for local evaluation).
|
|
209
|
+
|
|
210
|
+
### FLAG_NOT_FOUND
|
|
211
|
+
|
|
212
|
+
Returned when the requested flag does not exist in Mixpanel.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
details = client.fetch_boolean_details(flag_key: 'nonexistent-flag', default_value: false)
|
|
216
|
+
|
|
217
|
+
if details.error_code == OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND
|
|
218
|
+
puts 'Flag does not exist, using default value'
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### TYPE_MISMATCH
|
|
223
|
+
|
|
224
|
+
Returned when the flag value type does not match the requested type.
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
# If 'my-flag' returns a string in Mixpanel but you request a boolean...
|
|
228
|
+
details = client.fetch_boolean_details(flag_key: 'my-flag', default_value: false)
|
|
229
|
+
|
|
230
|
+
if details.error_code == OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH
|
|
231
|
+
puts 'Flag is not a boolean, using default value'
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## FAQ
|
|
236
|
+
|
|
237
|
+
### What Ruby versions are supported?
|
|
238
|
+
|
|
239
|
+
Ruby 3.1.0 or later is required.
|
|
240
|
+
|
|
241
|
+
### What is the difference between local and remote evaluation?
|
|
242
|
+
|
|
243
|
+
**Local evaluation** downloads all flag definitions upfront and evaluates them in-process. This is faster (no network round-trip per evaluation) and works offline after the initial fetch. Use `from_local` for this mode.
|
|
244
|
+
|
|
245
|
+
**Remote evaluation** sends each flag check to Mixpanel's servers. This ensures you always have the latest flag values and supports server-side-only targeting rules. Use `from_remote` for this mode.
|
|
246
|
+
|
|
247
|
+
### Does targetingKey have special meaning?
|
|
248
|
+
|
|
249
|
+
Unlike some feature flag providers, `targetingKey` is not used as a special bucketing key in Mixpanel. It is passed as another context property alongside all other fields. Mixpanel's server-side configuration determines which properties are used for targeting rules and bucketing.
|
|
250
|
+
|
|
251
|
+
### Does the provider call mixpanel.identify()?
|
|
252
|
+
|
|
253
|
+
No. User identity should be managed separately through the Mixpanel tracker's `track` or `people` methods. The provider only handles feature flag evaluation.
|
|
254
|
+
|
|
255
|
+
### How are exposure events tracked?
|
|
256
|
+
|
|
257
|
+
When a flag is successfully resolved, the provider automatically reports an exposure event via `report_exposure: true`. This tracks `$experiment_started` events in Mixpanel for analytics and experimentation reporting.
|
|
258
|
+
|
|
259
|
+
### Can I use this with Rails?
|
|
260
|
+
|
|
261
|
+
Yes. A common pattern is to initialize the provider in an initializer:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# config/initializers/openfeature.rb
|
|
265
|
+
provider = Mixpanel::OpenFeature::Provider.from_local(
|
|
266
|
+
ENV['MIXPANEL_TOKEN'],
|
|
267
|
+
{ poll_interval: 300 }
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
OpenFeature::SDK.configure do |config|
|
|
271
|
+
config.set_provider(provider)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
at_exit { provider.shutdown }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Then use it in controllers or services:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
client = OpenFeature::SDK.build_client
|
|
281
|
+
show_banner = client.fetch_boolean_value(flag_key: 'show-banner', default_value: false)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
Apache-2.0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Releasing the OpenFeature Provider
|
|
2
|
+
|
|
3
|
+
The OpenFeature provider (`mixpanel-ruby-openfeature`) is published to RubyGems independently from the core SDK.
|
|
4
|
+
|
|
5
|
+
## Release Order
|
|
6
|
+
|
|
7
|
+
The OpenFeature provider depends on features added to `mixpanel-ruby` in version 3.1.0+ (e.g., `shutdown` methods on flags providers). You **must** publish the core SDK first:
|
|
8
|
+
|
|
9
|
+
1. Publish `mixpanel-ruby` (bump version in `lib/mixpanel-ruby/version.rb`, build, push)
|
|
10
|
+
2. Verify the new version is live on https://rubygems.org/gems/mixpanel-ruby
|
|
11
|
+
3. Then publish `mixpanel-ruby-openfeature` (steps below)
|
|
12
|
+
|
|
13
|
+
If you update the core SDK version, update the dependency constraint in `mixpanel-ruby-openfeature.gemspec` to match.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Ruby 3.1+
|
|
18
|
+
- A RubyGems account with permission to push to the `mixpanel-ruby-openfeature` gem
|
|
19
|
+
- For the first upload, you'll need owner access or to create the gem under the Mixpanel org
|
|
20
|
+
- Sign in and get your API key at https://rubygems.org/profile/edit
|
|
21
|
+
|
|
22
|
+
## Releasing
|
|
23
|
+
|
|
24
|
+
1. Update the version in `mixpanel-ruby-openfeature.gemspec`
|
|
25
|
+
|
|
26
|
+
2. Build the gem:
|
|
27
|
+
```bash
|
|
28
|
+
cd openfeature-provider
|
|
29
|
+
gem build mixpanel-ruby-openfeature.gemspec
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. Verify the built artifact:
|
|
33
|
+
```bash
|
|
34
|
+
ls *.gem
|
|
35
|
+
# Should show: mixpanel-ruby-openfeature-<version>.gem
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. Push to RubyGems:
|
|
39
|
+
```bash
|
|
40
|
+
gem push mixpanel-ruby-openfeature-<version>.gem
|
|
41
|
+
```
|
|
42
|
+
You'll be prompted for your RubyGems credentials on first push. Alternatively, configure `~/.gem/credentials` with your API key:
|
|
43
|
+
```yaml
|
|
44
|
+
---
|
|
45
|
+
:rubygems_api_key: rubygems_<your-key>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
5. Verify at https://rubygems.org/gems/mixpanel-ruby-openfeature
|
|
49
|
+
|
|
50
|
+
## Versioning
|
|
51
|
+
|
|
52
|
+
The OpenFeature provider is versioned independently from the core SDK. The core SDK dependency is declared in the gemspec (`mixpanel-ruby ~> 3.0`) — update it when the provider needs features from a newer core SDK release.
|