Skip to content

Commit 0f5c9c2

Browse files
committed
Create Reporter for exposures reporting
(AI generated)
1 parent b1730b6 commit 0f5c9c2

File tree

12 files changed

+226
-35
lines changed

12 files changed

+226
-35
lines changed

lib/datadog/open_feature/component.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require_relative 'evaluation_engine'
44
require_relative 'exposures'
5+
require_relative 'transport/http'
56

67
module Datadog
78
module OpenFeature
@@ -31,11 +32,18 @@ def initialize(settings, agent_settings, logger:, telemetry:)
3132
@logger = logger
3233
@telemetry = telemetry
3334

34-
@engine = EvaluationEngine.new(telemetry, logger: logger)
35+
transport = Transport::HTTP.exposures(agent_settings: agent_settings, logger: logger)
36+
@worker = Exposures::Worker.new(transport: transport, logger: logger)
37+
@reporter = Exposures::Reporter.new(worker: @worker, logger: logger)
38+
@engine = EvaluationEngine.new(@reporter, telemetry: telemetry, logger: logger)
3539
end
3640

3741
def shutdown!
38-
# no-op
42+
@reporter&.clear
43+
return unless defined?(@worker) && @worker
44+
45+
@worker.flush
46+
@worker.stop(true)
3947
end
4048
end
4149
end

lib/datadog/open_feature/evaluation_engine.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ module OpenFeature
88
# This class performs the evaluation of the feature flag
99
class EvaluationEngine
1010
attr_accessor :configuration
11+
attr_reader :reporter
1112

1213
ResolutionError = Struct.new(:reason, :code, :message, keyword_init: true)
1314

1415
ALLOWED_TYPES = %i[boolean string number float integer object].freeze
1516

16-
def initialize(telemetry, logger: Datadog.logger)
17+
def initialize(reporter, telemetry:, logger: Datadog.logger)
18+
@reporter = reporter
1719
@telemetry = telemetry
1820
@logger = logger
1921

lib/datadog/open_feature/exposures.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ module Exposures
1212
require_relative 'exposures/batch'
1313
require_relative 'exposures/buffer'
1414
require_relative 'exposures/worker'
15+
require_relative 'exposures/reporter'

sig/datadog/open_feature/evaluation_engine.rbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ module Datadog
1515

1616
attr_writer configuration: ::String
1717

18-
def initialize: (Core::Telemetry::Component telemetry, ?logger: Core::Logger) -> void
18+
attr_reader reporter: Exposures::Reporter
19+
20+
def initialize: (Exposures::Reporter reporter, telemetry: Core::Telemetry::Component, ?logger: Core::Logger) -> void
1921

2022
def fetch_value: (
2123
flag_key: ::String,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module Datadog
2+
module OpenFeature
3+
module Exposures
4+
class Reporter
5+
DEFAULT_CACHE_LIMIT: ::Integer
6+
7+
def initialize: (
8+
worker: Worker,
9+
?cache: Datadog::AppSec::APISecurity::LRUCache,
10+
?logger: Core::Logger,
11+
?time_provider: untyped
12+
) -> void
13+
14+
def report: (result: ::Hash[untyped, untyped], ?context: untyped) -> bool
15+
16+
def clear: () -> void
17+
18+
private
19+
20+
def duplicate?: (String, ::Integer) -> bool
21+
22+
def normalize: (untyped, untyped) -> ::Hash[Symbol, untyped]?
23+
24+
def build_event: (::Hash[Symbol, untyped]) -> Event
25+
26+
def extract_subject_id: (::Hash[untyped, untyped], untyped) -> (String | nil)
27+
28+
def extract_attributes: (::Hash[untyped, untyped], untyped) -> (::Hash[untyped, untyped] | nil)
29+
30+
def context_value: (untyped, Symbol) -> untyped
31+
32+
def ensure_hash: (untyped) -> ::Hash[untyped, untyped]
33+
34+
def cache_key: (untyped, untyped) -> (String | nil)
35+
36+
def digest: (untyped, untyped) -> ::Integer
37+
end
38+
end
39+
end
40+
end
41+
42+

sig/datadog/open_feature/exposures/worker.rbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ module Datadog
1313
def initialize: (
1414
transport: Transport::Exposures::Transport,
1515
logger: Core::Logger,
16-
flush_interval_seconds: ::Integer,
17-
buffer_limit: ::Integer,
18-
context_builder: (^() -> ::Hash[::Symbol, untyped])?
16+
?flush_interval_seconds: ::Integer,
17+
?buffer_limit: ::Integer,
18+
?context_builder: (^() -> ::Hash[::Symbol, untyped])
1919
) -> void
2020

2121
def start: () -> void

sig/datadog/open_feature/transport/http.rbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ module Datadog
44
module HTTP
55
def self?.exposures: (
66
agent_settings: ::Datadog::Core::Configuration::AgentSettings,
7-
logger: ::Datadog::Core::Logger?,
8-
headers: ::Hash[::Symbol, untyped]?
7+
?logger: ::Datadog::Core::Logger?,
8+
?headers: ::Hash[::Symbol, untyped]?
99
) ?{ (::Datadog::Core::Transport::HTTP::Builder) -> void } -> ::Datadog::OpenFeature::Transport::Exposures::Transport
1010
end
1111
end

spec/datadog/open_feature/component_spec.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,26 @@
66
RSpec.describe Datadog::OpenFeature::Component do
77
before do
88
allow(logger).to receive(:warn)
9+
allow(logger).to receive(:debug)
10+
allow(Datadog::OpenFeature::Transport::HTTP).to receive(:exposures).and_return(transport)
11+
allow(Datadog::OpenFeature::Exposures::Worker).to receive(:new).and_return(worker)
12+
allow(Datadog::OpenFeature::Exposures::Reporter).to receive(:new).and_return(reporter)
913
end
1014

1115
let(:telemetry) { instance_double(Datadog::Core::Telemetry::Component) }
1216
let(:settings) { Datadog::Core::Configuration::Settings.new }
1317
let(:agent_settings) { instance_double(Datadog::Core::Configuration::AgentSettings) }
1418
let(:logger) { instance_double(Logger) }
19+
let(:transport) { instance_double(Datadog::OpenFeature::Transport::Exposures::Transport) }
20+
let(:worker) do
21+
instance_double(
22+
Datadog::OpenFeature::Exposures::Worker,
23+
enqueue: true,
24+
flush: nil,
25+
stop: nil
26+
)
27+
end
28+
let(:reporter) { instance_double(Datadog::OpenFeature::Exposures::Reporter, clear: nil) }
1529

1630
describe '.build' do
1731
subject(:component) do
@@ -29,6 +43,7 @@
2943
it 'returns configured component instance' do
3044
expect(component).to be_a(described_class)
3145
expect(component.engine).to be_a(Datadog::OpenFeature::EvaluationEngine)
46+
expect(Datadog::OpenFeature::Exposures::Reporter).to have_received(:new)
3247
end
3348
end
3449

@@ -70,4 +85,21 @@
7085
it { expect(component).to be_nil }
7186
end
7287
end
88+
89+
describe '#shutdown!' do
90+
subject(:component) { described_class.new(settings, agent_settings, logger: logger, telemetry: telemetry) }
91+
92+
before do
93+
settings.open_feature.enabled = true
94+
settings.remote.enabled = true
95+
end
96+
97+
it 'flushes reporter cache and stops worker' do
98+
expect(reporter).to receive(:clear)
99+
expect(worker).to receive(:flush)
100+
expect(worker).to receive(:stop).with(true)
101+
102+
component.shutdown!
103+
end
104+
end
73105
end

spec/datadog/open_feature/evaluation_engine_spec.rb

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
require 'datadog/open_feature/evaluation_engine'
55

66
RSpec.describe Datadog::OpenFeature::EvaluationEngine do
7-
let(:evaluator) { described_class.new(telemetry, logger: logger) }
7+
let(:engine) { described_class.new(reporter, telemetry: telemetry, logger: logger) }
8+
let(:reporter) { instance_double(Datadog::OpenFeature::Exposures::Reporter) }
89
let(:telemetry) { instance_double(Datadog::Core::Telemetry::Component) }
910
let(:logger) { instance_double(Datadog::Core::Logger) }
1011
let(:ufc) do
@@ -41,7 +42,7 @@
4142
end
4243

4344
describe '#fetch_value' do
44-
let(:result) { evaluator.fetch_value(flag_key: 'test', expected_type: :string) }
45+
let(:result) { engine.fetch_value(flag_key: 'test', expected_type: :string) }
4546

4647
context 'when binding evaluator is not ready' do
4748
it 'returns evaluation error' do
@@ -53,8 +54,8 @@
5354

5455
context 'when binding evaluator returns error' do
5556
before do
56-
evaluator.configuration = ufc
57-
evaluator.reconfigure!
57+
engine.configuration = ufc
58+
engine.reconfigure!
5859

5960
allow_any_instance_of(Datadog::OpenFeature::Binding::Evaluator).to receive(:get_assignment)
6061
.and_return(error)
@@ -71,8 +72,8 @@
7172

7273
context 'when binding evaluator raises error' do
7374
before do
74-
evaluator.configuration = ufc
75-
evaluator.reconfigure!
75+
engine.configuration = ufc
76+
engine.reconfigure!
7677

7778
allow(telemetry).to receive(:report)
7879
allow_any_instance_of(Datadog::OpenFeature::Binding::Evaluator).to receive(:get_assignment)
@@ -90,11 +91,11 @@
9091

9192
context 'when expected type not in the allowed list' do
9293
before do
93-
evaluator.configuration = ufc
94-
evaluator.reconfigure!
94+
engine.configuration = ufc
95+
engine.reconfigure!
9596
end
9697

97-
let(:result) { evaluator.fetch_value(flag_key: 'test', expected_type: :whatever) }
98+
let(:result) { engine.fetch_value(flag_key: 'test', expected_type: :whatever) }
9899

99100
it 'returns evaluation error' do
100101
expect(result.reason).to eq('ERROR')
@@ -105,11 +106,11 @@
105106

106107
context 'when binding evaluator returns resolution details' do
107108
before do
108-
evaluator.configuration = ufc
109-
evaluator.reconfigure!
109+
engine.configuration = ufc
110+
engine.reconfigure!
110111
end
111112

112-
let(:result) { evaluator.fetch_value(flag_key: 'test', expected_type: :string) }
113+
let(:result) { engine.fetch_value(flag_key: 'test', expected_type: :string) }
113114

114115
it { expect(result.value).to eq('hello') }
115116
end
@@ -120,14 +121,14 @@
120121
it 'does nothing and logs the issue' do
121122
expect(logger).to receive(:debug).with(/OpenFeature: Configuration is not received, skip reconfiguration/)
122123

123-
evaluator.reconfigure!
124+
engine.reconfigure!
124125
end
125126
end
126127

127128
context 'when binding initialization fails with exception' do
128129
before do
129-
evaluator.configuration = ufc
130-
evaluator.reconfigure!
130+
engine.configuration = ufc
131+
engine.reconfigure!
131132

132133
allow(Datadog::OpenFeature::Binding::Evaluator).to receive(:new).and_raise(error)
133134
end
@@ -139,25 +140,25 @@
139140
expect(telemetry).to receive(:report)
140141
.with(error, description: match(/OpenFeature: Failed to reconfigure/))
141142

142-
evaluator.configuration = '{}'
143-
expect { evaluator.reconfigure! }.not_to raise_error
143+
engine.configuration = '{}'
144+
expect { engine.reconfigure! }.not_to raise_error
144145
end
145146

146147
it 'persists previouly configured evaluator' do
147148
allow(logger).to receive(:error)
148149
allow(telemetry).to receive(:report)
149150

150-
evaluator.configuration = '{}'
151-
expect { evaluator.reconfigure! }.not_to change {
152-
evaluator.fetch_value(flag_key: 'test', expected_type: :string).value
151+
engine.configuration = '{}'
152+
expect { engine.reconfigure! }.not_to change {
153+
engine.fetch_value(flag_key: 'test', expected_type: :string).value
153154
}.from('hello')
154155
end
155156
end
156157

157158
context 'when binding initialization succeeds' do
158159
before do
159-
evaluator.configuration = ufc
160-
evaluator.reconfigure!
160+
engine.configuration = ufc
161+
engine.reconfigure!
161162
end
162163

163164
let(:new_ufc) do
@@ -194,8 +195,8 @@
194195
end
195196

196197
xit 'reconfigures binding evaluator with new flags configuration' do
197-
expect { evaluator.configuration = new_ufc; evaluator.reconfigure!}
198-
.to change { evaluator.fetch_value(flag_key: 'test', expected_type: :string).value }
198+
expect { engine.configuration = new_ufc; engine.reconfigure!}
199+
.to change { engine.fetch_value(flag_key: 'test', expected_type: :string).value }
199200
.from('hello').to('goodbye')
200201
end
201202
end

0 commit comments

Comments
 (0)