Skip to content

Commit b495a48

Browse files
feat: add odp manager (#314)
* remove odp_config from event manager constructor * remove odp_config from segment manager constructor * add odp manager
1 parent 3579f5d commit b495a48

File tree

7 files changed

+575
-78
lines changed

7 files changed

+575
-78
lines changed

lib/optimizely/helpers/constants.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,8 @@ module Constants
386386
FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).',
387387
ODP_EVENT_FAILED: 'ODP event send failed (%s).',
388388
ODP_NOT_ENABLED: 'ODP is not enabled.',
389-
ODP_NOT_INTEGRATED: 'ODP is not integrated.'
389+
ODP_NOT_INTEGRATED: 'ODP is not integrated.',
390+
ODP_INVALID_DATA: 'ODP data is not valid.'
390391
}.freeze
391392

392393
DECISION_NOTIFICATION_TYPES = {
@@ -425,6 +426,16 @@ module Constants
425426
REQUEST_TIMEOUT: 10
426427
}.freeze
427428

429+
ODP_SEGMENTS_CACHE_CONFIG = {
430+
DEFAULT_CAPACITY: 10_000,
431+
DEFAULT_TIMEOUT_SECONDS: 600
432+
}.freeze
433+
434+
ODP_MANAGER_CONFIG = {
435+
KEY_FOR_USER_ID: 'fs_user_id',
436+
EVENT_TYPE: 'fullstack'
437+
}.freeze
438+
428439
ODP_CONFIG_STATE = {
429440
UNDETERMINED: 'UNDETERMINED',
430441
INTEGRATED: 'INTEGRATED',

lib/optimizely/odp/odp_event_manager.rb

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ class OdpEventManager
2626
# the BlockingQueue and buffers them for either a configured batch size or for a
2727
# maximum duration before the resulting LogEvent is sent to the NotificationCenter.
2828

29-
attr_reader :batch_size, :odp_config, :zaius_manager, :logger
29+
attr_reader :batch_size, :zaius_manager, :logger
30+
attr_accessor :odp_config
3031

3132
def initialize(
32-
odp_config,
3333
api_manager: nil,
3434
logger: NoOpLogger.new,
3535
proxy_config: nil
3636
)
3737
super()
3838

39-
@odp_config = odp_config
40-
@api_host = odp_config.api_host
41-
@api_key = odp_config.api_key
39+
@odp_config = nil
40+
@api_host = nil
41+
@api_key = nil
4242

4343
@mutex = Mutex.new
4444
@event_queue = SizedQueue.new(Optimizely::Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_QUEUE_CAPACITY])
@@ -53,14 +53,20 @@ def initialize(
5353
@retry_count = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_RETRY_COUNT]
5454
# current_batch should only be accessed by processing thread
5555
@current_batch = []
56+
@thread = nil
5657
@thread_exception = false
5758
end
5859

59-
def start!
60+
def start!(odp_config)
6061
if running?
6162
@logger.log(Logger::WARN, 'Service already started.')
6263
return
6364
end
65+
66+
@odp_config = odp_config
67+
@api_host = odp_config.api_host
68+
@api_key = odp_config.api_key
69+
6470
@thread = Thread.new { run }
6571
@logger.log(Logger::INFO, 'Starting scheduler.')
6672
end
@@ -117,7 +123,10 @@ def dispatch(event)
117123
end
118124

119125
def send_event(type:, action:, identifiers:, data:)
120-
case @odp_config.odp_state
126+
case @odp_config&.odp_state
127+
when nil
128+
@logger.log(Logger::DEBUG, 'ODP event queue: cannot send before config has been set.')
129+
return
121130
when OdpConfig::ODP_CONFIG_STATE[:UNDETERMINED]
122131
@logger.log(Logger::DEBUG, 'ODP event queue: cannot send before the datafile has loaded.')
123132
return
@@ -154,7 +163,7 @@ def stop!
154163
end
155164

156165
def running?
157-
@thread && !!@thread.status && !@event_queue.closed?
166+
!!@thread && !!@thread.status && !@event_queue.closed?
158167
end
159168

160169
private
@@ -270,8 +279,8 @@ def process_config_update
270279
# Updates the configuration used to send events.
271280
flush_batch! unless @current_batch.empty?
272281

273-
@api_key = @odp_config.api_key
274-
@api_host = @odp_config.api_host
282+
@api_key = @odp_config&.api_key
283+
@api_host = @odp_config&.api_host
275284
end
276285
end
277286
end

lib/optimizely/odp/odp_manager.rb

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2022, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'optimizely/logger'
20+
require_relative '../helpers/constants'
21+
require_relative '../helpers/validator'
22+
require_relative '../exceptions'
23+
require_relative 'odp_config'
24+
require_relative 'lru_cache'
25+
require_relative 'odp_segment_manager'
26+
require_relative 'odp_event_manager'
27+
28+
module Optimizely
29+
class OdpManager
30+
ODP_LOGS = Helpers::Constants::ODP_LOGS
31+
ODP_MANAGER_CONFIG = Helpers::Constants::ODP_MANAGER_CONFIG
32+
ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
33+
34+
def initialize(disable:, segments_cache: nil, segment_manager: nil, event_manager: nil, logger: nil)
35+
@enabled = !disable
36+
@segment_manager = segment_manager
37+
@event_manager = event_manager
38+
@logger = logger || NoOpLogger.new
39+
@odp_config = OdpConfig.new
40+
41+
unless @enabled
42+
@logger.log(Logger::INFO, ODP_LOGS[:ODP_NOT_ENABLED])
43+
return
44+
end
45+
46+
unless @segment_manager
47+
segments_cache ||= LRUCache.new(
48+
Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
49+
Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS]
50+
)
51+
@segment_manager = Optimizely::OdpSegmentManager.new(segments_cache, nil, @logger)
52+
end
53+
54+
@event_manager ||= Optimizely::OdpEventManager.new(logger: @logger)
55+
56+
@segment_manager.odp_config = @odp_config
57+
@event_manager.start!(@odp_config)
58+
end
59+
60+
def fetch_qualified_segments(user_id:, options:)
61+
# Returns qualified segments for the user from the cache or the ODP server if not in the cache.
62+
#
63+
# @param user_id - The user id.
64+
# @param options - An array of OptimizelySegmentOptions used to ignore and/or reset the cache.
65+
#
66+
# @return - Array of qualified segments or nil.
67+
options ||= []
68+
unless @enabled
69+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
70+
return nil
71+
end
72+
73+
if @odp_config.odp_state == ODP_CONFIG_STATE[:UNDETERMINED]
74+
@logger.log(Logger::ERROR, 'Cannot fetch segments before the datafile has loaded.')
75+
return nil
76+
end
77+
78+
@segment_manager.fetch_qualified_segments(ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID], user_id, options)
79+
end
80+
81+
def identify_user(user_id:)
82+
unless @enabled
83+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).')
84+
return
85+
end
86+
87+
case @odp_config.odp_state
88+
when ODP_CONFIG_STATE[:UNDETERMINED]
89+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (datafile not ready).')
90+
return
91+
when ODP_CONFIG_STATE[:NOT_INTEGRATED]
92+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP not integrated).')
93+
return
94+
end
95+
96+
@event_manager.send_event(
97+
type: ODP_MANAGER_CONFIG[:EVENT_TYPE],
98+
action: 'identified',
99+
identifiers: {ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id},
100+
data: {}
101+
)
102+
end
103+
104+
def send_event(type:, action:, identifiers:, data:)
105+
# Send an event to the ODP server.
106+
#
107+
# @param type - the event type.
108+
# @param action - the event action name.
109+
# @param identifiers - a hash for identifiers.
110+
# @param data - a hash for associated data. The default event data will be added to this data before sending to the ODP server.
111+
unless @enabled
112+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
113+
return
114+
end
115+
116+
unless Helpers::Validator.odp_data_types_valid?(data)
117+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_INVALID_DATA])
118+
return
119+
end
120+
121+
@event_manager.send_event(type: type, action: action, identifiers: identifiers, data: data)
122+
end
123+
124+
def update_odp_config(api_key, api_host, segments_to_check)
125+
# Update the odp config, reset the cache and send signal to the event processor to update its config.
126+
return unless @enabled
127+
128+
config_changed = @odp_config.update(api_key, api_host, segments_to_check)
129+
unless config_changed
130+
@logger.log(Logger::DEBUG, 'Odp config was not changed.')
131+
return
132+
end
133+
134+
@segment_manager.reset
135+
@event_manager.update_config
136+
end
137+
138+
def close!
139+
return unless @enabled
140+
141+
@event_manager.stop!
142+
end
143+
end
144+
end

lib/optimizely/odp/odp_segment_manager.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
module Optimizely
2323
class OdpSegmentManager
2424
# Schedules connections to ODP for audience segmentation and caches the results
25-
attr_reader :odp_config, :segments_cache, :zaius_manager, :logger
25+
attr_accessor :odp_config
26+
attr_reader :segments_cache, :zaius_manager, :logger
2627

27-
def initialize(odp_config, segments_cache, api_manager = nil, logger = nil, proxy_config = nil)
28-
@odp_config = odp_config
28+
def initialize(segments_cache, api_manager = nil, logger = nil, proxy_config = nil)
29+
@odp_config = nil
2930
@logger = logger || NoOpLogger.new
3031
@zaius_manager = api_manager || ZaiusGraphQLApiManager.new(logger: @logger, proxy_config: proxy_config)
3132
@segments_cache = segments_cache
@@ -39,8 +40,8 @@ def initialize(odp_config, segments_cache, api_manager = nil, logger = nil, prox
3940
#
4041
# @return - Array of qualified segments.
4142
def fetch_qualified_segments(user_key, user_value, options)
42-
odp_api_key = @odp_config.api_key
43-
odp_api_host = @odp_config.api_host
43+
odp_api_key = @odp_config&.api_key
44+
odp_api_host = @odp_config&.api_host
4445
segments_to_check = @odp_config&.segments_to_check
4546

4647
if odp_api_key.nil? || odp_api_host.nil?

0 commit comments

Comments
 (0)