openfeature-sdk 0.6.4 → 0.6.6
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/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.tool-versions +1 -1
- data/.yardopts +5 -0
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +4 -1
- data/CONTRIBUTING.md +15 -1
- data/Gemfile +17 -2
- data/Gemfile.lock +98 -67
- data/README.md +24 -8
- data/Rakefile +14 -0
- data/SECURITY.md +10 -0
- data/Steepfile +7 -0
- data/examples/basic_usage.rb +22 -0
- data/examples/custom_provider.rb +56 -0
- data/examples/rails_integration.rb +60 -0
- data/lib/open_feature/sdk/api.rb +1 -0
- data/lib/open_feature/sdk/client.rb +2 -3
- data/lib/open_feature/sdk/configuration.rb +26 -13
- data/lib/open_feature/sdk/evaluation_error.rb +28 -0
- data/lib/open_feature/sdk/hooks/hook_executor.rb +5 -5
- data/lib/open_feature/sdk/hooks/logging_hook.rb +1 -2
- data/lib/open_feature/sdk/provider/in_memory_provider.rb +24 -0
- data/lib/open_feature/sdk/provider_event.rb +5 -1
- data/lib/open_feature/sdk/provider_state_registry.rb +2 -2
- data/lib/open_feature/sdk/version.rb +1 -1
- data/openfeature-sdk.gemspec +34 -0
- data/rbs_collection.lock.yaml +292 -0
- data/rbs_collection.yaml +14 -0
- data/renovate.json +1 -1
- data/sig/open_feature/sdk/api.rbs +35 -0
- data/sig/open_feature/sdk/client.rbs +49 -0
- data/sig/open_feature/sdk/client_metadata.rbs +11 -0
- data/sig/open_feature/sdk/configuration.rbs +44 -0
- data/sig/open_feature/sdk/evaluation_context.rbs +19 -0
- data/sig/open_feature/sdk/evaluation_context_builder.rbs +7 -0
- data/sig/open_feature/sdk/evaluation_details.rbs +19 -0
- data/sig/open_feature/sdk/evaluation_error.rbs +11 -0
- data/sig/open_feature/sdk/event_dispatcher.rbs +24 -0
- data/sig/open_feature/sdk/hooks/hints.rbs +30 -0
- data/sig/open_feature/sdk/hooks/hook.rbs +19 -0
- data/sig/open_feature/sdk/hooks/hook_context.rbs +18 -0
- data/sig/open_feature/sdk/hooks/hook_executor.rbs +20 -0
- data/sig/open_feature/sdk/hooks/logging_hook.rbs +25 -0
- data/sig/open_feature/sdk/provider/error_code.rbs +16 -0
- data/sig/open_feature/sdk/provider/event_emitter.rbs +16 -0
- data/sig/open_feature/sdk/provider/in_memory_provider.rbs +46 -0
- data/sig/open_feature/sdk/provider/no_op_provider.rbs +25 -0
- data/sig/open_feature/sdk/provider/provider_metadata.rbs +11 -0
- data/sig/open_feature/sdk/provider/reason.rbs +17 -0
- data/sig/open_feature/sdk/provider/resolution_details.rbs +19 -0
- data/sig/open_feature/sdk/provider.rbs +24 -0
- data/sig/open_feature/sdk/provider_event.rbs +14 -0
- data/sig/open_feature/sdk/provider_initialization_error.rbs +11 -0
- data/sig/open_feature/sdk/provider_state.rbs +13 -0
- data/sig/open_feature/sdk/provider_state_registry.rbs +22 -0
- data/sig/open_feature/sdk/telemetry.rbs +29 -0
- data/sig/open_feature/sdk/thread_local_transaction_context_propagator.rbs +12 -0
- data/sig/open_feature/sdk/tracking_event_details.rbs +10 -0
- data/sig/open_feature/sdk/transaction_context_propagator.rbs +11 -0
- data/sig/open_feature/sdk/version.rbs +5 -0
- data/sig/open_feature/sdk.rbs +22 -0
- metadata +54 -140
- data/.simplecov +0 -12
- data/docs/plans/2026-03-07-telemetry-utility-design.md +0 -98
- data/docs/plans/2026-03-07-telemetry-utility-plan.md +0 -578
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
# Telemetry Utility Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add a dependency-free `OpenFeature::SDK::Telemetry` module that creates OTel-compatible evaluation events from hook context and evaluation details.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Single module (`Telemetry`) in one file with constants, a Struct, and one public `module_function`. Designed for consumption in a hook's `finally` stage. No third-party dependencies.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Ruby >= 3.1, RSpec, Standard Ruby (linter)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
### Task 1: Create telemetry module with constants and struct
|
|
14
|
-
|
|
15
|
-
**Files:**
|
|
16
|
-
- Create: `lib/open_feature/sdk/telemetry.rb`
|
|
17
|
-
|
|
18
|
-
**Step 1: Create the module file with constants and struct**
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
# frozen_string_literal: true
|
|
22
|
-
|
|
23
|
-
module OpenFeature
|
|
24
|
-
module SDK
|
|
25
|
-
module Telemetry
|
|
26
|
-
EVENT_NAME = "feature_flag.evaluation"
|
|
27
|
-
|
|
28
|
-
FLAG_KEY = "feature_flag.key"
|
|
29
|
-
CONTEXT_ID_KEY = "feature_flag.context.id"
|
|
30
|
-
ERROR_MESSAGE_KEY = "error.message"
|
|
31
|
-
ERROR_TYPE_KEY = "error.type"
|
|
32
|
-
PROVIDER_NAME_KEY = "feature_flag.provider.name"
|
|
33
|
-
RESULT_REASON_KEY = "feature_flag.result.reason"
|
|
34
|
-
RESULT_VALUE_KEY = "feature_flag.result.value"
|
|
35
|
-
RESULT_VARIANT_KEY = "feature_flag.result.variant"
|
|
36
|
-
FLAG_SET_ID_KEY = "feature_flag.set.id"
|
|
37
|
-
VERSION_KEY = "feature_flag.version"
|
|
38
|
-
|
|
39
|
-
METADATA_KEY_MAP = {
|
|
40
|
-
"contextId" => CONTEXT_ID_KEY,
|
|
41
|
-
"flagSetId" => FLAG_SET_ID_KEY,
|
|
42
|
-
"version" => VERSION_KEY
|
|
43
|
-
}.freeze
|
|
44
|
-
|
|
45
|
-
EvaluationEvent = Struct.new(:name, :attributes, keyword_init: true)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Step 2: Wire up the require**
|
|
52
|
-
|
|
53
|
-
Modify: `lib/open_feature/sdk.rb` — add `require_relative "sdk/telemetry"` after the existing requires (line 3 area).
|
|
54
|
-
|
|
55
|
-
**Step 3: Verify it loads**
|
|
56
|
-
|
|
57
|
-
Run: `bundle exec ruby -e "require 'open_feature/sdk'; puts OpenFeature::SDK::Telemetry::EVENT_NAME"`
|
|
58
|
-
Expected: `feature_flag.evaluation`
|
|
59
|
-
|
|
60
|
-
**Step 4: Commit**
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
git add lib/open_feature/sdk/telemetry.rb lib/open_feature/sdk.rb
|
|
64
|
-
git commit -s -S -m "feat(telemetry): add Telemetry module with OTel constants and EvaluationEvent struct"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
### Task 2: Write tests for happy path and return type
|
|
70
|
-
|
|
71
|
-
**Files:**
|
|
72
|
-
- Create: `spec/open_feature/sdk/telemetry_spec.rb`
|
|
73
|
-
|
|
74
|
-
**Step 1: Write the test file with happy path and return type tests**
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
# frozen_string_literal: true
|
|
78
|
-
|
|
79
|
-
require "spec_helper"
|
|
80
|
-
|
|
81
|
-
RSpec.describe OpenFeature::SDK::Telemetry do
|
|
82
|
-
let(:client_metadata) { OpenFeature::SDK::ClientMetadata.new(domain: "test-domain") }
|
|
83
|
-
let(:provider_metadata) { OpenFeature::SDK::Provider::ProviderMetadata.new(name: "test-provider") }
|
|
84
|
-
let(:evaluation_context) { OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123") }
|
|
85
|
-
|
|
86
|
-
let(:hook_context) do
|
|
87
|
-
OpenFeature::SDK::Hooks::HookContext.new(
|
|
88
|
-
flag_key: "my-flag",
|
|
89
|
-
flag_value_type: :boolean,
|
|
90
|
-
default_value: false,
|
|
91
|
-
evaluation_context: evaluation_context,
|
|
92
|
-
client_metadata: client_metadata,
|
|
93
|
-
provider_metadata: provider_metadata
|
|
94
|
-
)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
let(:flag_metadata) do
|
|
98
|
-
{
|
|
99
|
-
"contextId" => "ctx-456",
|
|
100
|
-
"flagSetId" => "set-789",
|
|
101
|
-
"version" => "v1.0"
|
|
102
|
-
}
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
let(:resolution_details) do
|
|
106
|
-
OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
107
|
-
value: true,
|
|
108
|
-
reason: "TARGETING_MATCH",
|
|
109
|
-
variant: "enabled",
|
|
110
|
-
flag_metadata: flag_metadata
|
|
111
|
-
)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
let(:evaluation_details) do
|
|
115
|
-
OpenFeature::SDK::EvaluationDetails.new(
|
|
116
|
-
flag_key: "my-flag",
|
|
117
|
-
resolution_details: resolution_details
|
|
118
|
-
)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
describe ".create_evaluation_event" do
|
|
122
|
-
context "with full data" do
|
|
123
|
-
it "returns an EvaluationEvent with all attributes populated" do
|
|
124
|
-
event = described_class.create_evaluation_event(
|
|
125
|
-
hook_context: hook_context,
|
|
126
|
-
evaluation_details: evaluation_details
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
expect(event).to be_a(OpenFeature::SDK::Telemetry::EvaluationEvent)
|
|
130
|
-
expect(event.name).to eq("feature_flag.evaluation")
|
|
131
|
-
expect(event.attributes).to eq(
|
|
132
|
-
"feature_flag.key" => "my-flag",
|
|
133
|
-
"feature_flag.provider.name" => "test-provider",
|
|
134
|
-
"feature_flag.result.variant" => "enabled",
|
|
135
|
-
"feature_flag.result.reason" => "targeting_match",
|
|
136
|
-
"feature_flag.context.id" => "ctx-456",
|
|
137
|
-
"feature_flag.set.id" => "set-789",
|
|
138
|
-
"feature_flag.version" => "v1.0"
|
|
139
|
-
)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**Step 2: Run the test to verify it fails**
|
|
147
|
-
|
|
148
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
149
|
-
Expected: FAIL — `NoMethodError: undefined method 'create_evaluation_event'`
|
|
150
|
-
|
|
151
|
-
**Step 3: Commit the failing test**
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
git add spec/open_feature/sdk/telemetry_spec.rb
|
|
155
|
-
git commit -s -S -m "test(telemetry): add failing test for happy path"
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
### Task 3: Implement create_evaluation_event
|
|
161
|
-
|
|
162
|
-
**Files:**
|
|
163
|
-
- Modify: `lib/open_feature/sdk/telemetry.rb`
|
|
164
|
-
|
|
165
|
-
**Step 1: Add the implementation to the Telemetry module**
|
|
166
|
-
|
|
167
|
-
Add after the `EvaluationEvent` struct definition:
|
|
168
|
-
|
|
169
|
-
```ruby
|
|
170
|
-
module_function
|
|
171
|
-
|
|
172
|
-
def create_evaluation_event(hook_context:, evaluation_details:)
|
|
173
|
-
attributes = {FLAG_KEY => hook_context.flag_key}
|
|
174
|
-
|
|
175
|
-
provider_name = hook_context.provider_metadata&.name
|
|
176
|
-
attributes[PROVIDER_NAME_KEY] = provider_name if provider_name
|
|
177
|
-
|
|
178
|
-
targeting_key = hook_context.evaluation_context&.targeting_key
|
|
179
|
-
attributes[CONTEXT_ID_KEY] = targeting_key if targeting_key
|
|
180
|
-
|
|
181
|
-
if evaluation_details
|
|
182
|
-
if evaluation_details.variant
|
|
183
|
-
attributes[RESULT_VARIANT_KEY] = evaluation_details.variant
|
|
184
|
-
else
|
|
185
|
-
attributes[RESULT_VALUE_KEY] = evaluation_details.value
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
if evaluation_details.reason
|
|
189
|
-
attributes[RESULT_REASON_KEY] = evaluation_details.reason.downcase
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
if evaluation_details.error_code
|
|
193
|
-
attributes[ERROR_TYPE_KEY] = evaluation_details.error_code.downcase
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
if evaluation_details.error_message
|
|
197
|
-
attributes[ERROR_MESSAGE_KEY] = evaluation_details.error_message
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
extract_metadata(evaluation_details.flag_metadata, attributes)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
EvaluationEvent.new(name: EVENT_NAME, attributes: attributes)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def extract_metadata(flag_metadata, attributes)
|
|
207
|
-
return unless flag_metadata
|
|
208
|
-
|
|
209
|
-
METADATA_KEY_MAP.each do |metadata_key, otel_key|
|
|
210
|
-
value = flag_metadata[metadata_key]
|
|
211
|
-
attributes[otel_key] = value unless value.nil?
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
private_class_method :extract_metadata
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
**Step 2: Run the test to verify it passes**
|
|
219
|
-
|
|
220
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
221
|
-
Expected: PASS (1 example, 0 failures)
|
|
222
|
-
|
|
223
|
-
**Step 3: Commit**
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
git add lib/open_feature/sdk/telemetry.rb
|
|
227
|
-
git commit -s -S -m "feat(telemetry): implement create_evaluation_event"
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
### Task 4: Add tests for variant vs value precedence
|
|
233
|
-
|
|
234
|
-
**Files:**
|
|
235
|
-
- Modify: `spec/open_feature/sdk/telemetry_spec.rb`
|
|
236
|
-
|
|
237
|
-
**Step 1: Add variant/value precedence tests**
|
|
238
|
-
|
|
239
|
-
Add inside the `describe ".create_evaluation_event"` block:
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
context "variant vs value precedence" do
|
|
243
|
-
it "uses variant when present and omits value" do
|
|
244
|
-
event = described_class.create_evaluation_event(
|
|
245
|
-
hook_context: hook_context,
|
|
246
|
-
evaluation_details: evaluation_details
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
expect(event.attributes).to have_key("feature_flag.result.variant")
|
|
250
|
-
expect(event.attributes).not_to have_key("feature_flag.result.value")
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
it "uses value when variant is nil" do
|
|
254
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
255
|
-
value: "blue",
|
|
256
|
-
reason: "STATIC"
|
|
257
|
-
)
|
|
258
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
259
|
-
flag_key: "color-flag",
|
|
260
|
-
resolution_details: resolution
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
event = described_class.create_evaluation_event(
|
|
264
|
-
hook_context: hook_context,
|
|
265
|
-
evaluation_details: details
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
expect(event.attributes["feature_flag.result.value"]).to eq("blue")
|
|
269
|
-
expect(event.attributes).not_to have_key("feature_flag.result.variant")
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Step 2: Run tests**
|
|
275
|
-
|
|
276
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
277
|
-
Expected: PASS (3 examples, 0 failures)
|
|
278
|
-
|
|
279
|
-
**Step 3: Commit**
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
git add spec/open_feature/sdk/telemetry_spec.rb
|
|
283
|
-
git commit -s -S -m "test(telemetry): add variant vs value precedence tests"
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
### Task 5: Add tests for enum downcasing
|
|
289
|
-
|
|
290
|
-
**Files:**
|
|
291
|
-
- Modify: `spec/open_feature/sdk/telemetry_spec.rb`
|
|
292
|
-
|
|
293
|
-
**Step 1: Add enum downcasing tests**
|
|
294
|
-
|
|
295
|
-
Add inside the `describe ".create_evaluation_event"` block:
|
|
296
|
-
|
|
297
|
-
```ruby
|
|
298
|
-
context "enum downcasing" do
|
|
299
|
-
it "downcases reason to OTel convention" do
|
|
300
|
-
event = described_class.create_evaluation_event(
|
|
301
|
-
hook_context: hook_context,
|
|
302
|
-
evaluation_details: evaluation_details
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
expect(event.attributes["feature_flag.result.reason"]).to eq("targeting_match")
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
it "downcases error_code to OTel convention" do
|
|
309
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
310
|
-
value: false,
|
|
311
|
-
reason: "ERROR",
|
|
312
|
-
error_code: "FLAG_NOT_FOUND",
|
|
313
|
-
error_message: "Flag not found"
|
|
314
|
-
)
|
|
315
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
316
|
-
flag_key: "missing-flag",
|
|
317
|
-
resolution_details: resolution
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
event = described_class.create_evaluation_event(
|
|
321
|
-
hook_context: hook_context,
|
|
322
|
-
evaluation_details: details
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
expect(event.attributes["error.type"]).to eq("flag_not_found")
|
|
326
|
-
expect(event.attributes["feature_flag.result.reason"]).to eq("error")
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
**Step 2: Run tests**
|
|
332
|
-
|
|
333
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
334
|
-
Expected: PASS (5 examples, 0 failures)
|
|
335
|
-
|
|
336
|
-
**Step 3: Commit**
|
|
337
|
-
|
|
338
|
-
```bash
|
|
339
|
-
git add spec/open_feature/sdk/telemetry_spec.rb
|
|
340
|
-
git commit -s -S -m "test(telemetry): add enum downcasing tests"
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
### Task 6: Add tests for error attributes and nil evaluation_details
|
|
346
|
-
|
|
347
|
-
**Files:**
|
|
348
|
-
- Modify: `spec/open_feature/sdk/telemetry_spec.rb`
|
|
349
|
-
|
|
350
|
-
**Step 1: Add error and nil evaluation_details tests**
|
|
351
|
-
|
|
352
|
-
Add inside the `describe ".create_evaluation_event"` block:
|
|
353
|
-
|
|
354
|
-
```ruby
|
|
355
|
-
context "error attributes" do
|
|
356
|
-
it "includes error attributes only when error occurred" do
|
|
357
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
358
|
-
value: false,
|
|
359
|
-
reason: "ERROR",
|
|
360
|
-
error_code: "PARSE_ERROR",
|
|
361
|
-
error_message: "Could not parse flag"
|
|
362
|
-
)
|
|
363
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
364
|
-
flag_key: "bad-flag",
|
|
365
|
-
resolution_details: resolution
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
event = described_class.create_evaluation_event(
|
|
369
|
-
hook_context: hook_context,
|
|
370
|
-
evaluation_details: details
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
expect(event.attributes["error.type"]).to eq("parse_error")
|
|
374
|
-
expect(event.attributes["error.message"]).to eq("Could not parse flag")
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
it "omits error attributes when no error" do
|
|
378
|
-
event = described_class.create_evaluation_event(
|
|
379
|
-
hook_context: hook_context,
|
|
380
|
-
evaluation_details: evaluation_details
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
expect(event.attributes).not_to have_key("error.type")
|
|
384
|
-
expect(event.attributes).not_to have_key("error.message")
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
context "nil evaluation_details" do
|
|
389
|
-
it "returns event with only flag_key and available context" do
|
|
390
|
-
event = described_class.create_evaluation_event(
|
|
391
|
-
hook_context: hook_context,
|
|
392
|
-
evaluation_details: nil
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
expect(event.name).to eq("feature_flag.evaluation")
|
|
396
|
-
expect(event.attributes).to eq(
|
|
397
|
-
"feature_flag.key" => "my-flag",
|
|
398
|
-
"feature_flag.provider.name" => "test-provider",
|
|
399
|
-
"feature_flag.context.id" => "user-123"
|
|
400
|
-
)
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Step 2: Run tests**
|
|
406
|
-
|
|
407
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
408
|
-
Expected: PASS (8 examples, 0 failures)
|
|
409
|
-
|
|
410
|
-
**Step 3: Commit**
|
|
411
|
-
|
|
412
|
-
```bash
|
|
413
|
-
git add spec/open_feature/sdk/telemetry_spec.rb
|
|
414
|
-
git commit -s -S -m "test(telemetry): add error attributes and nil evaluation_details tests"
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
### Task 7: Add tests for flag metadata and context ID precedence
|
|
420
|
-
|
|
421
|
-
**Files:**
|
|
422
|
-
- Modify: `spec/open_feature/sdk/telemetry_spec.rb`
|
|
423
|
-
|
|
424
|
-
**Step 1: Add metadata and context ID tests**
|
|
425
|
-
|
|
426
|
-
Add inside the `describe ".create_evaluation_event"` block:
|
|
427
|
-
|
|
428
|
-
```ruby
|
|
429
|
-
context "flag metadata" do
|
|
430
|
-
it "ignores nil flag_metadata" do
|
|
431
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
432
|
-
value: true,
|
|
433
|
-
variant: "on"
|
|
434
|
-
)
|
|
435
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
436
|
-
flag_key: "my-flag",
|
|
437
|
-
resolution_details: resolution
|
|
438
|
-
)
|
|
439
|
-
|
|
440
|
-
event = described_class.create_evaluation_event(
|
|
441
|
-
hook_context: hook_context,
|
|
442
|
-
evaluation_details: details
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
expect(event.attributes).not_to have_key("feature_flag.set.id")
|
|
446
|
-
expect(event.attributes).not_to have_key("feature_flag.version")
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
it "ignores empty flag_metadata" do
|
|
450
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
451
|
-
value: true,
|
|
452
|
-
variant: "on",
|
|
453
|
-
flag_metadata: {}
|
|
454
|
-
)
|
|
455
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
456
|
-
flag_key: "my-flag",
|
|
457
|
-
resolution_details: resolution
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
event = described_class.create_evaluation_event(
|
|
461
|
-
hook_context: hook_context,
|
|
462
|
-
evaluation_details: details
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
expect(event.attributes).not_to have_key("feature_flag.set.id")
|
|
466
|
-
expect(event.attributes).not_to have_key("feature_flag.version")
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
it "ignores unknown metadata keys" do
|
|
470
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
471
|
-
value: true,
|
|
472
|
-
variant: "on",
|
|
473
|
-
flag_metadata: {"customKey" => "custom-value", "anotherKey" => 42}
|
|
474
|
-
)
|
|
475
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
476
|
-
flag_key: "my-flag",
|
|
477
|
-
resolution_details: resolution
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
event = described_class.create_evaluation_event(
|
|
481
|
-
hook_context: hook_context,
|
|
482
|
-
evaluation_details: details
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
expect(event.attributes).not_to have_key("customKey")
|
|
486
|
-
expect(event.attributes).not_to have_key("anotherKey")
|
|
487
|
-
end
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
context "context ID precedence" do
|
|
491
|
-
it "uses metadata contextId over targeting_key" do
|
|
492
|
-
event = described_class.create_evaluation_event(
|
|
493
|
-
hook_context: hook_context,
|
|
494
|
-
evaluation_details: evaluation_details
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
# flag_metadata has contextId "ctx-456", targeting_key is "user-123"
|
|
498
|
-
expect(event.attributes["feature_flag.context.id"]).to eq("ctx-456")
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
it "falls back to targeting_key when no contextId in metadata" do
|
|
502
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
503
|
-
value: true,
|
|
504
|
-
variant: "on",
|
|
505
|
-
flag_metadata: {"flagSetId" => "set-1"}
|
|
506
|
-
)
|
|
507
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
508
|
-
flag_key: "my-flag",
|
|
509
|
-
resolution_details: resolution
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
event = described_class.create_evaluation_event(
|
|
513
|
-
hook_context: hook_context,
|
|
514
|
-
evaluation_details: details
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
expect(event.attributes["feature_flag.context.id"]).to eq("user-123")
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
it "omits context ID when neither targeting_key nor contextId available" do
|
|
521
|
-
bare_context = OpenFeature::SDK::EvaluationContext.new(env: "prod")
|
|
522
|
-
bare_hook_context = OpenFeature::SDK::Hooks::HookContext.new(
|
|
523
|
-
flag_key: "my-flag",
|
|
524
|
-
flag_value_type: :boolean,
|
|
525
|
-
default_value: false,
|
|
526
|
-
evaluation_context: bare_context
|
|
527
|
-
)
|
|
528
|
-
resolution = OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
529
|
-
value: true,
|
|
530
|
-
variant: "on"
|
|
531
|
-
)
|
|
532
|
-
details = OpenFeature::SDK::EvaluationDetails.new(
|
|
533
|
-
flag_key: "my-flag",
|
|
534
|
-
resolution_details: resolution
|
|
535
|
-
)
|
|
536
|
-
|
|
537
|
-
event = described_class.create_evaluation_event(
|
|
538
|
-
hook_context: bare_hook_context,
|
|
539
|
-
evaluation_details: details
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
expect(event.attributes).not_to have_key("feature_flag.context.id")
|
|
543
|
-
end
|
|
544
|
-
end
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
**Step 2: Run tests**
|
|
548
|
-
|
|
549
|
-
Run: `bundle exec rspec spec/open_feature/sdk/telemetry_spec.rb`
|
|
550
|
-
Expected: PASS (14 examples, 0 failures)
|
|
551
|
-
|
|
552
|
-
**Step 3: Commit**
|
|
553
|
-
|
|
554
|
-
```bash
|
|
555
|
-
git add spec/open_feature/sdk/telemetry_spec.rb
|
|
556
|
-
git commit -s -S -m "test(telemetry): add flag metadata and context ID precedence tests"
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
### Task 8: Run full suite and lint
|
|
562
|
-
|
|
563
|
-
**Step 1: Run full test suite**
|
|
564
|
-
|
|
565
|
-
Run: `bundle exec rspec`
|
|
566
|
-
Expected: All existing tests still pass, plus 14 new telemetry tests.
|
|
567
|
-
|
|
568
|
-
**Step 2: Run linter**
|
|
569
|
-
|
|
570
|
-
Run: `bundle exec standardrb`
|
|
571
|
-
Expected: No offenses. If any, fix with `bundle exec standardrb --fix`.
|
|
572
|
-
|
|
573
|
-
**Step 3: Final commit if lint fixes needed**
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
git add -A
|
|
577
|
-
git commit -s -S -m "style(telemetry): fix standardrb lint issues"
|
|
578
|
-
```
|