Skip to content

Commit

Permalink
feat(eventfactory): Event Factory and UserEventFactory (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
rashidsp authored and Michael Ng committed Jul 31, 2019
1 parent 5a0f58a commit 8767636
Show file tree
Hide file tree
Showing 15 changed files with 1,111 additions and 69 deletions.
3 changes: 3 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ Metrics/ParameterLists:
- 'lib/optimizely/config_manager/http_project_config_manager.rb'
- 'lib/optimizely.rb'
- 'lib/optimizely/optimizely_factory.rb'
- 'lib/optimizely/event/entity/impression_event.rb'
- 'lib/optimizely/event/entity/snapshot_event.rb'
- 'lib/optimizely/event/entity/conversion_event.rb'
- 'lib/optimizely/event/entity/event_context.rb'

Naming/AccessorMethodName:
Exclude:
Expand Down
18 changes: 11 additions & 7 deletions lib/optimizely/event/entity/conversion_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@
# limitations under the License.
#
require_relative 'user_event'
require 'optimizely/helpers/date_time_utils'
module Optimizely
class ConversionEvent < UserEvent
attr_reader :event_context, :event, :user_id, :visitor_attributes, :tags, :bot_filtering
# Represents conversion event
attr_reader :event, :user_id, :visitor_attributes, :tags, :bot_filtering

def initialize(
event_context,
event,
user_id,
visitor_attributes,
tags,
bot_filtering = nil
event_context:,
event:,
user_id:,
visitor_attributes:,
tags:,
bot_filtering:
)
@event_context = event_context
@uuid = SecureRandom.uuid
@timestamp = Helpers::DateTimeUtils.create_timestamp
@event = event
@user_id = user_id
@visitor_attributes = visitor_attributes
Expand Down
2 changes: 1 addition & 1 deletion lib/optimizely/event/entity/decision.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Optimizely
class Decision
attr_reader :campaign_id, :experiment_id, :variation_id

def initialize(campaign_id, experiment_id, variation_id)
def initialize(campaign_id:, experiment_id:, variation_id:)
@campaign_id = campaign_id
@experiment_id = experiment_id
@variation_id = variation_id
Expand Down
30 changes: 21 additions & 9 deletions lib/optimizely/event/entity/event_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,34 @@
#
module Optimizely
class EventContext
attr_reader :account_id, :project_id, :revision, :client_name,
:client_version, :anonymize_ip
attr_reader :account_id, :project_id, :anonymize_ip, :revision, :client_name,
:client_version

def initialize(
account_id,
project_id,
revision,
client_name,
client_version,
anonymize_ip
account_id:,
project_id:,
anonymize_ip:,
revision:,
client_name:,
client_version:
)
@account_id = account_id
@project_id = project_id
@anonymize_ip = anonymize_ip
@revision = revision
@client_name = client_name
@client_version = client_version
@anonymize_ip = anonymize_ip
end

def as_json
{
account_id: @account_id,
project_id: @project_id,
anonymize_ip: @anonymize_ip,
revision: @revision,
client_name: @client_name,
client_version: @client_version
}
end
end
end
25 changes: 15 additions & 10 deletions lib/optimizely/event/entity/impression_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@
# limitations under the License.
#
require_relative 'user_event'
require 'optimizely/helpers/date_time_utils'
module Optimizely
class ImpressionEvent < UserEvent
attr_reader :event_context, :user_id, :experiment, :variation, :visitor_attributes,
:bot_filtering
attr_reader :user_id, :experiment_layer_id, :experiment_id, :variation_id,
:visitor_attributes, :bot_filtering

def initialize(
event_context,
user_id,
experiment,
variation,
visitor_attributes,
bot_filtering
event_context:,
user_id:,
experiment_layer_id:,
experiment_id:,
variation_id:,
visitor_attributes:,
bot_filtering:
)
@event_context = event_context
@uuid = SecureRandom.uuid
@timestamp = Helpers::DateTimeUtils.create_timestamp
@user_id = user_id
@experiment = experiment
@variation = variation
@experiment_layer_id = experiment_layer_id
@experiment_id = experiment_id
@variation_id = variation_id
@visitor_attributes = visitor_attributes
@bot_filtering = bot_filtering
end
Expand Down
11 changes: 4 additions & 7 deletions lib/optimizely/event/entity/snapshot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@
#
module Optimizely
class Snapshot
attr_reader :decisions, :events
attr_reader :events, :decisions

def initialize(events, decisions = nil)
def initialize(events:, decisions: nil)
@decisions = decisions
@events = events
end

def as_json
hash = {
events: @events,
decisions: @decisions
}
hash.delete_if { |_key, value| value.nil? }
hash = {events: @events}
hash[:decisions] = @decisions unless @decisions.nil?
hash
end
end
Expand Down
22 changes: 8 additions & 14 deletions lib/optimizely/event/entity/snapshot_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ class SnapshotEvent
attr_reader :entity_id, :uuid, :key, :timestamp, :revenue, :value, :tags

def initialize(
entity_id: nil,
uuid: nil,
key: nil,
timestamp: nil,
entity_id:,
uuid:,
key:,
timestamp:,
revenue: nil,
value: nil,
tags: nil
Expand All @@ -38,16 +38,10 @@ def initialize(
end

def as_json
hash = {
entity_id: @entity_id,
uuid: @uuid,
key: @key,
timestamp: @timestamp,
revenue: @revenue,
value: @value,
tags: @tags
}
hash.delete_if { |_key, value| value.nil? }
hash = {entity_id: @entity_id, uuid: @uuid, key: @key, timestamp: @timestamp}
hash[:revenue] = @revenue unless @revenue.nil?
hash[:value] = @value unless @value.nil?
hash[:tags] = @tags unless @tags.nil?
hash
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/optimizely/event/entity/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
#
module Optimizely
class Visitor
attr_reader :snapshots, :attributes, :visitor_id
def initialize(snapshots, attributes, visitor_id)
attr_reader :snapshots, :visitor_id, :attributes
def initialize(snapshots:, visitor_id:, attributes:)
@snapshots = snapshots
@attributes = attributes
@visitor_id = visitor_id
@attributes = attributes
end

def as_json
{
snapshots: @snapshots,
attributes: @attributes,
visitor_id: @visitor_id
visitor_id: @visitor_id,
attributes: @attributes
}
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/optimizely/event/entity/visitor_attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
module Optimizely
class VisitorAttribute
attr_reader :entity_id, :key, :type, :value
def initialize(entity_id, key, type, value)
def initialize(entity_id:, key:, type:, value:)
@entity_id = entity_id
@key = key
@type = type
Expand Down
154 changes: 154 additions & 0 deletions lib/optimizely/event/event_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# frozen_string_literal: true

#
# Copyright 2019, 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_relative 'entity/event_batch'
require_relative 'entity/conversion_event'
require_relative 'entity/decision'
require_relative 'entity/impression_event'
require_relative 'entity/snapshot'
require_relative 'entity/snapshot_event'
require_relative 'entity/visitor'
require 'optimizely/helpers/validator'
module Optimizely
class EventFactory
# EventFactory builds LogEvent objects from a given user_event.
class << self
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
ENDPOINT = 'https://logx.optimizely.com/v1/events'
POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
ACTIVATE_EVENT_KEY = 'campaign_activated'

def create_log_event(user_events, logger)
@logger = logger
builder = Optimizely::EventBatch::Builder.new

user_events = [user_events] unless user_events.is_a? Array

visitors = []
user_context = nil
user_events.each do |user_event|
if user_event.is_a? Optimizely::ImpressionEvent
visitor = create_impression_event_visitor(user_event)
visitors.push(visitor)
elsif user_event.is_a? Optimizely::ConversionEvent
visitor = create_conversion_event_visitor(user_event)
visitors.push(visitor)
else
@logger.log(Logger::WARN, 'invalid UserEvent added in a list.')
next
end
user_context = user_event.event_context
end

return nil if visitors.empty?

builder.with_account_id(user_context[:account_id])
builder.with_project_id(user_context[:project_id])
builder.with_client_version(user_context[:client_version])
builder.with_revision(user_context[:revision])
builder.with_client_name(user_context[:client_name])
builder.with_anonymize_ip(user_context[:anonymize_ip])
builder.with_enrich_decisions(true)

builder.with_visitors(visitors)
event_batch = builder.build
Event.new(:post, ENDPOINT, event_batch.as_json, POST_HEADERS)
end

def build_attribute_list(user_attributes, project_config)
visitor_attributes = []
user_attributes&.keys&.each do |attribute_key|
# Omit attribute values that are not supported by the log endpoint.
attribute_value = user_attributes[attribute_key]
next unless Helpers::Validator.attribute_valid?(attribute_key, attribute_value)

attribute_id = project_config.get_attribute_id attribute_key
next if attribute_id.nil?

visitor_attributes.push(
entity_id: attribute_id,
key: attribute_key,
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
value: attribute_value
)
end

return unless Helpers::Validator.boolean? project_config.bot_filtering

# Append Bot Filtering Attribute
visitor_attributes.push(
entity_id: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
key: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
value: project_config.bot_filtering
)
end

private

def create_impression_event_visitor(impression_event)
decision = Optimizely::Decision.new(
campaign_id: impression_event.experiment_layer_id,
experiment_id: impression_event.experiment_id,
variation_id: impression_event.variation_id
)

snapshot_event = Optimizely::SnapshotEvent.new(
entity_id: impression_event.experiment_layer_id,
timestamp: impression_event.timestamp,
uuid: impression_event.uuid,
key: ACTIVATE_EVENT_KEY
)

snapshot = Optimizely::Snapshot.new(
events: [snapshot_event.as_json],
decisions: [decision.as_json]
)

visitor = Optimizely::Visitor.new(
snapshots: [snapshot.as_json],
visitor_id: impression_event.user_id,
attributes: impression_event.visitor_attributes
)
visitor.as_json
end

def create_conversion_event_visitor(conversion_event)
revenue_value = Helpers::EventTagUtils.get_revenue_value(conversion_event.tags, @logger)
numeric_value = Helpers::EventTagUtils.get_numeric_value(conversion_event.tags, @logger)
snapshot_event = Optimizely::SnapshotEvent.new(
entity_id: conversion_event.event['id'],
timestamp: conversion_event.timestamp,
uuid: conversion_event.uuid,
key: conversion_event.event['key'],
revenue: revenue_value,
value: numeric_value,
tags: conversion_event.tags
)

snapshot = Optimizely::Snapshot.new(events: [snapshot_event.as_json])

visitor = Optimizely::Visitor.new(
snapshots: [snapshot.as_json],
visitor_id: conversion_event.user_id,
attributes: conversion_event.visitor_attributes
)
visitor.as_json
end
end
end
end
Loading

0 comments on commit 8767636

Please sign in to comment.