require 'base64' class OEHClient::Realtime::Interaction # # -------[ CONSTANTS ] # # constants used to construct the restful API Request URL API_REALTIME = "/interaction" API_OFFLINE = "/offline" # ONE attributes that are either in the request and/or returned in the response ONE_PARAM_URI = "uri" ONE_PARAM_DEVICE = "device" ONE_PARAM_CK = "customerKey" ONE_PARAM_TID = "tid" ONE_PARAM_SESSION = "session" ONE_PARAM_SK = "sk" ONE_PARAM_TS = "timestamp" ONE_PARAM_PROPERTIES = "properties" # Property Hash Keys ONE_PROPERTIES_NAME = "name" ONE_PROPERTIES_VALUE = "value" # Collection objects returned in the response ONE_RESPONSE_OPTIMIZATIONS = "optimizations" ONE_RESPONSE_OPTIMIZATION_DATA = "data" ONE_RESPONSE_ACTIONS = "actions" ONE_RESPONSE_TRACKERS = "trackers" ONE_RESPONSE_CAPTURES = "captures" # # -------[ CLASS ATTRIBUTES ] # attr_accessor :uri, # The full touchpoint/interaction URI used to post the interaction :space, # The configured OEHClient::Config::Space object that represents the OEH workspace :device, # The device information :timestamp, # A millisecond value representing a Time value of the interaction :keyname, # The customer keyname to use based on multi-key configuration. Default is customerKey :customer_key, # The customer to which this interaction is related :tid, # The Thunderhead ID (TID) to which this interaction is related :session, # The session id to which this interaction is related :optimizations, # The collection of optimizations that are relevent for the interaction :trackers, # The collection of tracking point data that are relevant for the interaction :captures, # The collection of capture point data that are relevant for the interactions :cookies # The JSON object of cookies returned within a ONE Request # # -------[ CLASS METHODS ] # class << self # class-level wrapper to post a new interaction to the OEH server using either the realtime or offline # API for an anonymous or known prospects/customer def post(site_key, uri, timestamp=nil, tid=nil, customer_key=nil, properties={}, device={}) # setup the baseline attributes hash with the site_key and interaction URI, which are the # minimal values needed for an interaction attributes = { :sk => site_key, :uri => uri } # conditionally merge the rest of the attributes if they are passed attributes.merge!(:timestamp => OEHClient::Helper::Timestamp.to_one_timestamp(timestamp)) unless(timestamp.blank?) attributes.merge!(:tid => tid) unless(timestamp.blank?) attributes.merge!(:device => device) if (customer_key.is_a?(Hash)) attributes.merge!(:ck => customer_key[:value]) if (customer_key.has_key?(:value)) attributes.merge!(:keyname => customer_key[:keyname]) if (customer_key.has_key?(:keyname)) else attributes.merge!(:ck => customer_key) unless(customer_key.blank?) end # create a new interaction using all attributes pass new_interaction = OEHClient::Realtime::Interaction.new(attributes) # Send the interaction for processing and return the instance of the interaction class new_interaction.send(properties) end # class-level wrapper to create a new instance of the OEHClient::Realtime::Interaction class, call the # send_update method, and return the resulting instance of the same class def update(site_key, uri, properties={}, tid=nil, customer_key=nil) # setup the baseline attributes hash with the site_key and interaction URI, which are the # minimal values needed for an interaction attributes = { :sk => site_key, :uri => uri } # conditionally merge the rest of the attributes if they are passed attributes.merge!(:tid => tid) if (!tid.nil? && !tid.empty?) if (customer_key.is_a?(Hash)) attributes.merge!(:ck => customer_key[:value]) if (customer_key.has_key?(:value)) attributes.merge!(:keyname => customer_key[:keyname]) if (customer_key.has_key?(:keyname)) else attributes.merge!(:ck => customer_key) unless(customer_key.blank?) end # create a new interaction using all parameters pass new_interaction = OEHClient::Realtime::Interaction.new(attributes) # send the update and return the current object new_interaction.send_update(properties) end end # # -------[ INSTANCE METHODS ] # # constructor that allows the passed Ruby Hash to be mapped to the def initialize(attributes=nil) # set the instance attributes is the parameter hash is created if (!attributes.nil? && attributes.kind_of?(Hash)) @uri = attributes[:uri] if (attributes.has_key?(:uri)) @keyname = attributes[:keyname] if (attributes.has_key?(:keyname)) @customer_key = attributes[:ck] if (attributes.has_key?(:ck)) @tid = attributes[:tid] if (attributes.has_key?(:tid)) @session = attributes[:session] if (attributes.has_key?(:session)) @timestamp = OEHClient::Helper::Timestamp.to_one_timestamp(attributes[:timestamp]) if (attributes.has_key?(:timestamp)) @space = OEHClient::Config::SpaceManager.instance.get(attributes[:sk]) if (attributes.has_key?(:sk)) @device = attributes[:device] if (attributes.has_key?(:device)) end end # send() will post a new interaction using either the realtime (current timestamp) or the offline (historic) # API interface based on the existence of a timestamp value def send(parameters={}) # raise the MissingParameterException when one (or more) of the miminal parameters are missing raise OEHClient::Exception::MissingParameterException.new(missing_minimal_parameters) unless (minimal_parameters?) # call the appropriate method based on the existance of the timestamp value #((!@timestamp.nil?) ? send_offline(parameters) : send_realtime(parameters)) send_realtime(parameters) # return the current instance interacton self end # send_new posts a new interaction using the existing instance data, but for a different touchpoint # URI. The method returns a new instance of the OEHClient::Realtime::Interaction class def send_new(uri, timestamp=nil, parameters={}) # raise the MissingParameterException when one (or more) of the miminal parameters are missing raise OEHClient::Exception::MissingParameterException.new(missing_minimal_parameters) unless (minimal_parameters?) # protect against NIL by creating a new Hash object if the parameters, for any reason is # NIL parameters ||= Hash.new # create a new interaction using all parameters from the existing other than the new touchpoint # URI and timestamp of the current Interaction instance. The method can be used to submit new # requests for the same customer, tid, & session new_interaction = OEHClient::Realtime::Interaction.new({ :uri => uri, :ck => @customer_key, :tid => @tid, :session => @session, :sk => @space.site_key, :timestamp => OEHClient::Helper::Timestamp.to_one_timestamp(timestamp) }) # Send the interaction for processing and return the current instance new_interaction.send(parameters) end # send_update allows the system to update the capture and tracking properties that are defined as # part of the existing interaction def send_update(properties={}) # raise the MissingParameterException when one (or more) of the miminal parameters are missing raise OEHClient::Exception::MissingParameterException.new(missing_minimal_parameters) unless (minimal_parameters?) # force the properties object to be an empty Hash if, for any reason, it is NIL properties ||= Hash.new # Call the PUT method to update the send_request(OEHClient::Helper::Request::PUT_METHOD, realtime_url, properties) unless properties.empty? # return the current object self end # # -------[ PRIVATE METHODS ] # private # send_realtime posts a new interaction occuring at the moment (in realtime). The response attributes # are mapped to the current instance attributes for all valid requests def send_realtime(properties={}) # Put timestamp in the header if it has been provides properties[:header] = {'X-ONE-Timestamp' => @timestamp}.merge!(OEHClient::Helper::Request.default_JSON_header()) unless (@timestamp.nil?) # POST the realtime interaction method response = send_request(OEHClient::Helper::Request::POST_METHOD, realtime_url, properties) # map the response message to the current instance attributes map_response(response) end # send_offline posts a historic interaction, using a specified timestamp def send_offline(properties={}) # PUT the offline interaction method response = send_request(OEHClient::Helper::Request::PUT_METHOD, offline_url, properties) # map the response message to the current instance attributes map_response(response) end # send_request acts as the wrapper to send all client requests to the ONE server in a unified manner def send_request(method, url, properties={}) # set the URL parameters for the site_key and the tid, of the value exists url_parameters = {:sk => @space.site_key} url_parameters.merge!({:tid => @tid}) unless (@tid.blank?) #url_parameters.merge!({:timestamp => @timestamp}) unless (@timestamp.nil?) send_args = {:params => url_parameters, :payload => ActiveSupport::JSON.encode(request_data(properties))} send_args[:header] = properties[:header] if (!properties.nil? && properties.has_key?(:header)) # send the POST or PUT methond along with the arguments to the OEHClient class OEHClient.send(method.downcase.to_sym, url, @space.oauth_consumer, send_args) end # map_response takes the attributes returned in an interaction response and maps it to exiting # attributes in the current instance of the interaction object def map_response(response) body = response[:body] # Save the tid and session data if they where not previously used in the request @tid = body[ONE_PARAM_TID] if (@tid.nil? || (!@tid.blank? && @tid != body[ONE_PARAM_TID])) @session = body[ONE_PARAM_SESSION] if @session.nil? # capture the optimizations returned from the request and map it to the OEHClient::Realtime::Optimization # class # initialize the optimizations collection if it is null @optimizations ||= Array.new # map each of the optimizations to the OEHClient::Realtime::Optmization class body[ONE_RESPONSE_OPTIMIZATIONS].each do | response_optimization | # decode the data of the optimization optimization_data = ActiveSupport::JSON.decode(Base64.decode64(response_optimization[ONE_RESPONSE_OPTIMIZATION_DATA])) # get the actions for each optimization optimization_data[ONE_RESPONSE_ACTIONS].each do | one_action | @optimizations << OEHClient::Realtime::Optimization.create(self, one_action) end end # store the cookies passed back by the system @cookies = response[:cookies] # capture the trackers returned from the request and mpt it to the OEHClient::Realtime::Tracker # class # TODO: Create OEHClient::Realtime::Tracker class @trackers = body[ONE_RESPONSE_TRACKERS] # capture the capture points returned from the request and map it to the OEHClient::Realtime::Capture # class # TODO: Create OEHClient::Realtime::Capture class @captures = body[ONE_RESPONSE_CAPTURES] end # minimal_parameters? determines if the minimal number of request parameters (uri & site_key) are # present in the current instance of the interaction class. This is a helper method that is used # before making any request def minimal_parameters?() ((!@uri.nil? && !@uri.empty?) && !@space.nil?) end # missing_minimal_parameters returns an array of the minimal attributes that are missing from the current # instance of OEHClient::Realtime::Interaction class def missing_minimal_parameters missing_parameters = [] missing_parameters << "site_key" if (!minimal_parameters? && @site_key.nil?) missing_parameters << "uri" if (!minimal_parameters? && @uri.nil?) missing_parameters end # request_url returns the base of the request URL used to make either a realtime or offline request # through published API def request_url() "#{OEHClient::Helper::Request::ONE_PROTOCOL}#{@space.host}#{OEHClient::Helper::Request::ONE_URI_PART}#{OEHClient::Helper::Request::API_URI_PART}#{OEHClient::Helper::Request::API_VERSION}" end # realtime_url is the interaction part of the API URI def realtime_url() "#{request_url}#{API_REALTIME}" end # offline_url appends the /offiline URI part to add support for historic data loading of interactions def offline_url() "#{realtime_url}#{API_OFFLINE}" end # request_data creates a properly formatted Hash object that represents the body of the request needed # for POST and PUT operations def request_data(passed_properties={}) # protect agains a NIL value in the passed_properties Hash passed_properties ||= Hash.new # Initialize the parameters hash parameters ||= Hash.new # merge in the different parts of the request data if the values currently exist within # the instance of the class parameters.merge!({ONE_PARAM_URI => @uri}) if (!@uri.nil? && @uri.length > 0) parameters.merge!({ONE_PARAM_DEVICE => @device}) if (!@device.nil? && !@device.empty?) parameters.merge!({"customerKeyName" => @keyname}) if (!@keyname.nil? && @keyname.length > 0) parameters.merge!({"customerKey" => @customer_key}) if (!@customer_key.nil? && @customer_key.length > 0) parameters.merge!({ONE_PARAM_SESSION => @session}) if (!@session.nil? && @session.length > 0) parameters.merge!({ONE_PARAM_TS => @timestamp}) if (!@timestamp.nil?) # for each of the properties hash entry, build a name/value pair in the properties collection properties = Array.new passed_properties.each do | key, value | properties << {ONE_PROPERTIES_NAME => key.to_s, ONE_PROPERTIES_VALUE => value} unless (key == :header) end # merge the properties (name / value) connections if there are additonal values to pass parameters.merge!({ONE_PARAM_PROPERTIES => properties}) if (properties.length > 0) # return the full parameter hash return(parameters) end end