Skip to content

feat: add ODP GraphQLApi interface #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions lib/optimizely/event_dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#
require_relative 'exceptions'
require_relative 'helpers/http_utils'
require_relative 'helpers/constants'

module Optimizely
class NoOpEventDispatcher
Expand All @@ -26,9 +27,6 @@ def dispatch_event(event); end
end

class EventDispatcher
# @api constants
REQUEST_TIMEOUT = 10

def initialize(logger: nil, error_handler: nil, proxy_config: nil)
@logger = logger || NoOpLogger.new
@error_handler = error_handler || NoOpErrorHandler.new
Expand All @@ -40,7 +38,7 @@ def initialize(logger: nil, error_handler: nil, proxy_config: nil)
# @param event - Event object
def dispatch_event(event)
response = Helpers::HttpUtils.make_request(
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config
event.url, event.http_verb, event.params.to_json, event.headers, Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT], @proxy_config
)

error_msg = "Event failed to dispatch with response code: #{response.code}"
Expand Down
20 changes: 19 additions & 1 deletion lib/optimizely/helpers/constants.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

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

ODP_LOGS = {
FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).',
ODP_EVENT_FAILED: 'ODP event send failed (invalid url).',
ODP_NOT_ENABLED: 'ODP is not enabled.'
}.freeze

DECISION_NOTIFICATION_TYPES = {
'AB_TEST' => 'ab-test',
'FEATURE' => 'feature',
Expand All @@ -406,6 +412,18 @@ module Constants
'REQUEST_TIMEOUT' => 10
}.freeze

EVENT_DISPATCH_CONFIG = {
REQUEST_TIMEOUT: 10
}.freeze

ODP_GRAPHQL_API_CONFIG = {
REQUEST_TIMEOUT: 10
}.freeze

ODP_REST_API_CONFIG = {
REQUEST_TIMEOUT: 10
}.freeze

HTTP_HEADERS = {
'IF_MODIFIED_SINCE' => 'If-Modified-Since',
'LAST_MODIFIED' => 'Last-Modified'
Expand Down
107 changes: 107 additions & 0 deletions lib/optimizely/odp/zaius_graphql_api_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# frozen_string_literal: true

#
# Copyright 2022, Optimizely and contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'json'

module Optimizely
class ZaiusGraphQlApiManager
# Interface that handles fetching audience segments.

def initialize(logger: nil, proxy_config: nil)
@logger = logger || NoOpLogger.new
@proxy_config = proxy_config
end

# Fetch segments from the ODP GraphQL API.
#
# @param api_key - public api key
# @param api_host - domain url of the host
# @param user_key - vuid or fs_user_id (client device id or fullstack id)
# @param user_value - value of user_key
# @param segments_to_check - array of segments to check

def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check)
url = "#{api_host}/v3/graphql"

headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}

payload = {
'query' => %'query {customer(#{user_key}: "#{user_value}")' \
"{audiences(subset:#{segments_to_check || '[]'}) {edges {node {name state}}}}}"
}.to_json

begin
response = Helpers::HttpUtils.make_request(
url, :post, payload, headers, Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config
)
rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e
@logger.log(Logger::DEBUG, "GraphQL download failed: #{e}")
log_failure('network error')
return []
rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e
log_failure(e)
return []
end

status = response.code.to_i
if status >= 400
log_failure(status)
return []
end

begin
response = JSON.parse(response.body)
rescue JSON::ParserError
log_failure('JSON decode error')
return []
end

if response.include?('errors')
error_class = response['errors']&.first&.dig('extensions', 'classification') || 'decode error'
if error_class == 'InvalidIdentifierException'
log_failure('invalid identifier', Logger::WARN)
else
log_failure(error_class)
end
return []
end

audiences = response.dig('data', 'customer', 'audiences', 'edges')
unless audiences
log_failure('decode error')
return []
end

audiences.filter_map do |edge|
name = edge.dig('node', 'name')
state = edge.dig('node', 'state')
unless name && state
log_failure('decode error')
return []
end
state == 'qualified' ? name : nil
end
end

private

def log_failure(message, level = Logger::ERROR)
@logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message))
end
end
end
2 changes: 1 addition & 1 deletion spec/event_dispatcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
event.http_verb,
event.params.to_json,
event.headers,
Optimizely::EventDispatcher::REQUEST_TIMEOUT,
Optimizely::Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT],
proxy_config
)

Expand Down
Loading