Skip to content

Commit 5a2ae4e

Browse files
feat: add ODP GraphQLApi interface (#308)
* add ZaiusGraphQLApiManager
1 parent 1263f22 commit 5a2ae4e

File tree

5 files changed

+567
-6
lines changed

5 files changed

+567
-6
lines changed

lib/optimizely/event_dispatcher.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#
1818
require_relative 'exceptions'
1919
require_relative 'helpers/http_utils'
20+
require_relative 'helpers/constants'
2021

2122
module Optimizely
2223
class NoOpEventDispatcher
@@ -26,9 +27,6 @@ def dispatch_event(event); end
2627
end
2728

2829
class EventDispatcher
29-
# @api constants
30-
REQUEST_TIMEOUT = 10
31-
3230
def initialize(logger: nil, error_handler: nil, proxy_config: nil)
3331
@logger = logger || NoOpLogger.new
3432
@error_handler = error_handler || NoOpErrorHandler.new
@@ -40,7 +38,7 @@ def initialize(logger: nil, error_handler: nil, proxy_config: nil)
4038
# @param event - Event object
4139
def dispatch_event(event)
4240
response = Helpers::HttpUtils.make_request(
43-
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config
41+
event.url, event.http_verb, event.params.to_json, event.headers, Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT], @proxy_config
4442
)
4543

4644
error_msg = "Event failed to dispatch with response code: #{response.code}"

lib/optimizely/helpers/constants.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2016-2020, Optimizely and contributors
4+
# Copyright 2016-2020, 2022, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -382,6 +382,12 @@ module Constants
382382
'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for rule '%s': %s."
383383
}.merge(AUDIENCE_EVALUATION_LOGS).freeze
384384

385+
ODP_LOGS = {
386+
FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).',
387+
ODP_EVENT_FAILED: 'ODP event send failed (invalid url).',
388+
ODP_NOT_ENABLED: 'ODP is not enabled.'
389+
}.freeze
390+
385391
DECISION_NOTIFICATION_TYPES = {
386392
'AB_TEST' => 'ab-test',
387393
'FEATURE' => 'feature',
@@ -406,6 +412,18 @@ module Constants
406412
'REQUEST_TIMEOUT' => 10
407413
}.freeze
408414

415+
EVENT_DISPATCH_CONFIG = {
416+
REQUEST_TIMEOUT: 10
417+
}.freeze
418+
419+
ODP_GRAPHQL_API_CONFIG = {
420+
REQUEST_TIMEOUT: 10
421+
}.freeze
422+
423+
ODP_REST_API_CONFIG = {
424+
REQUEST_TIMEOUT: 10
425+
}.freeze
426+
409427
HTTP_HEADERS = {
410428
'IF_MODIFIED_SINCE' => 'If-Modified-Since',
411429
'LAST_MODIFIED' => 'Last-Modified'
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 'json'
20+
21+
module Optimizely
22+
class ZaiusGraphQlApiManager
23+
# Interface that handles fetching audience segments.
24+
25+
def initialize(logger: nil, proxy_config: nil)
26+
@logger = logger || NoOpLogger.new
27+
@proxy_config = proxy_config
28+
end
29+
30+
# Fetch segments from the ODP GraphQL API.
31+
#
32+
# @param api_key - public api key
33+
# @param api_host - domain url of the host
34+
# @param user_key - vuid or fs_user_id (client device id or fullstack id)
35+
# @param user_value - value of user_key
36+
# @param segments_to_check - array of segments to check
37+
38+
def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check)
39+
url = "#{api_host}/v3/graphql"
40+
41+
headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
42+
43+
payload = {
44+
'query' => %'query {customer(#{user_key}: "#{user_value}")' \
45+
"{audiences(subset:#{segments_to_check || '[]'}) {edges {node {name state}}}}}"
46+
}.to_json
47+
48+
begin
49+
response = Helpers::HttpUtils.make_request(
50+
url, :post, payload, headers, Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config
51+
)
52+
rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e
53+
@logger.log(Logger::DEBUG, "GraphQL download failed: #{e}")
54+
log_failure('network error')
55+
return []
56+
rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e
57+
log_failure(e)
58+
return []
59+
end
60+
61+
status = response.code.to_i
62+
if status >= 400
63+
log_failure(status)
64+
return []
65+
end
66+
67+
begin
68+
response = JSON.parse(response.body)
69+
rescue JSON::ParserError
70+
log_failure('JSON decode error')
71+
return []
72+
end
73+
74+
if response.include?('errors')
75+
error_class = response['errors']&.first&.dig('extensions', 'classification') || 'decode error'
76+
if error_class == 'InvalidIdentifierException'
77+
log_failure('invalid identifier', Logger::WARN)
78+
else
79+
log_failure(error_class)
80+
end
81+
return []
82+
end
83+
84+
audiences = response.dig('data', 'customer', 'audiences', 'edges')
85+
unless audiences
86+
log_failure('decode error')
87+
return []
88+
end
89+
90+
audiences.filter_map do |edge|
91+
name = edge.dig('node', 'name')
92+
state = edge.dig('node', 'state')
93+
unless name && state
94+
log_failure('decode error')
95+
return []
96+
end
97+
state == 'qualified' ? name : nil
98+
end
99+
end
100+
101+
private
102+
103+
def log_failure(message, level = Logger::ERROR)
104+
@logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message))
105+
end
106+
end
107+
end

spec/event_dispatcher_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
event.http_verb,
5353
event.params.to_json,
5454
event.headers,
55-
Optimizely::EventDispatcher::REQUEST_TIMEOUT,
55+
Optimizely::Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT],
5656
proxy_config
5757
)
5858

0 commit comments

Comments
 (0)